[SERVER-14094] Conditional Updates: Conditionally update secondary fields if primary fields updated Created: 30/May/14  Updated: 06/Dec/22  Resolved: 29/Jun/19

Status: Closed
Project: Core Server
Component/s: Write Ops
Affects Version/s: None
Fix Version/s: None

Type: New Feature Priority: Major - P3
Reporter: Kris Brown Assignee: Backlog - Query Team (Inactive)
Resolution: Won't Do Votes: 3
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Related
related to SERVER-13578 add $setOnUpdate to update operation Closed
related to SERVER-6566 Support conditional updates: $updates Closed
Assigned Teams:
Query
Participants:

 Description   

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:



 Comments   
Comment by Asya Kamsky [ 29/Jun/19 ]

SERVER-40381 implemented support for aggregation expressions to specify update for 4.2. 

You can see some examples here.

This can be done by using various conditional aggregation expressions. I believe this can handle all of the examples/use cases that were mentioned in the comments.

Comment by David Storch [ 07/Mar/16 ]

asya ian.whalen this looks slightly different than SERVER-13578. This ticket's request is to apply an update if a document matched and a previous update modifier was not a no-op. The problematic aspect of this request is that the order in which update modifiers apply is current undefined---which is why it is generally an error to apply two update modifiers to the same path. SERVER-13578 describes simpler semantics where, for an upsert, the modifier only applies if the operation results in an update and not if the operation results in an insert.

Generated at Thu Feb 08 03:33:51 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.