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)

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Closed
    • Priority: Major - P3
    • Resolution: Fixed
    • Affects Version/s: 2.6.0
    • Fix Version/s: 2.6.4, 2.7.4
    • Component/s: Write Ops
    • Labels:
      None
    • Operating System:
      ALL
    • Backport Completed:
    • Steps To Reproduce:
      Hide

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

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

      Description

      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.

        Attachments

          Activity

            People

            • Votes:
              2 Vote for this issue
              Watchers:
              9 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: