diff options
Diffstat (limited to 'jstests/core/update_with_pipeline.js')
-rw-r--r-- | jstests/core/update_with_pipeline.js | 417 |
1 files changed, 202 insertions, 215 deletions
diff --git a/jstests/core/update_with_pipeline.js b/jstests/core/update_with_pipeline.js index 07d92c718df..963d72b6592 100644 --- a/jstests/core/update_with_pipeline.js +++ b/jstests/core/update_with_pipeline.js @@ -7,227 +7,214 @@ * @tags: [requires_find_command, requires_non_retryable_writes] */ (function() { - "use strict"; - - load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers. - - const collName = "update_with_pipeline"; - const coll = db[collName]; - - assert.commandWorked(coll.createIndex({x: 1})); - assert.commandWorked(coll.createIndex({"y.$**": 1})); - - /** - * Confirms that an update returns the expected set of documents. 'nModified' documents from - * 'resultDocList' must match. 'nModified' may be smaller then the number of elements in - * 'resultDocList'. This allows for the case where there are multiple documents that could be - * updated, but only one is actually updated due to a 'multi: false' argument. Constant values - * to the update command are passed in the 'constants' argument. - */ - function testUpdate({ - query, - initialDocumentList, - update, - resultDocList, - nModified, - options = {}, - constants = undefined - }) { - assert.eq(initialDocumentList.length, resultDocList.length); - assert.commandWorked(coll.remove({})); - assert.commandWorked(coll.insert(initialDocumentList)); - const upd = Object.assign({q: query, u: update}, options); - if (constants !== undefined) { - upd.c = constants; - } - const res = assert.commandWorked(db.runCommand({update: collName, updates: [upd]})); - assert.eq(nModified, res.nModified); - - let nMatched = 0; - for (let i = 0; i < resultDocList.length; ++i) { - if (0 === bsonWoCompare(coll.findOne(resultDocList[i]), resultDocList[i])) { - ++nMatched; - } - } - assert.eq( - nModified, nMatched, `actual=${coll.find().toArray()}, expected=${resultDocList}`); - } +"use strict"; - function testUpsertDoesInsert(query, update, resultDoc) { - assert.commandWorked(coll.remove({})); - assert.commandWorked(coll.update(query, update, {upsert: true})); - assert.eq(coll.findOne({}), resultDoc, coll.find({}).toArray()); - } +load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers. - // Update with existing document. - testUpdate({ - query: {_id: 1}, - initialDocumentList: [{_id: 1, x: 1}], - update: [{$set: {foo: 4}}], - resultDocList: [{_id: 1, x: 1, foo: 4}], - nModified: 1 - }); - testUpdate({ - query: {_id: 1}, - initialDocumentList: [{_id: 1, x: 1, y: 1}], - update: [{$project: {x: 1}}], - resultDocList: [{_id: 1, x: 1}], - nModified: 1 - }); - testUpdate({ - query: {_id: 1}, - initialDocumentList: [{_id: 1, x: 1, y: [{z: 1, foo: 1}]}], - update: [{$unset: ["x", "y.z"]}], - resultDocList: [{_id: 1, y: [{foo: 1}]}], - nModified: 1 - }); - testUpdate({ - query: {_id: 1}, - initialDocumentList: [{_id: 1, x: 1, t: {u: {v: 1}}}], - update: [{$replaceWith: "$t"}], - resultDocList: [{_id: 1, u: {v: 1}}], - nModified: 1 - }); +const collName = "update_with_pipeline"; +const coll = db[collName]; - // Multi-update. - testUpdate({ - query: {x: 1}, - initialDocumentList: [{_id: 1, x: 1}, {_id: 2, x: 1}], - update: [{$set: {bar: 4}}], - resultDocList: [{_id: 1, x: 1, bar: 4}, {_id: 2, x: 1, bar: 4}], - nModified: 2, - options: {multi: true} - }); +assert.commandWorked(coll.createIndex({x: 1})); +assert.commandWorked(coll.createIndex({"y.$**": 1})); - // This test will fail in a sharded cluster when the 2 initial documents live on different - // shards. - if (!FixtureHelpers.isMongos(db)) { - testUpdate({ - query: {_id: {$in: [1, 2]}}, - initialDocumentList: [{_id: 1, x: 1}, {_id: 2, x: 2}], - update: [{$set: {bar: 4}}], - resultDocList: [{_id: 1, x: 1, bar: 4}, {_id: 2, x: 2, bar: 4}], - nModified: 1, - options: {multi: false} - }); +/** + * Confirms that an update returns the expected set of documents. 'nModified' documents from + * 'resultDocList' must match. 'nModified' may be smaller then the number of elements in + * 'resultDocList'. This allows for the case where there are multiple documents that could be + * updated, but only one is actually updated due to a 'multi: false' argument. Constant values + * to the update command are passed in the 'constants' argument. + */ +function testUpdate({ + query, + initialDocumentList, + update, + resultDocList, + nModified, + options = {}, + constants = undefined +}) { + assert.eq(initialDocumentList.length, resultDocList.length); + assert.commandWorked(coll.remove({})); + assert.commandWorked(coll.insert(initialDocumentList)); + const upd = Object.assign({q: query, u: update}, options); + if (constants !== undefined) { + upd.c = constants; } + const res = assert.commandWorked(db.runCommand({update: collName, updates: [upd]})); + assert.eq(nModified, res.nModified); - // Upsert performs insert. - testUpsertDoesInsert({_id: 1, x: 1}, [{$set: {foo: 4}}], {_id: 1, x: 1, foo: 4}); - testUpsertDoesInsert({_id: 1, x: 1}, [{$project: {x: 1}}], {_id: 1, x: 1}); - testUpsertDoesInsert({_id: 1, x: 1}, [{$project: {x: "foo"}}], {_id: 1, x: "foo"}); - testUpsertDoesInsert({_id: 1, x: 1, y: 1}, [{$unset: ["x"]}], {_id: 1, y: 1}); - - // Update fails when invalid stage is specified. This is a sanity check rather than an - // exhaustive test of all stages. - assert.commandFailedWithCode(coll.update({x: 1}, [{$match: {x: 1}}]), - ErrorCodes.InvalidOptions); - assert.commandFailedWithCode(coll.update({x: 1}, [{$sort: {x: 1}}]), ErrorCodes.InvalidOptions); - assert.commandFailedWithCode(coll.update({x: 1}, [{$facet: {a: [{$match: {x: 1}}]}}]), - ErrorCodes.InvalidOptions); - assert.commandFailedWithCode(coll.update({x: 1}, [{$indexStats: {}}]), - ErrorCodes.InvalidOptions); - assert.commandFailedWithCode(coll.update({x: 1}, [{ - $bucket: { - groupBy: "$a", - boundaries: [0, 1], - default: "foo", - output: {count: {$sum: 1}} - } - }]), - ErrorCodes.InvalidOptions); - assert.commandFailedWithCode( - coll.update({x: 1}, - [{$lookup: {from: "foo", as: "as", localField: "a", foreignField: "b"}}]), - ErrorCodes.InvalidOptions); - assert.commandFailedWithCode(coll.update({x: 1}, [{ - $graphLookup: { - from: "foo", - startWith: "$a", - connectFromField: "a", - connectToField: "b", - as: "as" - } - }]), - ErrorCodes.InvalidOptions); - - // Update fails when supported agg stage is specified outside of pipeline. - assert.commandFailedWithCode(coll.update({_id: 1}, {$addFields: {x: 1}}), - ErrorCodes.FailedToParse); - - // The 'arrayFilters' option is not valid for pipeline updates. - assert.commandFailedWithCode( - coll.update({_id: 1}, [{$set: {x: 1}}], {arrayFilters: [{x: {$eq: 1}}]}), - ErrorCodes.FailedToParse); - - // Constants can be specified with pipeline-style updates. - testUpdate({ - query: {_id: 1}, - initialDocumentList: [{_id: 1, x: 1}], - useUpdateCommand: true, - constants: {foo: "bar"}, - update: [{$set: {foo: "$$foo"}}], - resultDocList: [{_id: 1, x: 1, foo: "bar"}], - nModified: 1 - }); - testUpdate({ - query: {_id: 1}, - initialDocumentList: [{_id: 1, x: 1}], - useUpdateCommand: true, - constants: {foo: {a: {b: {c: "bar"}}}}, - update: [{$set: {foo: "$$foo"}}], - resultDocList: [{_id: 1, x: 1, foo: {a: {b: {c: "bar"}}}}], - nModified: 1 - }); - testUpdate({ - query: {_id: 1}, - initialDocumentList: [{_id: 1, x: 1}], - useUpdateCommand: true, - constants: {foo: [1, 2, 3]}, - update: [{$set: {foo: {$arrayElemAt: ["$$foo", 2]}}}], - resultDocList: [{_id: 1, x: 1, foo: 3}], - nModified: 1 - }); - - const largeStr = "x".repeat(1000); - testUpdate({ - query: {_id: 1}, - initialDocumentList: [{_id: 1, x: 1}], - useUpdateCommand: true, - constants: {largeStr: largeStr}, - update: [{$set: {foo: "$$largeStr"}}], - resultDocList: [{_id: 1, x: 1, foo: largeStr}], - nModified: 1 - }); - - // References to document fields are not resolved in constants. + let nMatched = 0; + for (let i = 0; i < resultDocList.length; ++i) { + if (0 === bsonWoCompare(coll.findOne(resultDocList[i]), resultDocList[i])) { + ++nMatched; + } + } + assert.eq(nModified, nMatched, `actual=${coll.find().toArray()}, expected=${resultDocList}`); +} + +function testUpsertDoesInsert(query, update, resultDoc) { + assert.commandWorked(coll.remove({})); + assert.commandWorked(coll.update(query, update, {upsert: true})); + assert.eq(coll.findOne({}), resultDoc, coll.find({}).toArray()); +} + +// Update with existing document. +testUpdate({ + query: {_id: 1}, + initialDocumentList: [{_id: 1, x: 1}], + update: [{$set: {foo: 4}}], + resultDocList: [{_id: 1, x: 1, foo: 4}], + nModified: 1 +}); +testUpdate({ + query: {_id: 1}, + initialDocumentList: [{_id: 1, x: 1, y: 1}], + update: [{$project: {x: 1}}], + resultDocList: [{_id: 1, x: 1}], + nModified: 1 +}); +testUpdate({ + query: {_id: 1}, + initialDocumentList: [{_id: 1, x: 1, y: [{z: 1, foo: 1}]}], + update: [{$unset: ["x", "y.z"]}], + resultDocList: [{_id: 1, y: [{foo: 1}]}], + nModified: 1 +}); +testUpdate({ + query: {_id: 1}, + initialDocumentList: [{_id: 1, x: 1, t: {u: {v: 1}}}], + update: [{$replaceWith: "$t"}], + resultDocList: [{_id: 1, u: {v: 1}}], + nModified: 1 +}); + +// Multi-update. +testUpdate({ + query: {x: 1}, + initialDocumentList: [{_id: 1, x: 1}, {_id: 2, x: 1}], + update: [{$set: {bar: 4}}], + resultDocList: [{_id: 1, x: 1, bar: 4}, {_id: 2, x: 1, bar: 4}], + nModified: 2, + options: {multi: true} +}); + +// This test will fail in a sharded cluster when the 2 initial documents live on different +// shards. +if (!FixtureHelpers.isMongos(db)) { testUpdate({ - query: {_id: 1}, - initialDocumentList: [{_id: 1, x: 1}], - useUpdateCommand: true, - constants: {foo: "$x"}, - update: [{$set: {foo: "$$foo"}}], - resultDocList: [{_id: 1, x: 1, foo: "$x"}], - nModified: 1 + query: {_id: {$in: [1, 2]}}, + initialDocumentList: [{_id: 1, x: 1}, {_id: 2, x: 2}], + update: [{$set: {bar: 4}}], + resultDocList: [{_id: 1, x: 1, bar: 4}, {_id: 2, x: 2, bar: 4}], + nModified: 1, + options: {multi: false} }); - - // Cannot use expressions in constants. - assert.commandFailedWithCode(db.runCommand({ - update: collName, - updates: [{q: {_id: 1}, u: [{$set: {x: "$$foo"}}], c: {foo: {$add: [1, 2]}}}] - }), - ErrorCodes.DollarPrefixedFieldName); - - // Cannot use constants with regular updates. - assert.commandFailedWithCode( - db.runCommand( - {update: collName, updates: [{q: {_id: 1}, u: {x: "$$foo"}, c: {foo: "bar"}}]}), - 51198); - assert.commandFailedWithCode( - db.runCommand( - {update: collName, updates: [{q: {_id: 1}, u: {$set: {x: "$$foo"}}, c: {foo: "bar"}}]}), - 51198); - assert.commandFailedWithCode( - db.runCommand({update: collName, updates: [{q: {_id: 1}, u: {$set: {x: "1"}}, c: {}}]}), - 51198); +} + +// Upsert performs insert. +testUpsertDoesInsert({_id: 1, x: 1}, [{$set: {foo: 4}}], {_id: 1, x: 1, foo: 4}); +testUpsertDoesInsert({_id: 1, x: 1}, [{$project: {x: 1}}], {_id: 1, x: 1}); +testUpsertDoesInsert({_id: 1, x: 1}, [{$project: {x: "foo"}}], {_id: 1, x: "foo"}); +testUpsertDoesInsert({_id: 1, x: 1, y: 1}, [{$unset: ["x"]}], {_id: 1, y: 1}); + +// Update fails when invalid stage is specified. This is a sanity check rather than an +// exhaustive test of all stages. +assert.commandFailedWithCode(coll.update({x: 1}, [{$match: {x: 1}}]), ErrorCodes.InvalidOptions); +assert.commandFailedWithCode(coll.update({x: 1}, [{$sort: {x: 1}}]), ErrorCodes.InvalidOptions); +assert.commandFailedWithCode(coll.update({x: 1}, [{$facet: {a: [{$match: {x: 1}}]}}]), + ErrorCodes.InvalidOptions); +assert.commandFailedWithCode(coll.update({x: 1}, [{$indexStats: {}}]), ErrorCodes.InvalidOptions); +assert.commandFailedWithCode( + coll.update( + {x: 1}, [{ + $bucket: {groupBy: "$a", boundaries: [0, 1], default: "foo", output: {count: {$sum: 1}}} + }]), + ErrorCodes.InvalidOptions); +assert.commandFailedWithCode( + coll.update({x: 1}, [{$lookup: {from: "foo", as: "as", localField: "a", foreignField: "b"}}]), + ErrorCodes.InvalidOptions); +assert.commandFailedWithCode( + coll.update( + {x: 1}, [{ + $graphLookup: + {from: "foo", startWith: "$a", connectFromField: "a", connectToField: "b", as: "as"} + }]), + ErrorCodes.InvalidOptions); + +// Update fails when supported agg stage is specified outside of pipeline. +assert.commandFailedWithCode(coll.update({_id: 1}, {$addFields: {x: 1}}), ErrorCodes.FailedToParse); + +// The 'arrayFilters' option is not valid for pipeline updates. +assert.commandFailedWithCode( + coll.update({_id: 1}, [{$set: {x: 1}}], {arrayFilters: [{x: {$eq: 1}}]}), + ErrorCodes.FailedToParse); + +// Constants can be specified with pipeline-style updates. +testUpdate({ + query: {_id: 1}, + initialDocumentList: [{_id: 1, x: 1}], + useUpdateCommand: true, + constants: {foo: "bar"}, + update: [{$set: {foo: "$$foo"}}], + resultDocList: [{_id: 1, x: 1, foo: "bar"}], + nModified: 1 +}); +testUpdate({ + query: {_id: 1}, + initialDocumentList: [{_id: 1, x: 1}], + useUpdateCommand: true, + constants: {foo: {a: {b: {c: "bar"}}}}, + update: [{$set: {foo: "$$foo"}}], + resultDocList: [{_id: 1, x: 1, foo: {a: {b: {c: "bar"}}}}], + nModified: 1 +}); +testUpdate({ + query: {_id: 1}, + initialDocumentList: [{_id: 1, x: 1}], + useUpdateCommand: true, + constants: {foo: [1, 2, 3]}, + update: [{$set: {foo: {$arrayElemAt: ["$$foo", 2]}}}], + resultDocList: [{_id: 1, x: 1, foo: 3}], + nModified: 1 +}); + +const largeStr = "x".repeat(1000); +testUpdate({ + query: {_id: 1}, + initialDocumentList: [{_id: 1, x: 1}], + useUpdateCommand: true, + constants: {largeStr: largeStr}, + update: [{$set: {foo: "$$largeStr"}}], + resultDocList: [{_id: 1, x: 1, foo: largeStr}], + nModified: 1 +}); + +// References to document fields are not resolved in constants. +testUpdate({ + query: {_id: 1}, + initialDocumentList: [{_id: 1, x: 1}], + useUpdateCommand: true, + constants: {foo: "$x"}, + update: [{$set: {foo: "$$foo"}}], + resultDocList: [{_id: 1, x: 1, foo: "$x"}], + nModified: 1 +}); + +// Cannot use expressions in constants. +assert.commandFailedWithCode(db.runCommand({ + update: collName, + updates: [{q: {_id: 1}, u: [{$set: {x: "$$foo"}}], c: {foo: {$add: [1, 2]}}}] +}), + ErrorCodes.DollarPrefixedFieldName); + +// Cannot use constants with regular updates. +assert.commandFailedWithCode( + db.runCommand({update: collName, updates: [{q: {_id: 1}, u: {x: "$$foo"}, c: {foo: "bar"}}]}), + 51198); +assert.commandFailedWithCode( + db.runCommand( + {update: collName, updates: [{q: {_id: 1}, u: {$set: {x: "$$foo"}}, c: {foo: "bar"}}]}), + 51198); +assert.commandFailedWithCode( + db.runCommand({update: collName, updates: [{q: {_id: 1}, u: {$set: {x: "1"}}, c: {}}]}), 51198); })(); |