Files
mongo/jstests/aggregation/sources/match/expr_index_use.js

164 lines
6.8 KiB
JavaScript

// Confirms expected index use when performing a match with a $expr statement.
(function() {
"use strict";
load("jstests/libs/analyze_plan.js");
const coll = db.expr_index_use;
coll.drop();
assert.writeOK(coll.insert({x: 0}));
assert.writeOK(coll.insert({x: 1, y: 1}));
assert.writeOK(coll.insert({x: 2, y: 2}));
assert.writeOK(coll.insert({x: 3, y: 10}));
assert.writeOK(coll.insert({y: 20}));
assert.writeOK(coll.insert({a: {b: 1}}));
assert.writeOK(coll.insert({a: {b: [1]}}));
assert.writeOK(coll.insert({a: [{b: 1}]}));
assert.writeOK(coll.insert({a: [{b: [1]}]}));
assert.commandWorked(coll.createIndex({x: 1, y: 1}));
assert.commandWorked(coll.createIndex({y: 1}));
assert.commandWorked(coll.createIndex({"a.b": 1}));
/**
* Executes an 'executionStats' explain on pipeline and confirms 'metricsToCheck' which is an
* object containing:
* - nReturned: The number of documents the pipeline is expected to return.
* - expectedIndex: Either an index specification object when index use is expected or
* 'null' if a collection scan is expected.
* - indexBounds: The expected index bounds.
*/
function confirmExpectedPipelineExecution(pipeline, metricsToCheck) {
assert(metricsToCheck.hasOwnProperty("nReturned"),
"metricsToCheck must contain an nReturned field");
assert.eq(metricsToCheck.nReturned, coll.aggregate(pipeline).itcount());
const explain = assert.commandWorked(coll.explain("executionStats").aggregate(pipeline));
if (metricsToCheck.hasOwnProperty("expectedIndex")) {
const stage = getAggPlanStage(explain, "IXSCAN");
assert.neq(null, stage, tojson(explain));
assert(stage.hasOwnProperty("keyPattern"), tojson(explain));
assert.docEq(stage.keyPattern, metricsToCheck.expectedIndex, tojson(explain));
} else {
assert.neq(null, aggPlanHasStage(explain, "COLLSCAN"), tojson(explain));
}
if (metricsToCheck.hasOwnProperty("indexBounds")) {
const stage = getAggPlanStage(explain, "IXSCAN");
assert.neq(null, stage, tojson(explain));
assert(stage.hasOwnProperty("indexBounds"), tojson(explain));
assert.docEq(stage.indexBounds, metricsToCheck.indexBounds, tojson(explain));
}
}
// Comparison of field and constant.
confirmExpectedPipelineExecution([{$match: {$expr: {$eq: ["$x", 1]}}}], {
nReturned: 1,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["[1.0, 1.0]"], "y": ["[MinKey, MaxKey]"]}
});
confirmExpectedPipelineExecution([{$match: {$expr: {$eq: [1, "$x"]}}}], {
nReturned: 1,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["[1.0, 1.0]"], "y": ["[MinKey, MaxKey]"]}
});
confirmExpectedPipelineExecution([{$match: {$expr: {$lt: ["$x", 1]}}}], {
nReturned: 1,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["[-inf.0, 1.0)"], "y": ["[MinKey, MaxKey]"]}
});
confirmExpectedPipelineExecution([{$match: {$expr: {$lt: [1, "$x"]}}}], {
nReturned: 2,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["(1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
});
confirmExpectedPipelineExecution([{$match: {$expr: {$lte: ["$x", 1]}}}], {
nReturned: 2,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["[-inf.0, 1.0]"], "y": ["[MinKey, MaxKey]"]}
});
confirmExpectedPipelineExecution([{$match: {$expr: {$lte: [1, "$x"]}}}], {
nReturned: 3,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["[1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
});
confirmExpectedPipelineExecution([{$match: {$expr: {$gt: ["$x", 1]}}}], {
nReturned: 2,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["(1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
});
confirmExpectedPipelineExecution([{$match: {$expr: {$gt: [1, "$x"]}}}], {
nReturned: 1,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["[-inf.0, 1.0)"], "y": ["[MinKey, MaxKey]"]}
});
confirmExpectedPipelineExecution([{$match: {$expr: {$gte: ["$x", 1]}}}], {
nReturned: 3,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["[1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
});
confirmExpectedPipelineExecution([{$match: {$expr: {$gte: [1, "$x"]}}}], {
nReturned: 2,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["[-inf.0, 1.0]"], "y": ["[MinKey, MaxKey]"]}
});
// $in with field and array of values.
confirmExpectedPipelineExecution([{$match: {$expr: {$in: ["$x", [1, 3]]}}}], {
nReturned: 2,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["[1.0, 1.0]", "[3.0, 3.0]"], "y": ["[MinKey, MaxKey]"]}
});
// $and with both children eligible for index use.
confirmExpectedPipelineExecution(
[{$match: {$expr: {$and: [{$eq: ["$x", 2]}, {$gt: ["$y", 1]}]}}}], {
nReturned: 1,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["[2.0, 2.0]"], "y": ["(1.0, inf.0]"]}
});
// $and with one child eligible for index use and one that is not.
confirmExpectedPipelineExecution(
[{$match: {$expr: {$and: [{$gt: ["$x", 1]}, {$eq: ["$x", "$y"]}]}}}], {
nReturned: 1,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["(1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
});
// $and with one child elibible for index use and a second child containing a $or where one of
// the two children are eligible.
confirmExpectedPipelineExecution(
[
{
$match: {
$expr:
{$and: [{$gt: ["$x", 1]}, {$or: [{$eq: ["$x", "$y"]}, {$gt: ["$y", 1]}]}]}
}
}
],
{
nReturned: 2,
expectedIndex: {x: 1, y: 1},
indexBounds: {"x": ["(1.0, inf.0]"], "y": ["[MinKey, MaxKey]"]}
});
// $cmp is not expected to use an index.
confirmExpectedPipelineExecution([{$match: {$expr: {$cmp: ["$x", 1]}}}], {nReturned: 8});
// An constant expression is not expected to use an index.
confirmExpectedPipelineExecution([{$match: {$expr: 1}}], {nReturned: 9});
// Comparison of 2 fields is not expected to use an index.
confirmExpectedPipelineExecution([{$match: {$expr: {$eq: ["$x", "$y"]}}}], {nReturned: 6});
// Comparison with field path length > 1 is not expected to use an index.
confirmExpectedPipelineExecution([{$match: {$expr: {$eq: ["$a.b", 1]}}}], {nReturned: 1});
// $in with field path length > 1 is not expected to use an index.
confirmExpectedPipelineExecution([{$match: {$expr: {$in: ["$a.b", [1, 3]]}}}], {nReturned: 1});
})();