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
|
/**
* Test that hidden indexes work as expected when corresponding index filters are applied.
*
* - When a query with a shape matching an index filter is executed, the index referenced by
* the filter is *not* used to answer the query if that index is currently hidden.
* - If an alternative non-hidden index in the index filter is available, it is used to answer the
* query. Otherwise, it results in a COLLSCAN.
* - Un-hiding the index restores the index filter behaviour.
* - It is legal to set an index filter on a hidden index, but the index will not actually be
* used until it is made visible.
*
* @tags: [
* # Command 'planCacheSetFilter' may return different values after a failover.
* does_not_support_stepdowns,
* sbe_incompatible,
* ]
*/
(function() {
"use strict";
load("jstests/libs/analyze_plan.js"); // For 'getPlanStages' and 'isCollscan'.
const collName = 'hidden_indexes_remain_visible_in_index_filters';
db[collName].drop();
const coll = db[collName];
assert.commandWorked(coll.insert([{a: 1, b: 1, c: 1}, {a: 2, b: 2, c: 2}]));
assert.commandWorked(coll.createIndex({a: 1}));
assert.commandWorked(coll.createIndex({a: 1, b: 1}));
assert.commandWorked(coll.createIndex({a: 1, b: 1, c: 1}));
const queryShape = {
query: {a: {$gt: 0}, b: {$gt: 0}},
sort: {a: -1, b: -1},
projection: {_id: 0, a: 1}
};
// Ensure the filters for the given query shape exist.
function ensureFilterExistsByQueryShape(queryShape) {
const res = assert.commandWorked(coll.runCommand('planCacheListFilters'));
assert(res.hasOwnProperty('filters'), 'filters missing from planCacheListFilters result');
const filter = res.filters.find(function(obj) {
return tojson(obj.query) === tojson(queryShape.query) &&
tojson(obj.projection) === tojson(queryShape.projection) &&
tojson(obj.sort) === tojson(queryShape.sort);
});
assert(filter, `Index filter not found for query shape ${tojson(queryShape)}`);
}
// If non-null 'idxName' is given, the given index 'idxName' is expected to be used for the given
// 'queryShape'. Otherwise, a COLLSCAN stage is expected.
function validateIxscanOrCollscanUsed(queryShape, idxName) {
const explain = assert.commandWorked(
coll.find(queryShape.query, queryShape.projection).sort(queryShape.sort).explain());
if (idxName) {
// Expect the given index was used.
const ixScanStage = getPlanStages(explain.queryPlanner.winningPlan, "IXSCAN")[0];
assert(ixScanStage, `Index '${idxName}' was not used.`);
assert.eq(ixScanStage.indexName, idxName, `Index '${idxName}' was not used.`);
} else {
// Expect a COLLSCAN stage.
assert(isCollscan(db, explain));
}
}
// Add index filters for simple query.
assert.commandWorked(coll.runCommand('planCacheSetFilter', {
query: queryShape.query,
sort: queryShape.sort,
projection: queryShape.projection,
indexes: [{a: 1}, {a: 1, b: 1}]
}));
ensureFilterExistsByQueryShape(queryShape);
// The index should be used as usual if it's not hidden.
validateIxscanOrCollscanUsed(queryShape, "a_1_b_1");
// Hide index 'a_1_b_1'. Expect the other unhidden index 'a_1' will be used.
assert.commandWorked(coll.hideIndex("a_1_b_1"));
validateIxscanOrCollscanUsed(queryShape, "a_1");
// Hide index 'a_1' as well, at which point there are no available indexes remaining in the index
// filter. We do not expect the planner to use the 'a_1_b_1_c_1' index since it is outside the
// filter, so we should see a COLLSCAN instead.
assert.commandWorked(coll.hideIndex("a_1"));
validateIxscanOrCollscanUsed(queryShape, null);
// Ensure the index filters in the plan cache won't be affected by hiding the corresponding indexes.
ensureFilterExistsByQueryShape(queryShape);
// Ensure that unhiding the indexes can restore the index filter behaviour.
assert.commandWorked(coll.unhideIndex("a_1"));
validateIxscanOrCollscanUsed(queryShape, "a_1");
assert.commandWorked(coll.unhideIndex("a_1_b_1"));
validateIxscanOrCollscanUsed(queryShape, "a_1_b_1");
// Ensure that it is legal to set an index filter on a hidden index, but the index will not actually
// be used until it is made visible.
assert.commandWorked(coll.hideIndex("a_1"));
// Set index filters on a hidden index.
assert.commandWorked(coll.runCommand('planCacheSetFilter', {
query: queryShape.query,
sort: queryShape.sort,
projection: queryShape.projection,
indexes: [{a: 1}]
}));
ensureFilterExistsByQueryShape(queryShape);
// The hidden index 'a_1' cannot be used even though it's in the index filter.
validateIxscanOrCollscanUsed(queryShape, null);
// Unhiding the index should make it able to be used.
assert.commandWorked(coll.unhideIndex("a_1"));
validateIxscanOrCollscanUsed(queryShape, "a_1");
})();
|