summaryrefslogtreecommitdiff
path: root/jstests/core/index_filter_commands_invalidate_plan_cache_entries.js
blob: 059241284accfe0b75288f380a762eef458e8e6c (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
/**
 * Test that index filter commands (planCacheSetFilter, planCacheClearFilters) invalidate the
 * corresponding plan cache entries.
 * The test runs commands that are not allowed with security token: planCacheClearFilters,
 * planCacheSetFilter.
 * @tags: [
 *   not_allowed_with_security_token,
 *   # This test attempts to perform queries with plan cache filters set up. The index filter
 *   # commands and the queries to which those index filters apply could be routed to different
 *   # nodes.
 *   assumes_read_preference_unchanged,
 *   assumes_read_concern_unchanged,
 *   assumes_unsharded_collection,
 *   does_not_support_stepdowns,
 *   requires_fcv_62,
 *   # Plan cache state is node-local and will not get migrated alongside tenant data.
 *   tenant_migration_incompatible,
 *   # TODO SERVER-67607: Test plan cache with CQF enabled.
 *   cqf_incompatible,
 * ]
 */
(function() {
"use strict";

load("jstests/libs/analyze_plan.js");  // For getPlanCacheKeyFromShape.
load("jstests/libs/sbe_util.js");      // For checkSBEEnabled.

// For testing convenience this variable is made an integer "1" if SBE is fully enabled, because the
// expected amount of plan cache entries differs between the SBE plan cache and the classic one.
const isSbeEnabled = checkSBEEnabled(db, ["featureFlagSbeFull"]) ? 1 : 0;

const collName = "index_filter_commands_invalidate_plan_cache_entries";
const coll = db[collName];

function initCollection(collection) {
    collection.drop();

    // We need multiple indexes so that the multi-planner is executed.
    assert.commandWorked(collection.createIndex({a: 1}));
    assert.commandWorked(collection.createIndex({b: 1}));
    assert.commandWorked(collection.createIndex({a: 1, b: 1}));
    assert.commandWorked(collection.insert({a: 1, b: 1, c: 1}));
}
initCollection(coll);

function existsInPlanCache(query, sort, projection, planCacheColl) {
    const keyHash = getPlanCacheKeyFromShape(
        {query: query, projection: projection, sort: sort, collection: planCacheColl, db: db});
    const res = planCacheColl.aggregate([{$planCacheStats: {}}, {$match: {planCacheKey: keyHash}}])
                    .toArray();

    return res.length > 0;
}

assert.eq(1, coll.find({a: 1}).itcount());
assert(existsInPlanCache({a: 1}, {}, {}, coll));
assert.eq(1, coll.find({a: 1, b: 1}).itcount());
assert(existsInPlanCache({a: 1, b: 1}, {}, {}, coll));
assert.eq(1, coll.find({a: 1, b: 1}).sort({a: 1}).itcount());
assert(existsInPlanCache({a: 1, b: 1}, {a: 1}, {}, coll));

assert.eq(coll.aggregate([{$planCacheStats: {}}]).toArray().length, 3);

// This query has same index filter key as the first query "{a: 1}" w/o skip. So when an index
// filter is set/cleared on query {a: 1}, the plan cache entry created for this query should also be
// invalidated.
assert.eq(0, coll.find({a: 1}).skip(1).itcount());

// SBE plan cache key encodes "skip", so there's one more plan cache entry in SBE plan cache. While
// in classic plan cache, queries with only difference in "skip" share the same plan cache entry.
assert.eq(coll.aggregate([{$planCacheStats: {}}]).itcount(), 3 + isSbeEnabled);

assert.commandWorked(
    db.runCommand({planCacheSetFilter: collName, query: {a: 1, b: 1}, indexes: [{a: 1}]}));
assert.eq(coll.aggregate([{$planCacheStats: {}}]).toArray().length, 2 + isSbeEnabled);

// This planCacheSetFilter command will invalidate plan cache entries with filter {a: 1}. There are
// two entries in the SBE plan cache that got invalidated, or one entry in the classic plan cache
// that got invalidated.
assert.commandWorked(
    db.runCommand({planCacheSetFilter: collName, query: {a: 1}, indexes: [{a: 1}]}));
assert.eq(coll.aggregate([{$planCacheStats: {}}]).toArray().length, 1);

// Test that plan cache entries with same query shape but in a different collection won't be cleared
// when an index filter with the same query shape is set/cleared.
const collNameOther = "index_filter_commands_invalidate_plan_cache_entries_other";
const collOther = db[collNameOther];

initCollection(coll);
initCollection(collOther);

assert.eq(1, coll.find({a: 1, b: 1}).itcount());
assert(existsInPlanCache({a: 1, b: 1}, {}, {}, coll));
assert.eq(1, collOther.find({a: 1, b: 1}).itcount());
assert(existsInPlanCache({a: 1, b: 1}, {}, {}, collOther));

// Test that planCacheClearFilters command invalidates corresponding plan cache entries of correct
// collection.
assert.commandWorked(db.runCommand({planCacheClearFilters: collName, query: {a: 1, b: 1}}));
assert(!existsInPlanCache({a: 1, b: 1}, {}, {}, coll));
assert(existsInPlanCache({a: 1, b: 1}, {}, {}, collOther));

// Test planCacheSetFilter command invalidates corresponding plan cache entries of correct
// collection.
assert.eq(1, coll.find({a: 1, b: 1}).itcount());
assert(existsInPlanCache({a: 1, b: 1}, {}, {}, coll));

assert.commandWorked(
    db.runCommand({planCacheSetFilter: collName, query: {a: 1, b: 1}, indexes: [{a: 1}]}));
assert(!existsInPlanCache({a: 1, b: 1}, {}, {}, coll));
assert(existsInPlanCache({a: 1, b: 1}, {}, {}, collOther));
})();