In Chapter 2, Command-line Operations and Indexes, we had some recipes that explained various CRUD operations that we perform in MongoDB. There was one concept that we didn't cover that is, atomically finding and modifying documents. Modify consists of both update
and delete
operations. In this recipe, we will see find
and modify
operations in some detail and, in the next recipe, Implementing atomic counters in MongoDB, we will put them to use in implementing counters.
Refer to the Single node installation of MongoDB recipe in Chapter 1, Installing and Starting the MongoDB Server, and start a single instance of MongoDB. That is the only prerequisite for this recipe. Start a Mongo shell and connect to the started server.
We will test a document in the atomicOperationsTest
collection as follows:
> db.atomicOperationsTest.drop() > db.atomicOperationsTest.insert({i:1})
> db.atomicOperationsTest.findAndModify({ query: {i: 1}, update: {$set : {text : 'Test String'}}, new: false } )
> db.atomicOperationsTest.findAndModify({ query: {i: 1}, update: {$set : {text : 'Updated String'}}, fields: {i: 1, text :1, _id:0}, new: true } )
> db.atomicOperationsTest.findAndModify({ query: {i: 2}, update: {$set : {text : 'Test String'}}, fields: {i: 1, text :1, _id:0}, upsert: true, new: true } )
> db.atomicOperationsTest.find().pretty()
delete
operation as follows:> db.atomicOperationsTest.findAndModify({ query: {i: 2}, remove: true, fields: {i: 1, text :1, _id:0}, new: false } )
If we perform the find
and update
operations independently by first finding the document and then updating it in MongoDB, the results might not be as expected as there might be an interleaving update between the find
and the update
operations that will change the document state. In some of the specific use cases, such as implementing atomic counters, this is not acceptable and thus, we need a way to atomically find, update, and return a document. The returned value is either the one before the update is applied or after the update is applied, and this is decided by the invoking client.
Now that we have executed the steps in the preceding section, let us see what we actually did and what all these fields in the JSON document, which are passed as parameters to the findAndModify
operation, mean. Starting with step 2, we gave a document as a parameter to the findAndModify
function that contains the following fields. The fields query
, update
, and new
are used to specify the query that will be used to find the document, the update that will be applied to it, and a Boolean value that will be used to specify whether the document returned by the operation is the one after the update is applied or before it was applied. In this case, the value of the new flag is false
. The resulting document returned is the one before the update is applied.
In step 3, we actually added a new field to the document, passed as a parameter called fields
, that is used to select a limited set of fields from the resulting document returned. Also, the value of the new
field is true
, which indicates that we want the updated document; that is, the one after the update
operation is executed and not the one before the update.
In step 4, the parameter contained a new field called upsert
, which upserted (update + insert) the document. That is, if the document with the given query is found, it is updated; otherwise, a new one is created and updated. If the document didn't exist and an upsert happens, having the value of the new
parameter as false
will return null
. This is because there was nothing present before the update
operation was executed.
Finally, in step 6, the parameter didn't have the update
field but had the remove
field with the value true
, indicating that the document is to be removed. Also, the value of the new
field was false
, which means that we expect the document that got deleted.