skip to Main Content

I am executing an update on a complex document in DocumentDB. I want to only update changed fields, without setting the object (and/or any nested objects) explicitly, to avoid overwriting existing data.

I am using TypeScript, and have written a function that rewrites the update to use dot notation for every field (to avoid overwriting objects).

The issue is that the document’s fields may be null, so updating foo.bar fails if foo is null.

I have tried adding $ifNull to the update query:

db.collection.updateOne(
  { _id: 1 },
  {
    $set: {
      "foo": { $ifNull: ["$foo", {}] },
      "foo.bar": "value1"
    }
  }
)

That doesn’t work – if foo is not null, then setting foo to $ifnull: ['$foo', {}] and then foo.bar in the same update fails, since it results in two updates to the foo.bar field in the same operation:

Cannot update 'foo.bar' and 'foo.bar' at the same time

The proper way to solve this would be an update using an aggregation pipeline using $mergeObjects, but DocumentDB does not support updates with an aggregation pipeline.

What are my options here? I want the solution to be as generic as possible and not require any knowledge about the schema or the data, so I could use it for all of my updates.

2

Answers


  1. I would go with 2 queries, potentially with bulk write:

    db.collection.updateOne(
      { _id: 1, foo:{$not:{ $type: 10 }}},
      {
        $set: {
          "foo.bar": "value1"
        }
      }
    )
    db.collection.updateOne(
      { _id: 1, foo:{ $type: 10 } },
      {
        $set: {
          "foo": { "bar": "value1"}
        }
      }
    )
    
    Login or Signup to reply.
  2. I would say the most canonical way would be using an aggregation to wrangle your data and iterate through the aggregation output to update back to the collection one by one. In this way you can leverage most of the supported aggregation API in AWS documentDB. Potentially if you are doing a lot of updates, you will want to wrap them in bulk operations too

    db.collection.aggregate([
      {
        "$set": {
          "foo": {
            "$cond": {
              "if": {
                "$eq": [
                  null,
                  {
                    "$ifNull": [
                      "$foo",
                      null
                    ]
                  }
                ]
              },
              "then": {},
              "else": {
                "$mergeObjects": [
                  "$foo",
                  {
                    "bar": "value1"
                  }
                ]
              }
            }
          }
        }
      }
    ]).forEach(function(result){
      db.collection.updateOne(
        {"_id": result._id}, 
        {"$set": {"foo": result.foo}}
      );
    })
    

    Mongo Playground to see the wrangle result

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search