mirror of https://github.com/mongodb/mongo
142 lines
5.1 KiB
JavaScript
142 lines
5.1 KiB
JavaScript
/**
|
|
* Test that queries containing $elemMatch correctly use an index if each child expression is
|
|
* compatible with the index.
|
|
* @tags: [
|
|
* assumes_balancer_off,
|
|
* assumes_read_concern_local,
|
|
* requires_getmore,
|
|
* ]
|
|
*/
|
|
import {getWinningPlanFromExplain, isIxscan} from "jstests/libs/query/analyze_plan.js";
|
|
|
|
const coll = db.elemMatch_index;
|
|
coll.drop();
|
|
|
|
assert.commandWorked(coll.insert({a: 1}));
|
|
assert.commandWorked(coll.insert({a: [{}]}));
|
|
assert.commandWorked(coll.insert({a: [1, null]}));
|
|
assert.commandWorked(coll.insert({a: [{type: "Point", coordinates: [0, 0]}]}));
|
|
|
|
assert.commandWorked(coll.createIndex({a: 1}, {sparse: true}));
|
|
|
|
function assertIndexResults(coll, query, useIndex, nReturned) {
|
|
const explainPlan = coll.find(query).explain("executionStats");
|
|
// Assert the plan is using an index scan.
|
|
assert.eq(isIxscan(db, getWinningPlanFromExplain(explainPlan)), useIndex);
|
|
assert.eq(explainPlan.executionStats.nReturned, nReturned);
|
|
}
|
|
|
|
assertIndexResults(coll, {a: {$elemMatch: {$exists: false}}}, false, 0);
|
|
|
|
// An $elemMatch predicate is treated as nested, and the index should be used for $exists:true.
|
|
assertIndexResults(coll, {a: {$elemMatch: {$exists: true}}}, true, 3);
|
|
|
|
// $not within $elemMatch should not attempt to use a sparse index for $exists:false.
|
|
assertIndexResults(coll, {a: {$elemMatch: {$not: {$exists: false}}}}, false, 3);
|
|
assertIndexResults(coll, {a: {$elemMatch: {$gt: 0, $not: {$exists: false}}}}, false, 1);
|
|
|
|
// $geo within $elemMatch should not attempt to use a non-geo index.
|
|
assertIndexResults(
|
|
coll,
|
|
{
|
|
a: {
|
|
$elemMatch: {
|
|
$geoWithin: {
|
|
$geometry: {
|
|
type: "Polygon",
|
|
coordinates: [
|
|
[
|
|
[0, 0],
|
|
[0, 1],
|
|
[1, 0],
|
|
[0, 0],
|
|
],
|
|
],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
false,
|
|
1,
|
|
);
|
|
|
|
// $in with a null value within $elemMatch should use a sparse index.
|
|
assertIndexResults(coll, {a: {$elemMatch: {$in: [null]}}}, true, 1);
|
|
|
|
// $eq with a null value within $elemMatch should use a sparse index.
|
|
assertIndexResults(coll, {a: {$elemMatch: {$eq: null}}}, true, 1);
|
|
|
|
// A negated regex within $elemMatch should not use an index, sparse or not.
|
|
assertIndexResults(coll, {a: {$elemMatch: {$not: {$in: [/^a/]}}}}, false, 3);
|
|
|
|
coll.dropIndexes();
|
|
assert.commandWorked(coll.createIndex({a: 1}));
|
|
assertIndexResults(coll, {a: {$elemMatch: {$not: {$in: [/^a/]}}}}, false, 3);
|
|
|
|
(function () {
|
|
assert(coll.drop());
|
|
assert.commandWorked(coll.insert({a: [{b: {c: "x"}}]}));
|
|
assert.commandWorked(coll.createIndex({"a.b.c": 1}));
|
|
|
|
// Tests $elemMatch with path components that are empty strings. The system should not attempt to
|
|
// use the index for these queries.
|
|
assertIndexResults(coll, {"": {$elemMatch: {"a.b.c": "x"}}}, false, 0);
|
|
assertIndexResults(coll, {"": {$all: [{$elemMatch: {"a.b.c": "x"}}]}}, false, 0);
|
|
assertIndexResults(coll, {a: {$elemMatch: {"": {$elemMatch: {"b.c": "x"}}}}}, false, 0);
|
|
|
|
// Tests $elemMatch with supporting index and no path components that are empty strings.
|
|
assertIndexResults(coll, {a: {$elemMatch: {"b.c": "x"}}}, true, 1);
|
|
assertIndexResults(coll, {a: {$all: [{$elemMatch: {"b.c": "x"}}]}}, true, 1);
|
|
|
|
// Tests that $elemMatch with path components that are empty strings are correctly handled in the
|
|
// plan enumerator in case if an index is used on another predicate of the query.
|
|
assertIndexResults(coll, {$and: [{"a.b.c": "x"}, {"": {$elemMatch: {"a.b.c": "x"}}}]}, true, 0);
|
|
assertIndexResults(coll, {$and: [{"a.b.c": "x"}, {"a.b.c": {$elemMatch: {"": "x"}}}]}, true, 0);
|
|
|
|
// Similar to the above but with the $elemMatch placed under a contained $or.
|
|
assertIndexResults(
|
|
coll,
|
|
{$and: [{"a.b.c": "x"}, {$or: [{"a.b.c": "y"}, {"": {$elemMatch: {"a.b.c": "x"}}}]}]},
|
|
true,
|
|
0,
|
|
);
|
|
assertIndexResults(
|
|
coll,
|
|
{$and: [{"a.b.c": "x"}, {$or: [{"a.b.c": "y"}, {"a.b.c": {$elemMatch: {"": "x"}}}]}]},
|
|
true,
|
|
0,
|
|
);
|
|
})();
|
|
|
|
(function () {
|
|
const coll = db.index_elemmatch1;
|
|
coll.drop();
|
|
|
|
let x = 0;
|
|
let y = 0;
|
|
const bulk = coll.initializeUnorderedBulkOp();
|
|
for (let a = 0; a < 10; a++) {
|
|
for (let b = 0; b < 10; b++) {
|
|
bulk.insert({a: a, b: b % 10, arr: [{x: x++ % 10, y: y++ % 10}]});
|
|
}
|
|
}
|
|
assert.commandWorked(bulk.execute());
|
|
|
|
assert.commandWorked(coll.createIndex({a: 1, b: 1}));
|
|
assert.commandWorked(coll.createIndex({"arr.x": 1, a: 1}));
|
|
|
|
const query = {
|
|
a: 5,
|
|
b: {$in: [1, 3, 5]},
|
|
arr: {$elemMatch: {x: 5, y: 5}},
|
|
};
|
|
|
|
const count = coll.find(query).itcount();
|
|
assert.eq(count, 1);
|
|
|
|
const explain = coll.find(query).hint({"arr.x": 1, a: 1}).explain("executionStats");
|
|
assert.commandWorked(explain);
|
|
assert.eq(count, explain.executionStats.totalKeysExamined, explain);
|
|
})();
|