summaryrefslogtreecommitdiff
path: root/jstests/core/index_filter_on_hidden_index.js
blob: 0315bd7fad7d6736b3370cc3630542146909b64f (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
/**
 * 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,
 * ]
 */

(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");
})();