/** * Shard key invariant: * * A document must be created with a full non-array-or-regex shard key, and the value of that shard * key can never change. * * To enforce this invariant, we have the following mongos rule: * * - Upserts must always contain the full shard key and must only be targeted* to the applicable *shard. * * and the following mongod rules: * * - Upserted shard key values must not be arrays (or regexes). * - If a shard key value is present in the update query, upserts must only insert documents which * match this value. * - Updates must not modify shard keys. * * *Updates are targeted by the update query if $op-style, or the update document if *replacement-style. * * NOTE: The above is enough to ensure that shard keys do not change. It is not enough to ensure * uniqueness of an upserted document based on the upsert query. This is necessary due to the *save() * style operation: * db.coll.update({ _id : xxx }, { _id : xxx, shard : xxx, key : xxx, other : xxx }, { upsert : true *}) * * TODO: Minimize the impact of this hole by disallowing anything but save-style upserts of this *form. * Save-style upserts of this form are not safe (duplicate _ids can be created) but the user is * explicitly responsible for this for the _id field. * * In addition, there is an rule where non-multi updates can only affect 0 or 1 documents. * * To enforce this, we have the following mongos rule: * * - Non-multi updates must be targeted based on an exact _id query or the full shard key. * * Test setup: * - replacement style updates have the multiUpdate flag set to false. * - $ op updates have multiUpdate flag set to true. */ var st = new ShardingTest({shards: 2}); st.adminCommand({enablesharding: "test"}); st.ensurePrimaryShard('test', 'shard0001'); st.adminCommand({shardcollection: "test.col0", key: {a: 1, b: 1}}); st.adminCommand({shardcollection: "test.col1", key: {'x.a': 1}}); var db = st.s.getDB('test'); var compoundColl = db.getCollection('col0'); var dotColl = db.getCollection('col1'); // // Empty query update // compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({}, {a: 1}, false)); var doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); doc = compoundColl.findOne(); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({}, {a: 1, b: 1}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({}, {a: 100, b: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({}, {a: 100, b: 100, _id: 1}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({}, {$set: {a: 1, b: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({}, {$set: {a: 100, b: 100}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Cannot modify _id compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({}, {$set: {a: 1, b: 1, _id: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({}, {$set: {c: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100, c: 1}), 'doc did not change: ' + tojson(doc)); // // Empty query upsert // compoundColl.remove({}, false); assert.writeError(compoundColl.update({}, {a: 1}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeOK(compoundColl.update({}, {a: 1, b: 1}, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 1, b: 1}), 'doc not upserted properly: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); assert.writeOK(compoundColl.update({}, {a: 1, b: 1, _id: 1}, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 1, b: 1}), 'doc not upserted properly: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({}, {$set: {a: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({}, {$set: {a: 1, b: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); assert.writeError(compoundColl.update({}, {$set: {a: 1, b: 1, _id: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({}, {$set: {c: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); // // Partial skey query update // compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100}, {a: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100}, {a: 2}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100}, {a: 100, b: 1}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Inspecting query and update alone is not enough to tell whether a shard key will change. compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({a: 100}, {a: 100, b: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100}, {a: 100, b: 100, _id: 1}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({a: 100}, {$set: {a: 100}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100}, {$set: {b: 200}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Inspecting query and update alone is not enough to tell whether a shard key will change. compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({a: 100}, {$set: {b: 100}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100}, {$set: {a: 100, b: 200}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Inspecting query and update alone is not enough to tell whether a shard key will change. compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({a: 100}, {$set: {a: 100, b: 100}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100}, {$set: {a: 100, b: 100, _id: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({a: 100}, {$set: {c: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100, c: 1}), 'doc did not change: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({a: 100}, {$rename: {c: 'a'}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // // Partial skey query upsert // compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100}, {a: 100}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100}, {a: 2}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100}, {a: 1, b: 1}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100}, {a: 1, b: 1, _id: 1}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100}, {$set: {a: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100}, {$set: {b: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100}, {$set: {a: 100, b: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100}, {$set: {a: 100, b: 1, _id: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100}, {$set: {c: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100}, {$rename: {c: 'a'}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); // // Not prefix of skey query update // compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({b: 100}, {b: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({b: 100}, {b: 2}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({b: 100}, {a: 1}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Inspecting query and update alone is not enough to tell whether a shard key will change. compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({b: 100}, {a: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({b: 100}, {a: 1, b: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Inspecting query and update alone is not enough to tell whether a shard key will change. compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({b: 100}, {a: 100, b: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({b: 100}, {a: 1, b: 1, _id: 1}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({b: 100}, {$set: {b: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({b: 100}, {$set: {a: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({b: 100}, {$set: {a: 100}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({b: 100}, {$set: {a: 1, b: 100}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Inspecting query and update alone is not enough to tell whether a shard key will change. compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({b: 100}, {$set: {a: 100, b: 100}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({b: 100}, {$set: {a: 100, b: 100, _id: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({b: 100}, {$set: {c: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100, c: 1}), 'doc did not change: ' + tojson(doc)); // // Not prefix of skey query upsert // compoundColl.remove({}, false); assert.writeError(compoundColl.update({b: 100}, {b: 100}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({b: 100}, {b: 2}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({b: 100}, {a: 1}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({b: 100}, {a: 1, b: 1}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); assert.writeError(compoundColl.update({b: 100}, {a: 1, b: 1, _id: 1}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({b: 100}, {$set: {b: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({b: 100}, {$set: {a: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({b: 100}, {$set: {a: 1, b: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); assert.writeError(compoundColl.update({b: 100}, {$set: {a: 1, b: 1, _id: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({b: 100}, {$set: {c: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc upserted: ' + tojson(doc)); // // Full skey query update // compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100, b: 100}, {a: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({a: 100, b: 100}, {a: 100, b: 100, c: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100, c: 100}), 'doc did not change: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100, b: 100}, {a: 100, b: 100, _id: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100, b: 100}, {b: 100}, false)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({a: 100, b: 100}, {$set: {b: 100, c: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100, c: 1}), 'doc did not change: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({a: 100, b: 100}, {$set: {a: 100, b: 100, c: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100, c: 1}), 'doc did not change: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError( compoundColl.update({a: 100, b: 100}, {$set: {a: 100, b: 100, _id: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeError(compoundColl.update({a: 100, b: 100}, {$set: {a: 100, b: 2, c: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({a: 100, b: 100}); assert.writeOK(compoundColl.update({a: 100, b: 100}, {$set: {c: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100, c: 1}), 'doc did not change: ' + tojson(doc)); // // Full skey query upsert // compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100, b: 100}, {a: 100}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeOK(compoundColl.update({a: 100, b: 100}, {a: 100, b: 100, c: 1}, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100, c: 1}), 'wrong doc: ' + tojson(doc)); // Cannot modify _id! compoundColl.remove({}, false); assert.writeOK(compoundColl.update({a: 100, b: 100}, {a: 100, b: 100, _id: 100}, true)); doc = compoundColl.findOne(); assert(friendlyEqual(doc, {_id: 100, a: 100, b: 100}), 'wrong doc: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100, b: 100}, {b: 100}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeOK(compoundColl.update({a: 100, b: 100}, {$set: {b: 100, c: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc != null, 'doc was not upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeOK(compoundColl.update({a: 100, b: 100}, {$set: {a: 100, b: 100, c: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc != null, 'doc was not upserted: ' + tojson(doc)); // Can upsert with new _id compoundColl.remove({}, false); assert.writeOK(compoundColl.update({a: 100, b: 100}, {$set: {a: 100, b: 100, _id: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc != null, 'doc was not upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({a: 100, b: 100}, {$set: {a: 100, b: 2, c: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeOK(compoundColl.update({a: 100, b: 100}, {$set: {c: 1}}, true, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100, c: 1}), 'wrong doc: ' + tojson(doc)); // // _id query update // compoundColl.remove({}, false); compoundColl.insert({_id: 1, a: 100, b: 100}); assert.writeError(compoundColl.update({_id: 1}, {a: 1})); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // Special case for _id. This is for making save method work. compoundColl.remove({}, false); compoundColl.insert({_id: 1, a: 100, b: 100}); assert.writeOK(compoundColl.update({_id: 1}, {a: 100, b: 100})); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({_id: 1, a: 100, b: 100}); assert.writeError(compoundColl.update({_id: 1}, {a: 1, b: 1})); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({_id: 1, a: 100, b: 100}); assert.writeError(compoundColl.update({_id: 1}, {$set: {a: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({_id: 1, a: 100, b: 100}); assert.writeOK(compoundColl.update({_id: 1}, {$set: {a: 100}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({_id: 1, a: 100, b: 100}); assert.writeError(compoundColl.update({_id: 1}, {$set: {b: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({_id: 1, a: 100, b: 100}); assert.writeOK(compoundColl.update({_id: 1}, {$set: {b: 100}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); compoundColl.remove({}, false); compoundColl.insert({_id: 1, a: 100, b: 100}); assert.writeError(compoundColl.update({_id: 1}, {$set: {a: 1, b: 1}}, false, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 100, b: 100}), 'doc changed: ' + tojson(doc)); // // _id query upsert // compoundColl.remove({}, false); assert.writeError(compoundColl.update({_id: 1}, {a: 1}, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeOK(compoundColl.update({_id: 1}, {a: 1, b: 1}, true)); doc = compoundColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {a: 1, b: 1}), 'bad doc: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({_id: 1}, {$set: {a: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({_id: 1}, {$set: {b: 1}}, true, true)); doc = compoundColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); compoundColl.remove({}, false); assert.writeError(compoundColl.update({_id: 1}, {$set: {a: 1, b: 1}}, true, true)); assert.eq(0, compoundColl.count(), 'doc should not be inserted'); // // Dotted query update // dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeOK(dotColl.update({'x.a': 100}, {x: {a: 100, b: 2}})); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100, b: 2}}), 'doc did not change: ' + tojson(doc)); // Dotted field names in the update object do not represent paths. The path must be expanded // since single document updates require a match on the shard key. // (e.g. 'x.a' will not work, but x: {a: ...} will work). dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeErrorWithCode(dotColl.update({'x.a': 100}, {x: {'a.z': 100}}), ErrorCodes.ShardKeyNotFound); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeErrorWithCode(dotColl.update({'x.a': 100}, {'x.a': 100}), ErrorCodes.ShardKeyNotFound); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeOK(dotColl.update({'x.a': 100}, {x: {a: 100}, b: 1})); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeError(dotColl.update({'x.a': 100}, {x: 100})); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100}}), 'doc changed: ' + tojson(doc)); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeError(dotColl.update({'x.a': 100}, {x: {b: 100}})); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100}}), 'doc changed: ' + tojson(doc)); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeOK(dotColl.update({'x.a': 100}, {$set: {x: {a: 100, b: 2}}}, false, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100, b: 2}}), 'doc did not change: ' + tojson(doc)); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeError(dotColl.update({'x.a': 100}, {$set: {x: {a: 2}}}, false, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100}}), 'doc changed: ' + tojson(doc)); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeError(dotColl.update({'x.a': 100}, {$set: {x: {b: 100}}}, false, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100}}), 'doc changed: ' + tojson(doc)); // Dotted field names within $set are treated as paths. dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeOK(dotColl.update({'x.a': 100}, {$set: {'x.a': 100, b: 2}}, false, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100}, b: 2}), 'doc did not change: ' + tojson(doc)); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeError(dotColl.update({'x.a': 100}, {$set: {x: {'a.z': 100}}}, false, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100}}), 'doc changed: ' + tojson(doc)); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeError(dotColl.update({'x.a': 100}, {$set: {'x.a.z': 100}}, false, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100}}), 'doc changed: ' + tojson(doc)); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeError(dotColl.update({'x.a': 100}, {$set: {x: 100}}, false, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100}}), 'doc changed: ' + tojson(doc)); dotColl.remove({}, false); dotColl.insert({x: {a: 100}}); assert.writeOK(dotColl.update({'x.a': 100}, {$set: {'x.b': 200}}, false, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100, b: 200}}), 'doc did not change: ' + tojson(doc)); // // Dotted query upsert // dotColl.remove({}, false); assert.writeOK(dotColl.update({'x.a': 100}, {x: {a: 100, b: 2}}, true)); doc = dotColl.findOne(); assert(doc != null, 'doc was not upserted: ' + tojson(doc)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {x: {z: 100}}, true)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {'x.a': 100}, true)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {'x.a.z': 100}, true)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {x: 100}, true)); doc = dotColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {x: {b: 100}}, true)); doc = dotColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); dotColl.remove({}, false); assert.writeOK(dotColl.update({'x.a': 100}, {$set: {x: {a: 100, b: 2}}}, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100, b: 2}}), 'bad doc: ' + tojson(doc)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {$set: {x: {a: 2}}}, true)); doc = dotColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {$set: {x: {b: 100}}}, true)); doc = dotColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); dotColl.remove({}, false); assert.writeOK(dotColl.update({'x.a': 100}, {$set: {'x.a': 100, b: 3}}, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100}, b: 3}), 'bad doc: ' + tojson(doc)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {$set: {'x.a': 2}}, true)); doc = dotColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {$set: {x: {'a.z': 100}}}, true)); doc = dotColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {$set: {'x.a.z': 100}}, true)); doc = dotColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); dotColl.remove({}, false); assert.writeError(dotColl.update({'x.a': 100}, {$set: {x: 100}}, true)); doc = dotColl.findOne(); assert(doc == null, 'doc was upserted: ' + tojson(doc)); dotColl.remove({}, false); assert.writeOK(dotColl.update({'x.a': 100}, {$set: {'x.b': 2}}, true)); doc = dotColl.findOne(); delete doc._id; assert(friendlyEqual(doc, {x: {a: 100, b: 2}}), 'bad doc: ' + tojson(doc)); st.stop();