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.