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

Update fails when query contains part of a DBRef and results in an insert (upsert:true)

    • Type: Icon: Bug Bug
    • Resolution: Done
    • Priority: Icon: Major - P3 Major - P3
    • 2.6.4, 2.7.4
    • Affects Version/s: 2.6.0
    • Component/s: Write Ops
    • None
    • ALL
    • Hide

      Run the second query, seems and "*.$id" upsert fails.

      Show
      Run the second query, seems and "*.$id" upsert fails.

      Issue Status as of Jul 22, 2014

      ISSUE SUMMARY

      When the update query contains a partial DBRef ($id / $ref / $db) with upsert:true, and a document is not found which results in an insert then the update will fail.

      Example:

      db.coll1.drop()
      db.coll1.update({a.$id:1}, {$set: {a: DBRef("coll2", 1)}, $inc: {c: 1}}}, {upsert:true})
      ...
        "code" : 55,
        "errmsg" : "Found $id field without a $ref before it, which is invalid."
      ...
      

      This failure is due to the query being validated for storage, which will fail due to the incomplete DBRef even though the final updated document will contain the full DBRef.

      USER IMPACT
      The upsert fails but no other operations are affected.

      WORKAROUNDS
      You can include the entire DBRef in the query part instead of incomplete DBRef fields.

      AFFECTED VERSIONS
      Production release versions 2.6.0 to 2.6.3 are affected by this issue.

      FIX VERSION
      The fix is included in the 2.6.4 production release.

      RESOLUTION DETAILS
      This code change moves the validation of the DBRef until after the full update has been performed.

      Original description

      When the update query contains a partial DBRef ($id/$ref/$db) with upsert:true, and a document is not found which results in an insert then the update will fail.

      > db.coll1.drop()
      > db.coll1.update({a.$id:1}, {$set: {a: DBRef("coll2", 1)}, $inc: {c: 1}}}, {upsert:true})
      ...
        "code" : 55,
        "errmsg" : "Found $id field without a $ref before it, which is invalid."
      ...
      

      This failure is due to the query being validated for storage, which will fail due to the incomplete DBRef even though the final updated document will contain the full DBRef. This code changes simply moves the validation of the DBRef until after the full update has been performed.

      Old Description
      We have an index on a DBRef field for it's $id value "config.$id" instead of on "config" for performance reasons. Find works fine, however doing an update since 2.6 and searching for "config.$id" errors out with:

      {
        "code" : 55,
        "errmsg" : "Found $id field without a $ref before it, which is invalid."
      }
      

      The error was happening in the PHP driver, yet it is testable via the command line as well.

      So this query works:

      db.alerts.update(
        {
          "config": {
            "$ref": "alert",
            "$id": ObjectId("5xxxxxxxxxxxxxxxxxxxxxx1"),
            "$db": "Configs"
          },
          "date":ISODate("2014-05-21T00:00:00Z"),
          "server": "someserver.com"
        },
        {
          "$set": {
            "config": {
              "$ref": "alert",
              "$id": ObjectId("5xxxxxxxxxxxxxxxxxxxxxx1"),
              "$db": "Config"
            },
            "date": ISODate("2014-05-21T04:00:00Z"),
            "server": "someserver.com",
            "user": {
              "$ref": "User",
              "$id": ObjectId("5xxxxxxxxxxxxxxxxxxxxxx2"),
              "$db": "Users"
            },
            "triggered": ISODate("2014-05-21T16:57:57Z")
          },
          "$inc": {
            "count": 1
          }
        },
        {
          "upsert": true
        }
      );
      

      However this one that uses the index doesn't:

      db.alerts.update(
        {
          "config.$id": ObjectId("5xxxxxxxxxxxxxxxxxxxxxx1"),
          "date":ISODate("2014-05-21T00:00:00Z"),
          "server": "someserver.com"
        },
        {
          "$set": {
            "config": {
              "$ref": "alert",
              "$id": ObjectId("5xxxxxxxxxxxxxxxxxxxxxx1"),
              "$db": "Config"
            },
            "date": ISODate("2014-05-21T04:00:00Z"),
            "server": "someserver.com",
            "user": {
              "$ref": "User",
              "$id": ObjectId("5xxxxxxxxxxxxxxxxxxxxxx2"),
              "$db": "Users"
            },
            "triggered": ISODate("2014-05-21T16:57:57Z")
          },
          "$inc": {
            "count": 1
          }
        },
        {
          "upsert": true
        }
      );
      

      A work around for now is I'll add a find, and then an update/insert if I find that document. Which would probably cleaner either way.

            Assignee:
            scotthernandez Scott Hernandez (Inactive)
            Reporter:
            mullanaphy John Mullanaphy
            Votes:
            2 Vote for this issue
            Watchers:
            8 Start watching this issue

              Created:
              Updated:
              Resolved: