summaryrefslogtreecommitdiff
path: root/jstests/core/index_filter_catalog_independent.js
blob: 48889c7e41459cbc2d50c0c9b1296ddb9c74442a (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
/**
 * Test that index filters are applied regardless of catalog changes. Intended to reproduce
 * SERVER-33303.
 *
 * @tags: [
 *   # This test performs queries with index filters set up. Since index filters are local to a
 *   # mongod, and do not replicate, this test must issue all of its commands against the same node.
 *   assumes_read_preference_unchanged,
 *   does_not_support_stepdowns,
 *   tenant_migration_incompatible,
 * ]
 */
(function() {
"use strict";

load("jstests/libs/analyze_plan.js");  // For getPlanStages.

const collName = "index_filter_catalog_independent";
const coll = db[collName];
coll.drop();

/*
 * Check that there's one index filter on the given query which allows only 'indexes'.
 */
function assertOneIndexFilter(query, indexes) {
    let res = assert.commandWorked(db.runCommand({planCacheListFilters: collName}));
    assert.eq(res.filters.length, 1);
    assert.eq(res.filters[0].query, query);
    assert.eq(res.filters[0].indexes, indexes);
}

function assertIsIxScanOnIndex(winningPlan, keyPattern) {
    const ixScans = getPlanStages(winningPlan, "IXSCAN");
    assert.gt(ixScans.length, 0);
    ixScans.every((ixScan) => assert.eq(ixScan.keyPattern, keyPattern));

    const collScans = getPlanStages(winningPlan, "COLLSCAN");
    assert.eq(collScans.length, 0);
}

function checkIndexFilterSet(explain, shouldBeSet) {
    if (explain.queryPlanner.winningPlan.shards) {
        for (let shard of explain.queryPlanner.winningPlan.shards) {
            assert.eq(shard.indexFilterSet, shouldBeSet);
        }
    } else {
        assert.eq(explain.queryPlanner.indexFilterSet, shouldBeSet);
    }
}

assert.commandWorked(coll.createIndexes([{x: 1}, {x: 1, y: 1}]));
assert.commandWorked(
    db.runCommand({planCacheSetFilter: collName, query: {"x": 3}, indexes: [{x: 1, y: 1}]}));
assertOneIndexFilter({x: 3}, [{x: 1, y: 1}]);

let explain = assert.commandWorked(coll.find({x: 3}).explain());
checkIndexFilterSet(explain, true);
assertIsIxScanOnIndex(getWinningPlan(explain.queryPlanner), {x: 1, y: 1});

// Drop an index. The filter should not change.
assert.commandWorked(coll.dropIndex({x: 1, y: 1}));
assertOneIndexFilter({x: 3}, [{x: 1, y: 1}]);

// The {x: 1} index _could_ be used, but should not be considered because of the filter.
// Since we dropped the {x: 1, y: 1} index, a COLLSCAN must be used.
explain = coll.find({x: 3}).explain();
checkIndexFilterSet(explain, true);
assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));

// Create another index. This should not change whether the index filter is applied.
assert.commandWorked(coll.createIndex({x: 1, z: 1}));
explain = assert.commandWorked(coll.find({x: 3}).explain());
checkIndexFilterSet(explain, true);
assert(isCollscan(db, getWinningPlan(explain.queryPlanner)));

// Changing the catalog and then setting an index filter should not result in duplicate entries.
assert.commandWorked(coll.createIndex({x: 1, a: 1}));
assert.commandWorked(
    db.runCommand({planCacheSetFilter: collName, query: {"x": 3}, indexes: [{x: 1, y: 1}]}));
assertOneIndexFilter({x: 3}, [{x: 1, y: 1}]);

// Recreate the {x: 1, y: 1} index and be sure that it's still used.
assert.commandWorked(coll.createIndexes([{x: 1}, {x: 1, y: 1}]));
assertOneIndexFilter({x: 3}, [{x: 1, y: 1}]);

explain = assert.commandWorked(coll.find({x: 3}).explain());
checkIndexFilterSet(explain, true);
assertIsIxScanOnIndex(getWinningPlan(explain.queryPlanner), {x: 1, y: 1});
})();