I noticed a strange behavior with the mongodb node.js driver findOneAndUpate()...
I mistakenly gave it just a objectId string....thinking it would default to searching by _id field of a document.... so, when I used
User.prototype.updatePetArray = function(user, petElement) {
return this.collection.findOneAndUpdate(user,
{ $push: { pets: petElement } },
{ returnOriginal: false,
maxTimeMS: QUERY_TIME});
}
it pulled up and modified this document, which does not have this number at all:
{ "_id" : ObjectId("56d4e2a381c9c28b3056f792"), "username" : "bob123", "location" : "AT", ...}
Why did it modify this document when 56d4d35881c9c28b3056f78a is not in the document?
After I test it following your code with one non-exist ObjectID,
var col = db.collection('users');
col.findOneAndUpdate('56cd129222222', {fname: 'Daved'}, function(err, r) {
if (err)
console.log(err);
else
console.log(r);
db.close();
});
As a result the first document in the collection was changed .
Per this doc
findOneAndUpdate(filter, update, options, callback)
filter: Specify an empty document { } to update the first document returned in the collection.
So I think this non-exist ObjectId is consider to the same behavior with {}
After reviewing the source code of findAndModify, eventually, the
// Execute the command
self.s.db.command(queryObject
is invoked, and the queryObject is
var queryObject = {
'findandmodify': self.s.name
, 'query': query
};
So I test runCommand in mongo shell with non-exist ObjectId as below, as result, the first document is returned.
> db.users.runCommand({findandmodify: 'users', query: '123ddae344', update: true
})
{
"lastErrorObject" : {
"updatedExisting" : true,
"n" : 1
},
"value" : {
"_id" : ObjectId("56c687275d81735019263d1f"),
"fname" : "Daved"
},
"ok" : 1
}
The docs keep saying the filter parameter should be an object.
The wrong behavior is likely to some side effect of mis-interpreting the value, being a string not an object (and maybe a truthy value, non-empty string).
Related
MongoDB collection/doc :
{
_id:something,
name:something,
todos: [
{key:1234},
{key:5678}
]
}
I want to delete the object with key:5678 using mongoose query. I did something like this but It's not deleting the object at all and returning the User with unchanged todos array.
Node Route:
router.post('/:action', async (req, res) => {
try {
if (req.params.action == "delete") {
const pullTodo = { $pull: { todos: { key: 5678 } } }
const todo = await User.findOneAndUpdate({ _id:req.body.id} },pullTodo)
if (todo) {
res.json({ msg: "Todo Deleted", data: todo });
}
}
} catch (err) {
console.log(err)
}
})
I have allso tried findByIdAndUpdate(),update() methods but none of them deleting the object from the array. Getting User as a result without deleting the object from the array.
It is working, but you forgot give an configuration to the function call of Model.findByIdAndUpdate..
const todo = await User.findOneAndUpdate({ _id:req.body.id} },pullTodo, {new: true});
// if {new: true} is enabled, then it will give the latest & updated document from the
// result of the query. By default it gives the previous document.
Do some, research first. This isn't a type of question that should be asked. It's already been answered several times in stackoverflow.
Try using Model.findOneAndRemove() instead. It also makes only one call to the database.
Example: User.findOneAndRemove({'todos':{'$elemMatch':{key}});
can you please re-visit your JSON like below and see if this design works for you.
> db.test6.find()
{ "_id" : "mallik", "name" : "mallik-name", "todos1" : { "key1" : [ 1234, 5678 ] } }
> db.test6.update({},{$pull:{"todos1.key1":5678}},{multi:true});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.test6.find()
{ "_id" : "mallik", "name" : "mallik-name", "todos1" : { "key1" : [ 1234 ] } }
>
I was adding a key property to every "Todo" using "mongoose.Types.ObjectId()" and I was querying with id string like : "5f439....." which was the problem. So I used:
1st Step: MongoId = require('mongodb').ObjectID;
2nd Step: const key = MongoId (**<actual id here>**);
I currently have an upsert function in my project which works but my main problem is that it creates another instance of the record, and updates the new instance instead. This is the code:
router.route('/carousel/update/:_id').put(function(req, res) {
var id;
if(req.params._id == 'undefined'){
id = crypto.randomBytes(12).toString('hex');
}
else {
id = ObjectId(req.params._id)
}
db.collection('home').updateOne({"_id": id},
{$set: req.body}, {upsert: true}, (err, results) => {
if (err) throw err;
res.send(results)
console.log(req.body)
});
});
The problem:
1. It mystifies me that mongoDB takes my crypto generated _id and takes it as the new _id for the upserted document. Why is that? When {upsert: true}, isn't mongoDB supposed to generate a new _id?
2. Because of the nature of problem 1, whenever I try to update the original document, it updates the upserted document instead since they have the same _id values even though their _ids are positioned at different document levels.
In conclusion, when given a 'home' document, how do I upsert correctly without adding a new record with the same values and _ids?
Thanks for your help!
EDIT
This is the JSON body content of the document with custom generated _id using crypto:
{
"_id": "1262d480eea83567181b3206",
"header": "hello",
"subheader": "hello"
}
Whereas, this is the body content of the upserted document.
{
"_id": {
"$oid": "1262d480eea83567181b3206"
},
"header": "helloasad",
"subheader": "helloasda"
}
As observed, after upserting, it takes the same _id value of the original document but on another document level.
A possible solution/explanation based on #Ashwanth Madhav information:
In your code 'id' was being sent to the update as a String type, but the id in MongoDB is an ObjectId type:
Code will be something like that:
var id;
if(req.params._id == 'undefined'){
// 'id' NEED TO BE AN ObjectId...
// 'id' WAS BEING SENT AS A 'String'
id = ObjectId(crypto.randomBytes(12).toString('hex'));
}
else {
id = ObjectId(req.params._id)
}
Node.JS, MONGODB, not using Mongoose.
I have a document I'm saving. When I use UPSERT, it structures the data as so :
{ "_id" : ObjectId("573a123f55e195b227e7a78d"),
"document" :
[ {"name" : "SomeName" } ]
}
When I use INSERT it inserts it all on the root level :
{ "_id" : ObjectId("573a123f55e195b227e7a78d"), "name" : "SomeName" }
This is obviously going to lead to lots of inconsistencies. I have tried various things. I've tried various methods such as findOneAndReplace, Update with Upsert, I've tried Replace, $setOnInsert. It all does the same thing when Upsert is involved it seems.
I have tried to use document[0] to access the first block of the array, but that throws an error... 'Unexpected Token ['
I have tried various methods and dug for hours through the various documentation, and have searched high and low for someone else having this problem, but it doesn't seem to be well documented issue for anyone else.
Anyone have any recommendations to make sure that all the fields are on the ROOT level, not nested under the variable name? Relevant code below.
findReplace: function(db, document, collection) {
var dbCollection = db.collection(collection);
var filter = document.name;
return new Promise(function(resolve, reject) {
dbCollection.updateOne({
"name" : filter
}, {
document
}, {
upsert: true
}, function(err, result) {
if (err) {
console.log('error', err)
reject(err);
}
console.log("Found Document and Upserted replacement Document");
resolve([{'status' : 'success'}]);
});
});
}
When you do this:
{
document
}
You are creating an object containing a document property and the variable's value as its value:
{
document: [ {"name" : "SomeName" } ]
}
This is new functionality from ES6. If you want to access the first item of the document variable, don't create a new object:
return new Promise(function(resolve, reject) {
dbCollection.updateOne({
"name" : filter
}, document[0], { // <========
upsert: true
}, function(err, result) {
if (err) {
console.log('error', err)
reject(err);
}
console.log("Found Document and Upserted replacement Document");
resolve([{'status' : 'success'}]);
});
});
A piece of my Mongo document structure is:
{ "_id": ObjectId("xxxxxx..."),
"Country" : "UNITED KINGDOM",
"Line" : "something",
"Records" : [
{"rdata" : "foo", "rtype" : "X", "name" : "John"},
{"rdata" : "bar", "rtype" : "Y", "name" : "Bill"}
], ...
I'm using Mongoose to access the data via the following model:
var Record = new Schema({
rdata: String,
rtype: String,
name: String
}, {_id: false});
var ThingSchema = new Schema({
Country: String,
Line : String,
Records : [Record],
Let's say I want to update the "Line" property of one of my documents, from being "Line" : "something" to "Line" : "way more interesting" by sending a PUT request to the appropriate API URL. I can see that the data being sent is all right. This is what the API does:
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Thing.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
var updated = _.merge(thing, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, updated);
});
});
};
The API comes back with 200/OK - but I see the following updated data:
{ "_id": ObjectId("xxxxxx..."),
"Country" : "UNITED KINGDOM",
"Line" : "way more interesting", <-- updated correctly
"Records" : [
{"rdata" : "foo", "rtype" : "X", "name" : "John"},
{"rdata" : "foo", "rtype" : "X", "name" : "John"}
], ...
Notice, how the Records array got messed up by overwriting my second record by duplicating the first one. (If I switch on the automatic addition of '_id' to the subdocument by Mongoose, then even the "_id" fields will be the same on the two records within the array).
It may be relevant, that originally the Records were not added via Mongoose - but by importing a JSON document. Any suggestion as to how to start finding out why this is happening would be fantastic.
Try changing _.merge to _.extend, then call save directly on the thing document returned by the findById() method instead of the merged object updated:
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Thing.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
_.extend(thing, req.body);
thing.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(200, thing);
});
});
}
Another option is using the set method on the entity i.e. thing.set(req.body) before calling the save method on the thing object.
This answer by ShitalShah highlights the differences between merge and extend that is causing duplicates in your resulting object with merge but essentially:
Here's how extend/assign works: For each property in source, copy its
value as-is to destination. if property values themselves are objects,
there is no recursive traversal of their properties. Entire object
would be taken from source and set in to destination.
Here's how merge works: For each property in source, check if that
property is object itself. If it is then go down recursively and try
to map child object properties from source to destination. So
essentially we merge object hierarchy from source to destination.
While for extend/assign, it's simple one level copy of properties from
source to destination.
JSBin to illustrate the differences
I am using MongoDB , MongooseJS and Nodejs.
I have a Collection ( called Member ) with the following Fields -
Country_id , Member_id , Name, Score
I want to write a query which returns the Member with the max Score where Country id = 10
I couldnt find suitable documentation for this in MongooseJS.
I found this at StackOVerflow ( this is MongoDB code )
Model.findOne({ field1 : 1 }).sort(last_mod, 1).run( function(err, doc) {
var max = doc.last_mod;
});
But how do I translate the same to MongooseJS ?
Member
.findOne({ country_id: 10 })
.sort('-score') // give me the max
.exec(function (err, member) {
// your callback code
});
Check the mongoose docs for querying, they are pretty good.
If you dont't want to write the same code again you could also add a static method to your Member model like this:
memberSchema.statics.findMax = function (callback) {
this.findOne({ country_id: 10 }) // 'this' now refers to the Member class
.sort('-score')
.exec(callback);
}
And call it later via Member.findMax(callback)
You do not need Mongoose documentation to do this.
Plain MongoDb will do the job.
Assume you have your Member collection:
{ "_id" : ObjectId("527619d6e964aa5d2bdca6e2"), "country_id" : 10, "name" : "tes2t", "score" : 15 }
{ "_id" : ObjectId("527619cfe964aa5d2bdca6e1"), "country_id" : 10, "name" : "test", "score" : 5 }
{ "_id" : ObjectId("527619e1e964aa5d2bdca6e3"), "country_id" : 10, "name" : "tes5t", "score" : -6 }
{ "_id" : ObjectId("527619e1e964aa5d2bdcd6f3"), "country_id" : 8, "name" : "tes5t", "score" : 24 }
The following query will return you a cursor to the document, you are looking for:
db.Member.find({country_id : 10}).sort({score : -1}).limit(1)
It might be faster to use find() than findOne().
With find().limit(1) an array of the one document is returned. To get the document object, you have to do get the first array element, maxResult[0].
Making Salvador's answer more complete ...
var findQuery = db.Member.find({country_id : 10}).sort({score : -1}).limit(1);
findQuery.exec(function(err, maxResult){
if (err) {return err;}
// do stuff with maxResult[0]
});
This is quick and easy using the Mongoose Query Helpers.
The general form for this could be:
<Your_Model>.find()
.sort("-field_to_sort_by")
.limit(1)
.exec( (error,data) => someFunc(error,data) {...} );
tldr:
This will give you an array of a single item with the highest value in 'field_to_sort_by'. Don't forget to access it as data[0], like I did for an hour.
Long-winded: Step-by-step on what that string of functions is doing...
Your_Model.find() starts the query, no args needed.
.sort("-field_to_sort_by") sorts the everything in descending order. That minus-sign in front of the field name specifies to sort in descending order, you can discard it to sort in ascending order and thus get the document with the minimum value.
.limit(1) tells the database to only return the first document, because we only want the top-ranked document.
.exec( (error,data) => someFunc(error,data) {...} ) finally passes any error and an array containing your document to into your function. You'll find your document in data[0]
You can also use the $max operator:
// find the max age of all users
Users.aggregate(
{ $group: { _id: null, maxAge: { $max: '$age' }}}
, { $project: { _id: 0, maxAge: 1 }}
, function (err, res) {
if (err) return handleError(err);
console.log(res); // [ { maxAge: 98 } ]
});