Uploaded image for project: 'Core Server'
  1. Core Server
  2. SERVER-14094

Conditional Updates: Conditionally update secondary fields if primary fields updated

    • Type: Icon: New Feature New Feature
    • Resolution: Won't Do
    • Priority: Icon: Major - P3 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:

            Assignee:
            backlog-server-query Backlog - Query Team (Inactive)
            Reporter:
            krisbrown Kris Brown
            Votes:
            3 Vote for this issue
            Watchers:
            10 Start watching this issue

              Created:
              Updated:
              Resolved: