mirror of https://github.com/mongodb/mongo
225 lines
10 KiB
JavaScript
225 lines
10 KiB
JavaScript
/**
|
|
* Tests that when the parameter internalQueryPermitMatchSwappingForComplexRenames is set,
|
|
* then match will swap with complex renames.
|
|
*/
|
|
|
|
import {normalizeArray} from "jstests/libs/golden_test.js";
|
|
import {code, linebreak, section, subSection} from "jstests/libs/pretty_md.js";
|
|
|
|
try {
|
|
assert.commandWorked(db.adminCommand({setParameter: 1, internalQueryPermitMatchSwappingForComplexRenames: true}));
|
|
const coll = db.complex_match_swap;
|
|
coll.drop();
|
|
|
|
section("Inserting docs:");
|
|
const docs = [
|
|
{_id: 1, z: 11, h: {i: 11}, b: {c: 42}},
|
|
{_id: 2, z: 12, h: {i: 12}, b: {}},
|
|
{_id: 3, z: 13, h: {i: 13}, b: {c: null}},
|
|
{_id: 4, z: 14, h: {i: 14}, b: {c: 42, d: "foo"}},
|
|
{_id: 5, z: 15, h: {i: 15}, b: {c: {e: 42, f: "bar"}}},
|
|
{_id: 6, z: 16, h: {i: 16}, b: {c: {e: 42, f: {g: 9}}, d: "foo"}},
|
|
];
|
|
code(tojson(docs));
|
|
|
|
assert.commandWorked(coll.insert(docs));
|
|
|
|
function runPipeline(testCaseName, pipeline) {
|
|
section(testCaseName);
|
|
subSection("Pipeline");
|
|
code(tojsononeline(pipeline));
|
|
|
|
// Append {$_internalInhibitOptimization: {}} to the front of the pipeline. This prevents
|
|
// pushdown into the find layer, which means that we can just print the pipeline (without
|
|
// $cursor) to the golden file.
|
|
pipeline.unshift({$_internalInhibitOptimization: {}});
|
|
|
|
// Print the results of the query to the golden file.
|
|
subSection("Results");
|
|
code(normalizeArray(coll.aggregate(pipeline).toArray()));
|
|
|
|
let explain = coll.explain("queryPlanner").aggregate(pipeline);
|
|
|
|
// Since we prevented pushdown into the find layer, we expect an array of pipeline stages to be
|
|
// present in the explain output.
|
|
assert(explain.hasOwnProperty("stages"), explain);
|
|
|
|
// Drop the first two stages, since we don't need to see the $cursor or $_inhibitOptimization
|
|
// in the golden output.
|
|
let stages = explain.stages;
|
|
assert.gte(stages.length, 3, explain);
|
|
stages = stages.slice(2);
|
|
subSection("Explain");
|
|
code(tojson(stages));
|
|
linebreak();
|
|
}
|
|
|
|
let testCaseName = "Basic inclusion projection";
|
|
let pipeline = [{$project: {_id: 1, a: "$b.c", z: 1}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Basic inclusion projection with excluded _id (variation 1)";
|
|
pipeline = [{$project: {_id: 0, a: "$b.c", z: 1}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Basic inclusion projection with excluded _id (variation 2)";
|
|
pipeline = [{$project: {_id: 0, a: "$b.c"}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Exclusion projection followed by inclusion projection";
|
|
pipeline = [{$project: {_id: 0, z: 0}}, {$project: {a: "$b.c"}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Basic $addFields";
|
|
pipeline = [{$addFields: {a: "$b.c"}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Basic $set";
|
|
pipeline = [{$set: {a: "$b.c"}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Inclusion projection with a match on a subpath of the renamed path (variation 1)";
|
|
pipeline = [{$project: {_id: 1, a: "$b.c", z: 1}}, {$match: {"a.e": {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Inclusion projection with a match on a subpath of the renamed path (variation 2)";
|
|
pipeline = [{$project: {_id: 0, a: "$b.c", z: 1}}, {$match: {"a.e": {$gte: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Inclusion projection with a match on a subpath of the renamed path (variation 3)";
|
|
pipeline = [{$project: {_id: 0, a: "$b.c"}}, {$match: {"a.e": {$type: "number"}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Exclusion/inclusion projection with a match on a subpath of the renamed path";
|
|
pipeline = [{$project: {_id: 0, z: 0}}, {$project: {a: "$b.c"}}, {$match: {"a.e": {$mod: [7, 0]}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "$addFields with a match on a subpath of the renamed path";
|
|
pipeline = [{$addFields: {a: "$b.c"}}, {$match: {"a.e": {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "$set with a match on a subpath of the renamed path";
|
|
pipeline = [{$set: {a: "$b.c"}}, {$match: {"a.e": {$lte: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Chain of complex renames";
|
|
pipeline = [
|
|
{$project: {_id: 0, n: "$b.c"}},
|
|
{$addFields: {q: "$n.f"}},
|
|
{$set: {r: "$q.g"}},
|
|
{$match: {r: {$eq: 9}}},
|
|
];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Multiple complex renames";
|
|
pipeline = [{$project: {n: "$b.c", q: "$h.i"}}, {$match: {$or: [{n: {$gt: 15}}, {q: {$lt: 13}}]}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Multiple complex renames as successive pipeline stages";
|
|
pipeline = [
|
|
{$project: {n: "$b.c", h: 1}},
|
|
{$addFields: {q: "$h.i"}},
|
|
{$project: {h: 0}},
|
|
{$match: {$or: [{n: {$gt: 15}}, {q: {$lt: 13}}]}},
|
|
];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "$match swaps past rename due to group";
|
|
pipeline = [{$group: {_id: {z: "$z"}}}, {$match: {"_id.z": {$lte: 14}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
// Here is a case that demonstrates one danger of pushing $match past a complex rename. Even
|
|
// when the data doesn't have arrays, the pipeline itself can introduce arrays.
|
|
testCaseName = "$match swaps past rename in the presence of arrays created by the pipeline";
|
|
pipeline = [
|
|
{$lookup: {from: "complex_match_swap", pipeline: [{$group: {_id: "$a", b: {$push: "$b"}}}], as: "arr"}},
|
|
{$project: {c: "$arr.b"}},
|
|
{$match: {c: {$eq: {}}}},
|
|
];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "$match with $exists swaps past rename";
|
|
pipeline = [{$project: {_id: 0, a: "$b.c", z: 1}}, {$match: {a: {$exists: true}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "$match with $expr swaps past rename";
|
|
pipeline = [{$project: {_id: 0, a: "$b.c", z: 1}}, {$match: {$expr: {$eq: ["$a", 42]}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "$match can be pushed beneath $replaceRoot";
|
|
pipeline = [{$replaceRoot: {newRoot: "$b"}}, {$match: {c: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "$match can be pushed beneath $replaceWith";
|
|
pipeline = [{$replaceWith: "$b"}, {$match: {c: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
//
|
|
// The remaining test cases are negative tests, meaning that we do not expect the $match to be
|
|
// pushed down.
|
|
//
|
|
testCaseName = "Negative case: Dotted path on the left and the right";
|
|
pipeline = [{$project: {_id: 0, "x.y": "$b.c", z: 1}}, {$match: {"x.y": {$lte: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: Dotted path on the left and the right with match on a subpath of the renamed path";
|
|
pipeline = [{$project: {_id: 0, "x.y": "$b.c", z: 1}}, {$match: {"x.y.e": {$lte: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: Dotted path of length 3 on the left";
|
|
pipeline = [{$project: {_id: 0, "n.q.r": "$b.c", z: 1}}, {$match: {"n.q.r.e": {$lte: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: Dotted path of length 3 on the left, expressed with nested objects";
|
|
pipeline = [{$project: {_id: 0, n: {q: {r: "$b.c"}}, z: 1}}, {$match: {"n.q.r.e": {$lte: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: Dotted path of length 3 on the left, expressed with nested objects and $addFields";
|
|
pipeline = [{$addFields: {n: {q: {r: "$b.c"}}}}, {$match: {"n.q.r.e": {$lte: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: conditional projection";
|
|
pipeline = [
|
|
{$project: {a: {$cond: {if: {$eq: [null, "$b.c"]}, then: "$$REMOVE", else: "$b.c"}}}},
|
|
{$match: {a: {$eq: 42}}},
|
|
];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: field path of length 3";
|
|
pipeline = [{$project: {_id: 1, a: "$b.c.e", z: 1}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: field path of length 3 with _id excluded (variation 1)";
|
|
pipeline = [{$project: {_id: 0, a: "$b.c.e", z: 1}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: field path of length 3 with _id excluded (variation 2)";
|
|
pipeline = [{$project: {_id: 0, a: "$b.c.e"}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: $addFields with field path of length 3";
|
|
pipeline = [{$addFields: {a: "$b.c.e"}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: $set with field path of length 3";
|
|
pipeline = [{$set: {a: "$b.c.e"}}, {$match: {a: {$eq: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: field path of length 4";
|
|
pipeline = [{$project: {a: "$b.c.f.g", z: 1}}, {$match: {a: {$eq: 9}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
testCaseName = "Negative case: $match cannot swap past complex rename when matching on subfield of $group key";
|
|
pipeline = [{$group: {_id: {x: "$b.c"}}}, {$match: {"_id.x.e": {$lte: 42}}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
|
|
// The dotted path on the left makes it so that "a" is always an object after the $addFields,
|
|
// which impacts the results of the $match stage.
|
|
testCaseName = "Negative case: dotted path on the left followed by equals-null $match";
|
|
pipeline = [{$addFields: {"a.d": "$c"}}, {$match: {"a.e": null}}];
|
|
runPipeline(testCaseName, pipeline);
|
|
} finally {
|
|
// Reset the parameter to its default value.
|
|
assert.commandWorked(db.adminCommand({setParameter: 1, internalQueryPermitMatchSwappingForComplexRenames: false}));
|
|
}
|