summaryrefslogtreecommitdiff
path: root/jstests/noPassthroughWithMongod/all_paths_cached_plans.js
blob: bd54cba889f0650a4c1427faa6ebd583119b8bab (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
125
126
127
128
129
130
131
132
133
134
135
136
/**
 * Test that cached plans which use allPaths indexes work.
 * TODO: SERVER-36198: Move this test back to jstests/core/
 */
(function() {
    "use strict";

    load('jstests/libs/analyze_plan.js');  // For getPlanStage().

    assert.commandWorked(
        db.adminCommand({setParameter: 1, internalQueryAllowAllPathsIndexes: true}));

    const coll = db.all_paths_cached_plans;
    coll.drop();
    assert.commandWorked(coll.createIndex({"b.$**": 1}));
    assert.commandWorked(coll.createIndex({"a": 1}));

    // In order for the plan cache to be used, there must be more than one plan available. Insert
    // data into the collection such that the b.$** index will be far more selective than the index
    // on 'a' for the query {a: 1, b: 1}.
    for (let i = 0; i < 1000; i++) {
        assert.commandWorked(coll.insert({a: 1}));
    }
    assert.commandWorked(coll.insert({a: 1, b: 1}));

    function getCacheEntryForQuery(query) {
        const aggRes =
            coll.aggregate([
                    {$planCacheStats: {}},
                    {$match: {createdFromQuery: {query: query, sort: {}, projection: {}}}}
                ])
                .toArray();
        assert.lte(aggRes.length, 1);
        if (aggRes.length > 0) {
            return aggRes[0];
        }
        return null;
    }

    function getQueryHash(query) {
        const explainRes = assert.commandWorked(coll.explain().find(query).finish());
        const hash = explainRes.queryPlanner.queryHash;
        assert.eq(typeof(hash), "string");
        return hash;
    }

    const query = {a: 1, b: 1};

    // The plan cache should be empty.
    assert.eq(getCacheEntryForQuery(query), null);

    // Run the query twice, once to create the cache entry, and again to make the cache entry
    // active.
    for (let i = 0; i < 2; i++) {
        assert.eq(coll.find(query).itcount(), 1);
    }

    // The plan cache should no longer be empty. Check that the chosen plan uses the b.$** index.
    const cacheEntry = getCacheEntryForQuery(query);
    assert.neq(cacheEntry, null);
    assert.eq(cacheEntry.isActive, true);
    // Should be at least two plans: one using the {a: 1} index and the other using the b.$** index.
    assert.gte(cacheEntry.creationExecStats.length, 2, tojson(cacheEntry.plans));
    const plan = cacheEntry.creationExecStats[0].executionStages;
    const ixScanStage = getPlanStage(plan, "IXSCAN");
    assert.neq(ixScanStage, null, () => tojson(plan));
    assert.eq(ixScanStage.keyPattern, {"$_path": 1, "b": 1}, () => tojson(plan));

    // Run the query again. This time it should use the cached plan. We should get the same result
    // as earlier.
    assert.eq(coll.find(query).itcount(), 1);

    // Now run a query where b is null. This should have a different shape key from the previous
    // query since $** indexes are sparse.
    const queryWithBNull = {a: 1, b: null};
    for (let i = 0; i < 2; i++) {
        assert.eq(coll.find({a: 1, b: null}).itcount(), 1000);
    }
    assert.neq(getQueryHash(queryWithBNull), getQueryHash(query));

    // There should only have been one solution for the above query, so it would not get cached.
    assert.eq(getCacheEntryForQuery({a: 1, b: null}), null);

    // Check that indexability discriminators work with collations.
    (function() {
        // Create allPaths index with a collation.
        assert.eq(coll.drop(), true);
        assert.commandWorked(
            db.createCollection(coll.getName(), {collation: {locale: "en_US", strength: 1}}));
        assert.commandWorked(coll.createIndex({"b.$**": 1}));

        // Run a query which uses a different collation from that of the index, but does not use
        // string bounds.
        const queryWithoutStringExplain =
            coll.explain().find({a: 5, b: 5}).collation({locale: "fr"}).finish();
        let ixScans = getPlanStages(queryWithoutStringExplain.queryPlanner.winningPlan, "IXSCAN");
        assert.eq(ixScans.length, 1);
        assert.eq(ixScans[0].keyPattern, {$_path: 1, b: 1});

        // Run a query which uses a different collation from that of the index and does have string
        // bounds.
        const queryWithStringExplain =
            coll.explain().find({a: 5, b: "a string"}).collation({locale: "fr"}).finish();
        ixScans = getPlanStages(queryWithStringExplain.queryPlanner.winningPlan, "IXSCAN");
        assert.eq(ixScans.length, 0);

        // Check that the shapes are different since the query which matches on a string will not
        // be eligible to use the b.$** index (since the index has a different collation).
        assert.neq(queryWithoutStringExplain.queryPlanner.queryHash,
                   queryWithStringExplain.queryPlanner.queryHash);
    })();

    // Check that indexability discriminators work with partial allPaths indexes.
    (function() {
        assert.eq(coll.drop(), true);
        assert.commandWorked(db.createCollection(coll.getName()));
        assert.commandWorked(
            coll.createIndex({"$**": 1}, {partialFilterExpression: {a: {$lte: 5}}}));

        // Run a query for a value included by the partial filter expression.
        const queryIndexedExplain = coll.find({a: 4}).explain();
        let ixScans = getPlanStages(queryIndexedExplain.queryPlanner.winningPlan, "IXSCAN");
        assert.eq(ixScans.length, 1);
        assert.eq(ixScans[0].keyPattern, {$_path: 1, a: 1});

        // Run a query which tries to get a value not included by the partial filter expression.
        const queryUnindexedExplain = coll.find({a: 100}).explain();
        ixScans = getPlanStages(queryUnindexedExplain.queryPlanner.winningPlan, "IXSCAN");
        assert.eq(ixScans.length, 0);

        // Check that the shapes are different since the query which searches for a value not
        // included by the partial filter expression won't be eligible to use the $** index.
        assert.neq(queryIndexedExplain.queryPlanner.queryHash,
                   queryUnindexedExplain.queryPlanner.queryHash);
    })();
})();