// @tags: [ // requires_non_retryable_commands, // sbe_incompatible, // ] t = db.jstests_rename4; t.drop(); function bad(f) { var docsBeforeUpdate = t.find().toArray(); var res = eval(f); // Ensure error if (!res.hasWriteError()) { print("Error:" + res.toString()); print("Existing docs (before)"); printjson(docsBeforeUpdate); print("Existing docs (after)"); printjson(t.find().toArray()); assert(false, "Expected error but didn't get one for: " + f); } } bad("t.update( {}, {$rename:{'a':'a'}} )"); bad("t.update( {}, {$rename:{'':'a'}} )"); bad("t.update( {}, {$rename:{'a':''}} )"); bad("t.update( {}, {$rename:{'.a':'b'}} )"); bad("t.update( {}, {$rename:{'a':'.b'}} )"); bad("t.update( {}, {$rename:{'a.':'b'}} )"); bad("t.update( {}, {$rename:{'a':'b.'}} )"); bad("t.update( {}, {$rename:{'a.b':'a'}} )"); bad("t.update( {}, {$rename:{'a.$':'b'}} )"); bad("t.update( {}, {$rename:{'a':'b.$'}} )"); // Only bad if input doc has field resulting in conflict t.save({_id: 1, a: 2}); bad("t.update( {}, {$rename:{'_id':'a'}} )"); bad("t.update( {}, {$set:{b:1},$rename:{'a':'b'}} )"); bad("t.update( {}, {$rename:{'a':'b'},$set:{b:1}} )"); bad("t.update( {}, {$rename:{'a':'b'},$set:{a:1}} )"); bad("t.update( {}, {$set:{'b.c':1},$rename:{'a':'b'}} )"); bad("t.update( {}, {$set:{b:1},$rename:{'a':'b.c'}} )"); bad("t.update( {}, {$rename:{'a':'b'},$set:{'b.c':1}} )"); bad("t.update( {}, {$rename:{'a':'b.c'},$set:{b:1}} )"); t.remove({}); t.save({a: [1], b: {c: [2]}, d: [{e: 3}], f: 4}); bad("t.update( {}, {$rename:{'a.0':'f'}} )"); bad("t.update( {}, {$rename:{'a.0':'g'}} )"); bad("t.update( {}, {$rename:{'f':'a.0'}} )"); bad("t.update( {}, {$rename:{'b.c.0':'f'}} )"); bad("t.update( {}, {$rename:{'f':'b.c.0'}} )"); bad("t.update( {}, {$rename:{'d.e':'d.f'}} )"); bad("t.update( {}, {$rename:{'d.e':'f'}} )"); bad("t.update( {}, {$rename:{'d.f':'d.e'}} )"); bad("t.update( {}, {$rename:{'f':'d.e'}} )"); bad("t.update( {}, {$rename:{'d.0.e':'d.f'}} )"); bad("t.update( {}, {$rename:{'d.0.e':'f'}} )"); bad("t.update( {}, {$rename:{'d.f':'d.0.e'}} )"); bad("t.update( {}, {$rename:{'f':'d.0.e'}} )"); bad("t.update( {}, {$rename:{'f.g':'a'}} )"); bad("t.update( {}, {$rename:{'a':'f.g'}} )"); function good(start, mod, expected) { t.remove({}); t.save(start); var res = t.update({}, mod); assert.commandWorked(res); var got = t.findOne(); delete got._id; assert.docEq(expected, got); } good({a: 1}, {$rename: {a: 'b'}}, {b: 1}); good({a: 1}, {$rename: {a: 'bb'}}, {bb: 1}); good({b: 1}, {$rename: {b: 'a'}}, {a: 1}); good({bb: 1}, {$rename: {bb: 'a'}}, {a: 1}); good({a: {y: 1}}, {$rename: {'a.y': 'a.z'}}, {a: {z: 1}}); good({a: {yy: 1}}, {$rename: {'a.yy': 'a.z'}}, {a: {z: 1}}); good({a: {z: 1}}, {$rename: {'a.z': 'a.y'}}, {a: {y: 1}}); good({a: {zz: 1}}, {$rename: {'a.zz': 'a.y'}}, {a: {y: 1}}); good({a: {c: 1}}, {$rename: {a: 'b'}}, {b: {c: 1}}); good({aa: {c: 1}}, {$rename: {aa: 'b'}}, {b: {c: 1}}); good({a: 1, b: 2}, {$rename: {a: 'b'}}, {b: 1}); good({aa: 1, b: 2}, {$rename: {aa: 'b'}}, {b: 1}); good({a: 1, bb: 2}, {$rename: {a: 'bb'}}, {bb: 1}); good({a: 1}, {$rename: {a: 'b.c'}}, {b: {c: 1}}); good({aa: 1}, {$rename: {aa: 'b.c'}}, {b: {c: 1}}); good({a: 1, b: {}}, {$rename: {a: 'b.c'}}, {b: {c: 1}}); good({aa: 1, b: {}}, {$rename: {aa: 'b.c'}}, {b: {c: 1}}); good({a: 1}, {$rename: {b: 'c'}}, {a: 1}); good({aa: 1}, {$rename: {b: 'c'}}, {aa: 1}); good({}, {$rename: {b: 'c'}}, {}); good({a: {b: 1, c: 2}}, {$rename: {'a.b': 'd'}}, {a: {c: 2}, d: 1}); good({a: {bb: 1, c: 2}}, {$rename: {'a.bb': 'd'}}, {a: {c: 2}, d: 1}); good({a: {b: 1}}, {$rename: {'a.b': 'd'}}, {a: {}, d: 1}); good({a: [5]}, {$rename: {a: 'b'}}, {b: [5]}); good({aa: [5]}, {$rename: {aa: 'b'}}, {b: [5]}); good({'0': 1}, {$rename: {'0': '5'}}, {'5': 1}); good({a: 1, b: 2}, {$rename: {a: 'c'}, $set: {b: 5}}, {b: 5, c: 1}); good({aa: 1, b: 2}, {$rename: {aa: 'c'}, $set: {b: 5}}, {b: 5, c: 1}); good({a: 1, b: 2}, {$rename: {z: 'c'}, $set: {b: 5}}, {a: 1, b: 5}); good({aa: 1, b: 2}, {$rename: {z: 'c'}, $set: {b: 5}}, {aa: 1, b: 5}); // (formerly) rewriting single field good({a: {z: 1, b: 1}}, {$rename: {'a.b': 'a.c'}}, {a: {c: 1, z: 1}}); good({a: {z: 1, tomato: 1}}, {$rename: {'a.tomato': 'a.potato'}}, {a: {potato: 1, z: 1}}); good({a: {z: 1, b: 1, c: 1}}, {$rename: {'a.b': 'a.c'}}, {a: {c: 1, z: 1}}); good( {a: {z: 1, tomato: 1, potato: 1}}, {$rename: {'a.tomato': 'a.potato'}}, {a: {potato: 1, z: 1}}); good({a: {z: 1, b: 1}}, {$rename: {'a.b': 'a.cc'}}, {a: {cc: 1, z: 1}}); good({a: {z: 1, b: 1, c: 1}}, {$rename: {'a.b': 'aa.c'}}, {a: {c: 1, z: 1}, aa: {c: 1}}); // invalid target, but missing source good({a: 1, c: 4}, {$rename: {b: 'c.d'}}, {a: 1, c: 4}); // TODO: This should be supported, and it is with the new update framework, but not with the // old, and we currently don't have a good way to check which mode we are in. When we do have // that, add this test guarded under that condition. Or, when we remove the old update path // just enable this test. // valid to rename away from an invalid name // good( {x:1}, {$rename:{'$a.b':'a.b'}}, {x:1} ); // check index t.drop(); t.createIndex({a: 1}); function l(start, mod, query, expected) { t.remove({}); t.save(start); var res = t.update({}, mod); assert.commandWorked(res); var got = t.find(query).hint({a: 1}).next(); delete got._id; assert.docEq(expected, got); } l({a: 1}, {$rename: {a: 'b'}}, {a: null}, {b: 1}); l({a: 1}, {$rename: {a: 'bb'}}, {a: null}, {bb: 1}); l({b: 1}, {$rename: {b: 'a'}}, {a: 1}, {a: 1}); l({bb: 1}, {$rename: {bb: 'a'}}, {a: 1}, {a: 1});