summaryrefslogtreecommitdiff
path: root/jstests/core/index_partial_read_ops.js
blob: a88448d750f0f39ab2f002089996d6a9f00683a0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Cannot implicitly shard accessed collections because the explain output from a mongod when run
// against a sharded collection is wrapped in a "shards" object with keys for each shard.
// @tags: [assumes_unsharded_collection, does_not_support_stepdowns, requires_fcv_46]

// Read ops tests for partial indexes.

// Include helpers for analyzing explain output.
load("jstests/libs/analyze_plan.js");

(function() {
"use strict";
var explain;
var coll = db.index_partial_read_ops;
coll.drop();

assert.commandWorked(coll.createIndex({x: 1}, {partialFilterExpression: {a: {$lte: 1.5}}}));
assert.commandWorked(coll.insert({x: 5, a: 2}));  // Not in index.
assert.commandWorked(coll.insert({x: 6, a: 1}));  // In index.

//
// Verify basic functionality with find().
//

// find() operations that should use index.
explain = coll.explain('executionStats').find({x: 6, a: 1}).finish();
assert.eq(1, explain.executionStats.nReturned);
assert(isIxscan(db, explain.queryPlanner.winningPlan));
explain = coll.explain('executionStats').find({x: {$gt: 1}, a: 1}).finish();
assert.eq(1, explain.executionStats.nReturned);
assert(isIxscan(db, explain.queryPlanner.winningPlan));
explain = coll.explain('executionStats').find({x: 6, a: {$lte: 1}}).finish();
assert.eq(1, explain.executionStats.nReturned);
assert(isIxscan(db, explain.queryPlanner.winningPlan));

// find() operations that should not use index.
explain = coll.explain('executionStats').find({x: 6, a: {$lt: 1.6}}).finish();
assert.eq(1, explain.executionStats.nReturned);
assert(isCollscan(db, explain.queryPlanner.winningPlan));
explain = coll.explain('executionStats').find({x: 6}).finish();
assert.eq(1, explain.executionStats.nReturned);
assert(isCollscan(db, explain.queryPlanner.winningPlan));

//
// Verify basic functionality with the count command.
//

// Count operation that should use index.
explain = coll.explain('executionStats').count({x: {$gt: 1}, a: 1});
assert(isIxscan(db, explain.queryPlanner.winningPlan));

// Count operation that should not use index.
explain = coll.explain('executionStats').count({x: {$gt: 1}, a: 2});
assert(isCollscan(db, explain.queryPlanner.winningPlan));

//
// Verify basic functionality with the aggregate command.
//

// Aggregate operation that should use index.
explain = coll.aggregate([{$match: {x: {$gt: 1}, a: 1}}], {explain: true});
assert(isIxscan(db, explain.queryPlanner.winningPlan));

// Aggregate operation that should not use index.
explain = coll.aggregate([{$match: {x: {$gt: 1}, a: 2}}], {explain: true});
assert(isCollscan(db, explain.queryPlanner.winningPlan));

//
// Verify basic functionality with the findAndModify command.
//

// findAndModify operation that should use index.
explain = coll.explain('executionStats')
              .findAndModify({query: {x: {$gt: 1}, a: 1}, update: {$inc: {x: 1}}});
assert.eq(1, explain.executionStats.nReturned);
assert(isIxscan(db, explain.queryPlanner.winningPlan));

// findAndModify operation that should not use index.
explain = coll.explain('executionStats')
              .findAndModify({query: {x: {$gt: 1}, a: 2}, update: {$inc: {x: 1}}});
assert.eq(1, explain.executionStats.nReturned);
assert(isCollscan(db, explain.queryPlanner.winningPlan));

//
// Verify functionality with multiple overlapping partial indexes on the same key pattern.
//

// Remove existing indexes and documents.
assert.commandWorked(coll.dropIndexes());
assert.commandWorked(coll.remove({}));

assert.commandWorked(
    coll.createIndex({a: 1}, {name: "index1", partialFilterExpression: {a: {$gte: 0}}}));
assert.commandWorked(
    coll.createIndex({a: 1}, {name: "index2", partialFilterExpression: {a: {$gte: 10}}}));
assert.commandWorked(
    coll.createIndex({a: 1}, {name: "index3", partialFilterExpression: {a: {$gte: 100}}}));

assert.commandWorked(coll.insert([{a: 1}, {a: 2}, {a: 3}]));
assert.commandWorked(coll.insert([{a: 11}, {a: 12}, {a: 13}]));
assert.commandWorked(coll.insert([{a: 101}, {a: 102}, {a: 103}]));

// Function which verifies that the given query is indexed, that it produces the same output as a
// COLLSCAN and the given 'expectedResults' array, and that 'numAlternativePlans' were generated.
function assertIndexedQueryAndResults(query, numAlternativePlans, expectedResults) {
    const explainOut = coll.explain().find(query).finish();
    const results = coll.find(query).toArray();
    assert(isIxscan(db, explainOut), tojson(explainOut));
    assert.eq(getRejectedPlans(explainOut).length, numAlternativePlans, tojson(explainOut));
    assert.sameMembers(results, coll.find(query).hint({$natural: 1}).toArray());
    assert.sameMembers(results.map(doc => (delete doc._id && doc)), expectedResults);
}

// Queries which fall within the covered ranges generate plans for all applicable partial indexes.
assertIndexedQueryAndResults({a: {$gt: 0, $lt: 10}}, 0, [{a: 1}, {a: 2}, {a: 3}]);
assertIndexedQueryAndResults({a: {$gt: 10, $lt: 100}}, 1, [{a: 11}, {a: 12}, {a: 13}]);
assertIndexedQueryAndResults({a: {$gt: 100, $lt: 1000}}, 2, [{a: 101}, {a: 102}, {a: 103}]);
assertIndexedQueryAndResults(
    {a: {$gt: 0}},
    0,
    [{a: 1}, {a: 2}, {a: 3}, {a: 11}, {a: 12}, {a: 13}, {a: 101}, {a: 102}, {a: 103}]);

// Queries which fall outside the range of any partial indexes produce a COLLSCAN.
assert(isCollscan(db, coll.explain().find({a: {$lt: 0}}).finish()));
})();