-
Type: New Feature
-
Resolution: Won't Do
-
Priority: Major - P3
-
None
-
Affects Version/s: None
-
Component/s: Write Ops
-
None
-
Query
Conditionally modify some fields only if “significant” fields are changed during an update.
For example: update the description of a product based on its external identifier (the SKU), and if this would change the document, also modify the updatedBy, updatedAt and version field.
A sample API call for this might be:
db.products.update( { sku: 'abc123' }, { $set: { description: "a description" }, $setOnInsert: { createdBy: "kris", createdAt: ISODate("2014-05-30T08:00:00.000Z"), updatedBy: "kris", updatedAt: ISODate("2014-05-30T08:00:00.000Z”), version: 1 }, $setIfModified: { updatedBy: "kris", updatedAt: ISODate("2014-05-30T08:00:00.000Z") }, $incIfModified: { version: 1 } }, { upsert: true } )
More formally:
For the matching document (or documents if multi:true) if the document exists and applying $set would change the document then apply $set, $setIfModified and $incIfModified.
Note: the timestamps are not the timestamp at which the document will be persisted, but the time at which the request to make the change happened. Therefore $currentDate or ObjectId.getTimestamp() are not suitable in this case.
Workaround: Single Operation
The closest I can get to my requirements using a single call is:
db.products.update( { sku: 'abc123' }, { $set: { description: "a description", updatedBy: "kris", updatedAt: ISODate("2014-05-30T08:00:00.000Z") }, $inc: { version: 1 }, $setOnInsert: { createdBy: "kris", createdAt: ISODate("2014-05-30T08:00:00.000Z") }, }, { upsert: true } )
This would allow an application to update the description of a product by an external identifier (the SKU). However, it would result in the updatedBy, updatedAt and version to be modified even if the document already had the same description, which is not the desirable behaviour in this case.
Workaround: Two operations with optimistic locking
Find or create a document matching the query conditions:
db.products.findAndModify({ query: { sku: 'abc123' }, update: { $setOnInsert: { createdBy: "kris", createdAt: ISODate("2014-05-30T08:00:00.000Z"), version: 1 } }, upsert: true, new: true })
Check the returned document to see if applying the primary changes would modify it, and if so, do an update (including the version in the query):
db.products.update( { sku: 'abc123', version: 1 }, { $set: { description: "a description", updatedBy: "kris", updatedAt: ISODate("2014-05-30T08:00:00.000Z") }, $inc: { version: 1 }, } )
Downside to this is that the version ends up being incremented twice on initial creation. Needs some logic too to check the write results and handle error cases (i.e. version mismatch due to secondary party modifying document between find and update).
Related information:
- http://docs.mongodb.org/manual/reference/operator/update-field/ - current operators on update
- https://jira.mongodb.org/browse/SERVER-340 - add $setOnInsert
- https://jira.mongodb.org/browse/SERVER-6566 - conditional updates with extra flexibility
- https://jira.mongodb.org/browse/SERVER-13578 - discusses $setOnUpdate and $incOnUpdate with slightly different semantics, but I think these would be better as $*IfModified
- related to
-
SERVER-13578 add $setOnUpdate to update operation
- Closed
-
SERVER-6566 Support conditional updates: $updates
- Closed