mongo/jstests/core/update_with_pipeline.js

279 lines
9.4 KiB
JavaScript

/**
* Tests execution of pipeline-style update.
*
* 'requires_find_command' needed to prevent this test from running with 'compatibility' write mode
* as pipeline-style update is not supported by OP_UPDATE.
*
* @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}`);
}
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: {$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}
});
}
// 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});
// Upsert with 'upsertSupplied' inserts the given document and populates _id from the query.
assert.commandWorked(db.runCommand({
update: coll.getName(),
updates: [{
q: {_id: "supplied_doc"},
u: [{$set: {x: 1}}],
upsert: true,
upsertSupplied: true,
c: {new: {suppliedDoc: true}}
}]
}));
assert(coll.findOne({_id: "supplied_doc", suppliedDoc: true}));
// Update with 'upsertSupplied:true' fails if 'upsert' is false.
assert.commandFailedWithCode(db.runCommand({
update: coll.getName(),
updates: [{
q: {_id: "supplied_doc"},
u: [{$set: {x: 1}}],
upsert: false,
upsertSupplied: true,
c: {new: {suppliedDoc: true}}
}]
}),
ErrorCodes.FailedToParse);
// Upsert with 'upsertSupplied' fails if no constants are provided.
assert.commandFailedWithCode(db.runCommand({
update: coll.getName(),
updates: [{q: {_id: "supplied_doc"}, u: [{$set: {x: 1}}], upsert: true, upsertSupplied: true}]
}),
ErrorCodes.FailedToParse);
// Upsert with 'upsertSupplied' fails if constants do not include a field called 'new'.
assert.commandFailedWithCode(db.runCommand({
update: coll.getName(),
updates:
[{q: {_id: "supplied_doc"}, u: [{$set: {x: 1}}], upsert: true, upsertSupplied: true, c: {}}]
}),
ErrorCodes.FailedToParse);
// Upsert with 'upsertSupplied' fails if c.new is not an object.
assert.commandFailedWithCode(db.runCommand({
update: coll.getName(),
updates: [{
q: {_id: "supplied_doc"},
u: [{$set: {x: 1}}],
upsert: true,
upsertSupplied: true,
c: {new: "string"}
}]
}),
ErrorCodes.FailedToParse);
// 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}, [{
$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);
// $indexStats is not supported in a transaction passthrough and will fail with a different error.
assert.commandFailedWithCode(
coll.update({x: 1}, [{$indexStats: {}}]),
[ErrorCodes.InvalidOptions, ErrorCodes.OperationNotSupportedInTransaction]);
// 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);
})();