how to check if the document updated was really modified? - node.js

I have an "items" collection and this is an example document in the collection:
{ "id": 0, "price": 23.55 }
I'm updating writing a query that updates an item's price,
but want to check if the price field has really modified.
For example, following query is legal but
does not actually modify the document.
db.collection('items').updateOne(
{ id: 0 },
{ $set: { price: 23.55 } }
);
Is there any methods I can achieve this?

updateOne will return nModified you can get to know whether the document was modified or not.
{ "n": 1, "nModified": 1 }
https://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html#updateOne
https://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html#~updateWriteOpCallback
https://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html#findOneAndUpdate
Use findOneAndUpdate with returnOriginal set to false, you'll get the updated document in the callback.
const options = { returnOriginal: false; } // to return the updated document
findOneAndUpdate(filter, update, options, callback)
https://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html#~findAndModifyCallback
Document returned from the findAndModify command. If no documents were found, value will be null by default (returnOriginal: true), even if a document was upserted; if returnOriginal was false, the upserted document will be returned in that case.

Related

Mongoose: create multiple documents if filter finds none

I'm trying to create multiple documents based on a filter: if document not found => create it.
After searching a bit I found that the correct way to do so is by using updateMany and setting upsert: true (docs).
This made somewhat sense from the documentation's example but as I understand it the filter modifier would be used for the newly-created document. As in the example:
try {
db.inspectors.updateMany(
{ "Sector" : { $gt : 4 }, "inspector" : "R. Coltrane" },
{ $set: { "Patrolling" : false } },
{ upsert: true }
);
} catch (e) {
print(e);
}
"Inspector" : "R. Coltrane" would be inserted to the newly-created document.
But what if my setOnInsert modifier contains the same field as the one in the filter?
What my code looks like:
//first find the already-created tags
await tagModel.find({"tagName": tags}).select('tagName -_id').exec()
.then(async (result: Tag[])=>{
//create the new tags
const newTags = tags.map((tag: any)=>new tagModel({tagName: tag}));
//now insert only the new tags, filtering out the already-created tags ("result")
await tagModel.updateMany(
{"tagName": result},
{$setOnInsert: newTags} ,
{upsert: true},
(err:any, res:any)=>{
...
At first, result is an empty Array ([]). What is created in my MongoDB database is a new Tag document, but its tagName is the result object. Meaning, it looks like:
{
"_id": {
"$oid": "61659c92c6267fe11963b236"
},
"tagName": {
"$in": []
}
}
So essentially my question is, what am I suppose to do in this case where my update modifier should replace my filter query? Perhaps it's just something bad in my code that makes the updateMany function to malfunction? Or should I replace it with a different function?

Check if a specific field was updated with a single findOneAndUpdate query?

My items collection has a following schema:
{
"item": "box",
"quantity": 12,
"lastReportedAt": "2021-10-22T00:12:34"
}
Every API call reports the latest quantity for a specific item, so I need to update the doc accordingly.
const newQuantity = 13;
const lastReportedAt = new Date();
db.collection("items").findOneAndUpdate(
{ "item": "box" },
{ $set: { "quantity": newQuantity, "lastReportedAt": newTime } },
{ upsert: true }
);
Here I would like to know if there was a change in "quantity" field, so that I can be notified.
While some of mongodb's update commands return the number of modified documents, but it also accounts for update in lastReportedAt fields.
Is there any way this can be done without using a transaction?
Use returnNewDocument in findOneAndUpdate, and compare with the old document in your application.

Problems with "updateMany" and "$pull" on array properties

I have the following object in my mongodb:
{
"item": {
"id": "/Ed/6wigZ9LTLs2mPDWDzOFD/he0vbUEvQBl2Bga/T8=",
"status": {
"likes": [
"HU0HKFoL2YQuQ2WrYhj0rYoFbRkwJ0EJEf4ML7vAp2Q="
]
}
}
}
Now I want to update my collection, and pull the value "/Ed/6wigZ9LTLs2mPDWDzOFD/he0vbUEvQBl2Bga/T8=" from all documents in the collection who have this value in their item.status.liles array.
When I use the tool Robo 3T, I can put into the command line:
db.getCollection('mycollection').update({"item.status.likes": "/Ed/6wigZ9LTLs2mPDWDzOFD/he0vbUEvQBl2Bga/T8=" }, { "$pull":{"item.status.likes": "/Ed/6wigZ9LTLs2mPDWDzOFD/he0vbUEvQBl2Bga/T8="}})
And it says Updated 0 record(s) in 2ms when I execute the command (which is correct, since there is no document matching).
Now when I do the same in Node.JS code, like this:
let filter = {item.status.likes: '/Ed/6wigZ9LTLs2mPDWDzOFD/he0vbUEvQBl2Bga/T8='};
let obj = {"$pull":{"item.status.likes":"/Ed/6wigZ9LTLs2mPDWDzOFD/he0vbUEvQBl2Bga/T8="}}
collection.updateMany(filter, obj, { upsert: true }, (err, info) => {...});
I get MongoError: Cannot apply $pull to a non-array value instead.
If I change it to:
collection.updateMany({}, obj, { upsert: true }, (err, info) => {...});
I get no error. This leads me to believe that there is a difference between "no filter" and "empty result set", and $pull does not work on the latter.
Why is this? I also tried with different "$pull"-syntax (e.g. {"item":{"status":{"likes"... vs item.status.likes) but this didnt change anything.
The error happens when there is no single document matching the filter because of upsert: true.
It tells mongo to insert a new document with fields from the filter, which makes following document:
{
"item": {
"status": {
"likes": "/Ed/6wigZ9LTLs2mPDWDzOFD/he0vbUEvQBl2Bga/T8="
}
}
}
Then it tries to apply the update to this document calling $pull on the string which results with the error.
The Robo3T version works because the update is upsert: false by default.

Insert or Update into Mongo using mongoose and modify inner elements while updating

Assume there's a collection with documents where field_1 is unique
[
{
field_1: 'abc',
field_2: 0,
field_3: []
}
]
I want to add another document, but then field_1 is the same 'abc'. In which case I want to increment field_2, and append element into field_3 while updating. And if field_1 is different, create another document.
What is the best way to approach such queries? My first thought was to search, and then insert if no documents are found, or is there a better way? The problem with this approach is, if I'm inserting multiple documents at once, I can't use 'search and, if no doc found, insert' approach effectively.
Mongoose now supports this natively with findOneAndUpdate (calls MongoDB findAndModify).
The upsert = true option creates the object if it doesn't exist. defaults to false.
MyModel.findOneAndUpdate(
{foo: 'bar'}, // find a document with that filter
modelDoc, // document to insert when nothing was found
{upsert: true, new: true, runValidators: true}, // options
function (err, doc) { // callback
if (err) {
// handle error
} else {
// handle document
}
}
);
If the uniqueness of field_1 is enforced by unique index, you can use a kind of optimistic locking.
First you try to update:
db.collection.update(
{
field_1: 'abc'
},
{
$inc: {field_2: 1},
$push: {field_3: 'abc'},
}
);
and check result of the operation - if 1 document updated, no more actions required. Otherwise, it's the first document with field_1 == 'abc', so you try to insert it:
db.collection.insert(
{
field_1: 'abc',
field_2: 0,
field_3: []
}
);
and catch the error. If there is no error, no more actions required. Otherwise there was a concurrent insert, so you need to repeat the update query once more.

In Mongo, Increment and return value -- possible in 1 call?

In Mongo, is it possible to increase and get the result of the increment?
collection.update({id: doc_id}, {$inc: {view_count: 1}});
I tried to output the result of that statement (in node) and I got the following:
{ _id: 1,
_state: undefined,
_result: undefined,
_subscribers: [] }
You can use findAndModify. Add the new:true option.
According to the docs:
The findAndModify command modifies and returns a single document. By default, the returned document does not include the modifications made on the update. To return the document with the modifications made on the update, use the new option.
You could do the following:
db.collection.findAndModify(
query: {_id: doc_id},
update: { $inc: { view_count :1 } },
new: true,
)
If you canĀ“t find the findAndModify method to use on your collection,you can use the findOneAndUpdate method.
Here is how to use:
The following code finds the first document where name : R. Stiles and increments the score by 5:
const result = await db.grades.findOneAndUpdate(
{ "name" : "R. Stiles" }, //also you can search for id
{ $inc: { "points" : 5 } }
)
The code returns the original document before the update inside the "value" propety:
{ _id: 6319, name: "R. Stiles", "points" : 0,... } // result.value returns document before update, but in the db it changued
If you want get the document uploaded, you has to set "returnNewDocument" to true, so the operation would return the updated document instead.
I hope it works for you.
source: https://www.mongodb.com/docs/manual/reference/method/db.collection.findOneAndUpdate/

Resources