How do I create auto increment value for a field in elastic search - node.js

I'm setting up an API for reading employee details, How can I se index ID and employee id as auto-increment
I'm using express4.17 and elasticsearch7.3
const client = require('../client/esConnect')
module.exports.createAttendance = async (body) => {
try{
let payload = payloadValue(body);
let response = await client.index(payload);
return response;
}catch(error){
console.log(error);
throw error;
}
}
payloadValue = (body) => {
return payload = {
index: "attendance",
type: "_doc",
id: 4,
body: {
empid: body.empid,
date: body.date,
present: body.present
}
}
}

As far as I can remember, Elasticsearch does not support (anymore) this kind of auto-increment ID. The ID field is automatically generated as a hash.
However, if you need to store somehow that number, you can add them within your code as another field or just overwriting the ID field.
Remember that Elasticsearch is NOT intended to be used as a Structured / ER database (like SQL).

Related

Firebase Cloud Function batch write update document overwrote the entire document?

The following Cloud Function has a batch write operation that, in part, updates a single field in a document. This overwrote the entire document and now the document has a single field joinedCount: -1. Is this not the way to update individual fields in documents without overwriting them?
exports.deleteUserTEST = functions.https.onCall(async (data, _context) => {
const uId = data.userId;
const db = admin.firestore();
try {
const batch = db.batch();
const settingsDoc = await db.collection("userSettings").doc(uId).get();
const joinedIds = settingsDoc.get("private.joinedIds");
Object.keys(joinedIds).forEach(function(jId, _index) {
batch.update(
db.collection("profiles").doc(jId),
{
private: {
joinedCount: admin.firestore.FieldValue.increment(-1), // <-- the culprit
},
},
);
});
await batch.commit();
} catch (error) {
throw new functions.https.HttpsError("unknown", "Failed the delete the user's content.", error);
}
return Promise.resolve(uId);
});
Moving the solution found in the comments by #Dharmaraj into a community answer, this problem was caused by the structure of the document.
Since all the data in the document was inside the private map field, passing a new map through the update method would make it appear that the entire document was being overwritten instead of updated.
In this case, you would need to access the fields through dot notation. This allows those inner fields within the map to be updated, without replacing the entire private map:
Object.keys(joinedIds).forEach(function(jId, _index) {
batch.update(db.collection("profiles").doc(jId), {
"private.joinedCount": admin.firestore.FieldValue.increment(-1)
});
});
Another example from the documentation:
import { doc, setDoc, updateDoc } from "firebase/firestore";
// Create an initial document to update.
const frankDocRef = doc(db, "users", "frank");
await setDoc(frankDocRef, {
name: "Frank",
favorites: { food: "Pizza", color: "Blue", subject: "recess" },
age: 12
});
// To update age and favorite color:
await updateDoc(frankDocRef, {
"age": 13,
"favorites.color": "Red"
});

Check if the body parameter is not null and update on MongoDB

I'm trying to update a document in MongoDB using NodeJS (NextJS). My current code is:
import connect from "../../util/mongodb";
async function api(req, res) {
if (req.method === "POST") {
const { id } = req.body;
const { name } = req.body;
const { email} = req.body;
const { anything1 } = req.body;
const { anything2 } = req.body;
if (!id) {
res.status(400).json({ "error": "missing id param" });
return;
}
const { db } = await connect();
const update = await db.collection("records_collection").findOneAndUpdate(
{ id },
{
$set: {
name,
email,
anything1,
anything2
}
},
{ returnOriginal: false }
);
res.status(200).json(update);
} else {
res.status(400).json({ "error": "wrong request method" });
}
}
export default api;
Everything is working. But I would like to request only the ID as mandatory, and for the other information, leave optional.
In this code, passing the id and name for example, the other three fields (email, anything1 and anything2) will be null in the document.
It is possible to implement the update without requiring all document information and ignore when body fields are null? (As a beginner in NodeJS and MongoDB, the only way to do that that comes to my head now is to surround it all by a lot of if...)
If I've understood your question correctly you can achieve your requirement using the body object in $set stage.
If there is a field which not exists in the object, mongo will not update that field.
As an example check this example where only name field is updated and the rest of fields are not set null.
Another example with 2 fields updated and 3 fields.
You can see how only is updated the field into object in the $set.
So you only need to pass the object received into the query. Something like this:
const update = await db.collection("records_collection").findOneAndUpdate(
{ id },
{
$set: req.body
},
{ returnOriginal: false }
);

Mongoose overwriting data in MongoDB with default values in Subdocuments

I currently have a problem with updating data in MongoDB via mongoose. I have a nested Document of the following structure
const someSchema:Schema = new mongoose.Schema({
Title: String,
Subdocuments: [{
SomeValue: String
Position: {
X: {type: Number, default: 0},
Y: {type: Number, default: 0},
Z: {type: Number, default: 0}
}
}]
});
Now my problem is that I am updating this with findOneAndUpdateById. I have previously set the position to values other than the default. I want to update leaving the position as is by making my request without the Position as my frontend should never update it (another application does).
However the following call
const updateById = async (Id: string, NewDoc: DocClass) => {
let doc: DocClass | null = await DocumentModel.findOneAndUpdate(
{ _id: Id },
{ $set: NewDoc },
{ new: true, runValidators: true });
if (!doc) {
throw createError.documentNotFound(
{ msg: `The Document you tried to update (Id: ${Id}) does not exist` }
);
}
return doc;
}
Now this works fine if I don't send a Title for the value in the root of the schema (also if i turn on default values for that Title) but if I leave out the Position in the Subdocument it gets reset to the default values X:0, Y:0, Z:0.
Any ideas how I could fix this and don't set the default values on update?
Why don't you find the document by id, update the new values, then save it?
const updateById = async (Id: string, NewDoc: Training) => {
const doc: Training | null = await TrainingModel.findById({ _id: Id });
if (!doc) {
throw createError.documentNotFound(
{ msg: `The Document you tried to update (Id: ${Id}) does not exist` }
);
}
doc.title = NewDoc.title;
doc.subdocument.someValue = NewDoc.subdocument.someValue
await doc.save();
return doc;
}
check out the link on how to update a document with Mongoose
https://mongoosejs.com/docs/documents.html#updating
Ok after I gave this some thought over the weekend I got to the conclusion that the behaviour of mongodb was correct.
Why?
I am passing a document and a query to the database. MongoDb then searches Documents with that query. It will update all Fields for which a value was supplied. If for Title I set a new string, the Title will get replaced with that one, a number with that one and so on. Now for my Subdocument I am passing an array. And as there is no query, the correct behavioud is that that field will get set to the array. So the subdocuments are not updated but indeed initialized. Which will correctly cause the default values to be set. If I just want to update the subdocuments this is not the correct way
How to do it right
For me the ideal way is to seperate the logic and create a seperate endpoint to update the subdocuments with their own query. So to update all given subdocuments the function would look something like this
const updateSubdocumentsById= async ({ Id, Subdocuments}: { Id: string; Subdocuments: Subdocument[]; }): Promise<Subdocument[]> => {
let updatedSubdocuments:Subdocument[] = [];
for (let doc of Subdocuments){
// Create the setter
let set = {};
for (let key of Object.keys(doc)){
set[`Subdocument.$.${key}`] = doc[key];
}
// Update the subdocument
let updatedDocument: Document| null = await DocumentModel.findOneAndUpdate(
{"_id": Id, "Subdocuments._id": doc._id},
{
"$set" : set
},
{ new : true}
);
// Aggregate and return the updated Subdocuments
if(updatedDocument){
let updatedSubdocument:Subdocument = updatedTraining.Subdocuments.filter((a: Subdocument) => a._id.toString() === doc._id)[0];
if(updatedSubdocument) updatedSubdocuments.push(updatedSubdocument);
}
}
return updatedSubdocuments;
}
Been struggling with this myself all evening. Just worked out a really simple solution that as far as I can see works perfectly.
const venue = await Venue.findById(_id)
venue.name = name
venue.venueContact = venueContact
venue.address.line1 = line1 || venue.address.line1
venue.address.line2 = line2 || venue.address.line2
venue.address.city = city || venue.address.city
venue.address.county = county || venue.address.county
venue.address.postCode = postCode || venue.address.postCode
venue.address.country = country || venue.address.country
venue.save()
res.send(venue)
The result of this is any keys that don't receive a new value will just be replaced by the original values.

Nested documents in mongodb implementation, just like Reddit comments

In my project, I would like to implement a comment section which consists of list of comments.
const myschema= mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
//other fields
comments : [comment]
},{collection : 'TABLE_NAME'} );
const comment= mongoose.Schema({
_id : mongoose.Schema.Types.ObjectId,
commentid: String, // id consists of uuid1()
senderid: Number, //user's id
body: String, // useful information
parent: String,// parent id consists of uuid1()
children: [] // I couldn't add children = [comment], it gives me an error,
//instead, I make it empty and will fill it when a comment comes
})
In the request tab, I will receive a JSON in the following format:
{
"userid": "NUMBERID HERE",
"comment": "COMMENT BODY",
"parent" : "Comment's parent id"
}
I would like to add a comment which can be a child of another comment. How can I search and find the appropriate position?
If there are no parent in JSON body, I'm doing this:
// import comment somewhere at the beginning
.then(doc =>{
var newc= new comment();
newc.cid = uuidv1();
newc.sender = req.body.userid;
newc.body = req.body.comment;
newc.parent = "";
newc.children = "";
doc.comments.push(newc);
// save to DB
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
res.status(500).json({error: err});
})
}
I have no idea how to find a comment that resides in a deep level
You cannot search for an array element or object property given an unspecified, arbitrarily-nested depth. It just isn't possible. You would need to instead handle this in the application layer. Denormalizing your data is fine in many cases, but arbitrary nesting depths isn't a recommended use case for data denormalization, especially since you can't index efficiently!
If you want a pure MongoDB solution, then you'll need a different document structure. I would recommend taking a look at the documentation, particularly the section concerning an array of ancestors, in order to properly model your data.
I have found a solution.
The manually traversing the comments and inserting in the right place works. However, it only works up to some level. In my case, I can insert a comment below comment and save it. I can also insert a 3rd deep level comment and see the JSON dump of the object that I have inserted or even wrap it in an HTTP response object and send to the client. But the database does not save this update.
What I did is, after inserting the comment in the correct place I added this code before saving to the database.
doc.markModified('comments'); // this is added
doc.save().then(/*prepare response*/);
Somehow the MongoDB or mongoose or javascript interpreter knows the document has been changed and save the updated version. If the markmodified part is not specified, probably the compiler thinks that the object has not been modified and skips it.
Hopefully, this helps people who encounter this issue.
This is my implementation
// find the document which comment is going to be inserted
MyDOC.findOne({'id' : req.params.id})
.exec()
.then(doc =>{
// if the comment has a parent, it should be inserted in nested
if(req.body.parent != null && req.body.parent != undefined)
{
// find correct position and insert
findComment(doc.comments, req.body);
doc.markModified('comments');
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
console.log(err);
res.status(500).json({error: err});
})
}
else
{
//Add comment to the root level
var comment= new Comment();
comment.cid = uuidv1();
comment.body = req.body.comment;
comment.parent = "";
doc.comments.push(comment);
doc.markModified('comments');
// save to DB
doc
.save()
.then(docres =>{
res.status(200).json(docres);
})
.catch(err => {
console.log(err);
res.status(500).json({error: err});
})
}
})
function findComment(comment, body)
{
comment.forEach(element => {
if(element.children.length != 0)
{
if(element.cid === body.parent)
{
// found, insert
var comment= new Comment();
comment.cid = uuidv1();
comment.body = body.comment;
comment.parent = body.parent;
element.children.push(comment);
return true;
}
if(findComment(element.children, body, usr))
return true;
}
else
{
// this comment does not have children. If this comments id and the body's parent is equal, add it
if(element.cid === body.parent)
{
// found, insert
var comment= new Comment();
comment.cid = uuidv1();
comment.body = body.comment;
comment.parent = body.parent;
element.children.push(comment);
return true;
}
return false;
}
});
}

how to write a query where it uses two fields in the document

The title is not enough maybe. Here let me explain.
Assume I have database structure as
{
name: "alex",
age: "21",
location: "university-alex"
}
I am aware that this database structure is not rational but I just want to show my problem in a shortest way.
I want to get the documents where location includes name field value. Here university-alex includes ,alex so it should return as a result of the query.
What have I done so far?
I wrote this query but couldn't get a result.
db.collection.find({location: {$regex: "$name"}})
How can I edit it?
I think what you're trying to achieve can be done with the $where operator.
According to this part of the documentation
db.collection.find({ $where: function() {
return (this.location.includes(this.name));
} } );
Or you can just pass the JS expression to the find method :
db.collection.find(function() {
return (this.location.includes(this.name));
});
Hope it helps,
best regards
A part of #boehm_s answer that's viable and quite clear you can also create a multiple field index that you can use to find what you need. This kind of index is very useful if you want to perform a combined query approach, e.g. looking in more than one string field if a passed parameters match their content or is contained into them. You can take a look at this doc page about Text Indexes. I don't know if you are using Mongoose or not but this answer may be useful.
Pay attention to the fact that this approach of mine would return ALL the documents that contain, in one or both field,s the "word" you are looking for.
After the creation of the index on your fields
db.collection.createIndex(
{
name: "text",
location: "text"
}
)
Let's suppose you've named this index txtIndex, then, you can do
Mongo Driver for Node Way
. . .
let userProjection = {
"name": 1,
"age": 1,
"location": 1
};
/**
* #param req Contains information to find a user
* #param req.findMe Contains name concatenated to location
*/
let findUsers = (req) => {
letUsers = db.collection('users');
return new Promise((resolve, reject) => {
User.findOne({'txtIndex': params.body.findMe}, {fields: userProjection},(err, user) => {
if (err) {
return reject({message: 'MongoDB Error', err: err});
}
if (!user) {
return reject({message: 'User not found!'});
}
return resolve(user);
});
});
}
Mongoose Way
let Users = require('./users-model.js);
/**
* #param req Contains information to find a user
* #param req.findMe Contains name concatenated to location
*/
let findUsers = (req) => {
Users.findOne({txtIndex: req.body.FindMe}).then( function (err, user) {
if (err) {
return reject({message: 'MongoDB Error', err: err});
}
if (!user) {
return reject({message: 'User not found!'});
}
return resolve(user);
});
}

Resources