mirror of https://github.com/mongodb/mongo
228 lines
7.3 KiB
JavaScript
228 lines
7.3 KiB
JavaScript
/**
|
|
* Tests the express code path, which bypasses regular query planning and execution, for writes.
|
|
* Verifies some basic eligibility restrictions such as match expression shape and index options,
|
|
* and checks the query results.
|
|
* @tags: [
|
|
* requires_fcv_81,
|
|
* requires_non_retryable_commands,
|
|
* requires_non_retryable_writes,
|
|
* requires_getmore,
|
|
* ]
|
|
*/
|
|
|
|
import {assertArrayEq} from "jstests/aggregation/extras/utils.js";
|
|
import {isExpress} from "jstests/libs/query/analyze_plan.js";
|
|
|
|
const collName = "express_write_coll";
|
|
const coll = db.getCollection(collName);
|
|
const docs = [
|
|
{_id: 0, a: 0},
|
|
{_id: "str", a: 1},
|
|
];
|
|
|
|
function runExpressTest({command, expectedDocs, usesExpress}) {
|
|
// Reset the collection docs.
|
|
assert.commandWorked(coll.remove({}));
|
|
assert.commandWorked(coll.insert(docs));
|
|
|
|
// Run the command to make sure it succeeds.
|
|
assert.commandWorked(db.runCommand(command));
|
|
assertArrayEq({
|
|
actual: coll.find().toArray(),
|
|
expected: expectedDocs,
|
|
extraErrorMsg: "Result set comparison failed for command: " + tojson(command),
|
|
});
|
|
|
|
// Reset the collection docs then run explain.
|
|
assert.commandWorked(coll.remove({}));
|
|
assert.commandWorked(coll.insert(docs));
|
|
const explain = assert.commandWorked(db.runCommand({explain: command, verbosity: "executionStats"}));
|
|
|
|
assert.eq(
|
|
usesExpress,
|
|
isExpress(db, explain),
|
|
"Expected the query to " + (usesExpress ? "" : "not ") + "use express: " + tojson(explain),
|
|
);
|
|
}
|
|
|
|
coll.drop();
|
|
|
|
// Cannot use express path when predicate is not a single equality or when a projection is present.
|
|
runExpressTest({
|
|
command: {delete: collName, deletes: [{q: {_id: 0, a: 0}, limit: 0}]},
|
|
usesExpress: false,
|
|
expectedDocs: [docs[1]],
|
|
});
|
|
runExpressTest({
|
|
command: {delete: collName, deletes: [{q: {_id: 0, a: 0}, limit: 1}]},
|
|
usesExpress: false,
|
|
expectedDocs: [docs[1]],
|
|
});
|
|
runExpressTest({
|
|
command: {findAndModify: collName, query: {_id: "str", a: 1}, update: [{$set: {c: 1}}]},
|
|
usesExpress: false,
|
|
expectedDocs: [docs[0], {_id: "str", a: 1, c: 1}],
|
|
});
|
|
runExpressTest({
|
|
command: {findAndModify: collName, query: {_id: 0}, remove: true, fields: {_id: 1, a: 1}},
|
|
usesExpress: false,
|
|
expectedDocs: [docs[1]],
|
|
});
|
|
runExpressTest({
|
|
command: {
|
|
findAndModify: collName,
|
|
query: {_id: "str"},
|
|
update: {$set: {c: 1}},
|
|
fields: {_id: 1, a: 1, c: 1},
|
|
},
|
|
usesExpress: false,
|
|
expectedDocs: [docs[0], {_id: "str", a: 1, c: 1}],
|
|
});
|
|
runExpressTest({
|
|
command: {update: collName, updates: [{q: {_id: "str", a: 1}, u: {$set: {c: 1}}}]},
|
|
usesExpress: false,
|
|
expectedDocs: [docs[0], {_id: "str", a: 1, c: 1}],
|
|
});
|
|
|
|
// Cannot use express path when a hint is specified.
|
|
assert.commandWorked(coll.createIndex({a: 1}));
|
|
runExpressTest({
|
|
command: {delete: collName, deletes: [{q: {_id: 0}, limit: 0, hint: {a: 1}}]},
|
|
usesExpress: false,
|
|
expectedDocs: [docs[1]],
|
|
});
|
|
runExpressTest({
|
|
command: {findAndModify: collName, query: {_id: "str"}, update: [{$set: {c: 1}}], hint: {a: 1}},
|
|
usesExpress: false,
|
|
expectedDocs: [docs[0], {_id: "str", a: 1, c: 1}],
|
|
});
|
|
runExpressTest({
|
|
command: {update: collName, updates: [{q: {_id: "str"}, u: {$set: {c: 1}}, hint: {a: 1}}]},
|
|
usesExpress: false,
|
|
expectedDocs: [docs[0], {_id: "str", a: 1, c: 1}],
|
|
});
|
|
coll.dropIndexes();
|
|
|
|
// Can use express path use for delete commands when the predicate is a single equality on _id,
|
|
// regardless of the 'limit' value.
|
|
runExpressTest({
|
|
command: {delete: collName, deletes: [{q: {_id: 0}, limit: 0}]},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[1]],
|
|
});
|
|
runExpressTest({
|
|
command: {delete: collName, deletes: [{q: {_id: 0}, limit: 1}]},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[1]],
|
|
});
|
|
runExpressTest({
|
|
command: {delete: collName, deletes: [{q: {_id: "str"}, limit: 0}]},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[0]],
|
|
});
|
|
// Same, but with query shape {_id: {$eq: <val>}}.
|
|
runExpressTest({
|
|
command: {delete: collName, deletes: [{q: {_id: {$eq: 0}}, limit: 0}]},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[1]],
|
|
});
|
|
runExpressTest({
|
|
command: {delete: collName, deletes: [{q: {_id: {$eq: 0}}, limit: 1}]},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[1]],
|
|
});
|
|
runExpressTest({
|
|
command: {delete: collName, deletes: [{q: {_id: {$eq: "str"}}, limit: 0}]},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[0]],
|
|
});
|
|
|
|
// Can use express path use for findAndModify commands when the predicate is a single equality on
|
|
// _id. This is true whether the findAndModify represents an update or a delete.
|
|
runExpressTest({
|
|
command: {findAndModify: collName, query: {_id: "str"}, update: [{$set: {c: 1}}]},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[0], {_id: "str", a: 1, c: 1}],
|
|
});
|
|
runExpressTest({
|
|
command: {findAndModify: collName, query: {_id: 0}, update: [{$set: {c: 1}}]},
|
|
usesExpress: true,
|
|
expectedDocs: [{_id: 0, a: 0, c: 1}, docs[1]],
|
|
});
|
|
runExpressTest({
|
|
command: {findAndModify: collName, query: {_id: 0}, remove: true},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[1]],
|
|
});
|
|
// Same, but with query shape {_id: {$eq: <val>}}.
|
|
runExpressTest({
|
|
command: {findAndModify: collName, query: {_id: {$eq: "str"}}, update: [{$set: {c: 1}}]},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[0], {_id: "str", a: 1, c: 1}],
|
|
});
|
|
runExpressTest({
|
|
command: {findAndModify: collName, query: {_id: {$eq: 0}}, update: [{$set: {c: 1}}]},
|
|
usesExpress: true,
|
|
expectedDocs: [{_id: 0, a: 0, c: 1}, docs[1]],
|
|
});
|
|
runExpressTest({
|
|
command: {findAndModify: collName, query: {_id: {$eq: 0}}, remove: true},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[1]],
|
|
});
|
|
|
|
// Can use express path use for update commands when the predicate is a single equality on _id.
|
|
runExpressTest({
|
|
command: {update: collName, updates: [{q: {_id: "str"}, u: {$set: {c: 1}}}]},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[0], {_id: "str", a: 1, c: 1}],
|
|
});
|
|
runExpressTest({
|
|
command: {update: collName, updates: [{q: {_id: 0}, u: {$set: {c: 1}}}]},
|
|
usesExpress: true,
|
|
expectedDocs: [{_id: 0, a: 0, c: 1}, docs[1]],
|
|
});
|
|
// Same, but with query shape {_id: {$eq: <val>}}.
|
|
runExpressTest({
|
|
command: {update: collName, updates: [{q: {_id: {$eq: "str"}}, u: {$set: {c: 1}}}]},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[0], {_id: "str", a: 1, c: 1}],
|
|
});
|
|
runExpressTest({
|
|
command: {update: collName, updates: [{q: {_id: {$eq: 0}}, u: {$set: {c: 1}}}]},
|
|
usesExpress: true,
|
|
expectedDocs: [{_id: 0, a: 0, c: 1}, docs[1]],
|
|
});
|
|
|
|
docs.push({_id: 2, arr: [99, 100, 101]});
|
|
runExpressTest({
|
|
command: {
|
|
update: collName,
|
|
updates: [
|
|
{
|
|
q: {_id: 2},
|
|
u: {$set: {"arr.$[element]": 100}},
|
|
arrayFilters: [{"element": {$gte: 100}}],
|
|
},
|
|
],
|
|
},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[0], docs[1], {_id: 2, arr: [99, 100, 100]}],
|
|
});
|
|
|
|
// Same, but with query shape {_id: {$eq: <val>}}.
|
|
runExpressTest({
|
|
command: {
|
|
update: collName,
|
|
updates: [
|
|
{
|
|
q: {_id: {$eq: 2}},
|
|
u: {$set: {"arr.$[element]": 100}},
|
|
arrayFilters: [{"element": {$gte: 100}}],
|
|
},
|
|
],
|
|
},
|
|
usesExpress: true,
|
|
expectedDocs: [docs[0], docs[1], {_id: 2, arr: [99, 100, 100]}],
|
|
});
|