...
Issue Status as of Sep 08, 2014 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.
xgen-internal-githook commented on Wed, 3 Sep 2014 14:08:43 +0000: Author: {u'username': u'scotthernandez', u'name': u'Scott Hernandez', u'email': u'scotthernandez@gmail.com'} Message: SERVER-15029: missing rename source field should be a no-op (cherry picked from commit 8f8e59a78107a079c5367abdc31580d20428d9cc) Branch: master https://github.com/mongodb/mongo/commit/0353f6b389b4cdae93bc34f8c5933431fb46f250 xgen-internal-githook commented on Wed, 3 Sep 2014 14:06:38 +0000: Author: {u'username': u'scotthernandez', u'name': u'Scott Hernandez', u'email': u'scotthernandez@gmail.com'} Message: SERVER-15029: missing rename source field should be a no-op Branch: v2.6 https://github.com/mongodb/mongo/commit/8f8e59a78107a079c5367abdc31580d20428d9cc asya commented on Mon, 25 Aug 2014 23:52:57 +0000: In 2.4.x $rename of non-existent field was a no-op so this did not affect 2.4.x or before. In addition, the incorrect update doesn't apply the same way on the secondary than the primary, resulting in a larger document on the secondary.
> db.a.drop() > db.a.insert({_id:1,a:{b:1}}) > db.a.update({_id:1},{$rename:{"a.NONE":"a.NEW"}}) > db.a.find() {_id:1, a:{NEW:{b:1}}}