ISSUE SUMMARY
A $rename modifier may incorrectly select the wrong source path element resulting in the move of the wrong element. This can happen when the full path of the source field cannot be found, as it doesn't exist, but a prefix does exists. In this case the prefix element will be used instead of the element at the full path location. For example, if the source field "a.b.c" doesn't exist but the field "a" does, $rename incorrectly uses "a" as the source field:
> db.foo.drop() > db.foo.insert({_id : 1, a : {}}) WriteResult({ "nInserted" : 1 }) > db.foo.update({}, {$rename : {"a.b.c" : "r"}}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) > db.foo.find() { "_id" : 1, "r" : { } }
The expected and correct behavior is for this $rename to be "no-op" because the source field doesn't exist, but this bug results in document mutation anyway.
USER IMPACT
The document can be changed in unexpected ways, due to this $rename bug. These changes can only be reverted if a backup of original data is available. Repeated modifications with $rename to new fields have been seen to produce constantly growing documents which can result in errors to clients and in replication.
Also, if $rename changes the wrong field, subsequent queries on the source or destination fields may not return expected results:
> db.foo.drop() > db.foo.insert({_id : 1, a : {x : 1}}) WriteResult({ "nInserted" : 1 }) > db.foo.insert({_id : 2, a : {x : 2}}) WriteResult({ "nInserted" : 1 }) > db.foo.insert({_id : 3, r : {x : 3}}) WriteResult({ "nInserted" : 1 }) > db.foo.update({_id : 1}, {$rename : {"a.b" : "r"}}) WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 }) > db.foo.find({a: {x : 1}}) > db.foo.find({r: {$exists : true}}) { "_id" : 1, "r" : { "x" : 1 } } { "_id" : 3, "r" : { "x" : 3 } }
WORKAROUNDS
Including the source path as part of the query in addition to the $rename will result in making sure that the document isn't selected, since it doesn't have the full path. For the example above:
> db.foo.update({_id: 1, "a.b" : {$exists : true}}, {$rename : {"a.b" : "r"}}) WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
AFFECTED VERSIONS
MongoDB 2.6 production releases are affected by this issue.
FIX VERSION
The fix is included in the 2.6.5 production release.
RESOLUTION DETAILS
$rename now operates correctly and a missing source field is a no-op, as expected.
Original description
When the full path cannot be found, but part can be, rename incorrectly uses that as the source for the rename operation.
For example, "a.b.c" essentially turns into "a", for this update due to this bug:
db.a.save({a:{}}) db.a.update({}, {$rename:{"a.b.c", "r"}})
The correct behavior is for this rename to be "no-op" because the source field doesn't exist.
- is related to
-
SERVER-20171 Invalid "cannot use the part to traverse the element"
- Closed
-
SERVER-25164 $rename doesn't rename fields with dots (.)
- Closed