summaryrefslogtreecommitdiff
path: root/jstests/core/index_filter_commands.js
blob: cec2437fff0e591705efd73d1980dfb3b5f6e22e (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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/**
 * Index Filter commands
 * 
 * Commands:
 * - planCacheListFilters
 *   Displays index filters for all query shapes in a collection.
 *
 * - planCacheClearFilters
 *   Clears index filter for a single query shape or,
 *   if the query shape is omitted, all filters for the collection.
 *
 * - planCacheSetFilter
 *   Sets index filter for a query shape. Overrides existing filter.
 *
 * Not a lot of data access in this test suite. Hint commands
 * manage a non-persistent mapping in the server of
 * query shape to list of index specs.
 *
 * Only time we might need to execute a query is to check the plan
 * cache state. We would do this with the planCacheListPlans command
 * on the same query shape with the index filters.
 *
 */ 

var t = db.jstests_index_filter_commands;

t.drop();

t.save({a: 1});

// Add 2 indexes.
// 1st index is more efficient.
// 2nd and 3rd indexes will be used to test index filters.
var indexA1 = {a: 1};
var indexA1B1 = {a: 1, b: 1};
var indexA1C1 = {a: 1, c: 1};
t.ensureIndex(indexA1);
t.ensureIndex(indexA1B1);
t.ensureIndex(indexA1C1);

var queryA1 = {a: 1};
var projectionA1 = {_id: 0, a: 1};
var sortA1 = {a: -1};

//
// Tests for planCacheListFilters, planCacheClearFilters, planCacheSetFilter
//

// Utility function to list index filters.
function getFilters() {
    var res = t.runCommand('planCacheListFilters');
    print('planCacheListFilters() = ' + tojson(res));
    assert.commandWorked(res, 'planCacheListFilters failed');
    assert(res.hasOwnProperty('filters'), 'filters missing from planCacheListFilters result');
    return res.filters;
    
}

// Check if key is in plan cache.
function planCacheContains(shape) {
    var res = t.runCommand('planCacheListPlans', shape);
    return res.ok;
}

// Utility function to list plans for a query.
function getPlans(shape) {
    var res = t.runCommand('planCacheListPlans', shape);
    assert.commandWorked(res, 'planCacheListPlans(' + tojson(shape, '', true) + ' failed');
    assert(res.hasOwnProperty('plans'), 'plans missing from planCacheListPlans(' +
           tojson(shape, '', true) + ') result');
    return res.plans;
}

// It is an error to retrieve index filters on a non-existent collection.
var missingCollection = db.jstests_index_filter_commands_missing;
missingCollection.drop();
assert.commandFailed(missingCollection.runCommand('planCacheListFilters'));

// Retrieve index filters from an empty test collection.
var filters = getFilters();
assert.eq(0, filters.length, 'unexpected number of index filters in planCacheListFilters result');

// Check details of winning plan in plan cache before setting index filter.
assert.eq(1, t.find(queryA1, projectionA1).sort(sortA1).itcount(), 'unexpected document count');
var shape = {query: queryA1, sort: sortA1, projection: projectionA1};
var planBeforeSetFilter = getPlans(shape)[0];
print('Winning plan (before setting index filters) = ' + tojson(planBeforeSetFilter));
// Check filterSet field in plan details
assert.eq(false, planBeforeSetFilter.filterSet, 'missing or invalid filterSet field in plan details');

// Add index filters for simple query.
assert.commandWorked(t.runCommand('planCacheSetFilter',
    {query: queryA1, sort: sortA1, projection: projectionA1, indexes: [indexA1B1, indexA1C1]}));
filters = getFilters();
assert.eq(1, filters.length, 'no change in query settings after successfully setting index filters');
assert.eq(queryA1, filters[0].query, 'unexpected query in filters');
assert.eq(sortA1, filters[0].sort, 'unexpected sort in filters');
assert.eq(projectionA1, filters[0].projection, 'unexpected projection in filters');
assert.eq(2, filters[0].indexes.length, 'unexpected number of indexes in filters');
assert.eq(indexA1B1, filters[0].indexes[0], 'unexpected first index');
assert.eq(indexA1C1, filters[0].indexes[1], 'unexpected first index');

// Plans for query shape should be removed after setting index filter.
assert(!planCacheContains(shape), 'plan cache for query shape not flushed after updating filter');

// Check details of winning plan in plan cache after setting filter and re-executing query.
assert.eq(1, t.find(queryA1, projectionA1).sort(sortA1).itcount(), 'unexpected document count');
planAfterSetFilter = getPlans(shape)[0];
print('Winning plan (after setting index filter) = ' + tojson(planAfterSetFilter));
// Check filterSet field in plan details
assert.eq(true, planAfterSetFilter.filterSet, 'missing or invalid filterSet field in plan details');

// Execute query with cursor.hint(). Check that user-provided hint is overridden.
// Applying the index filters will remove the user requested index from the list
// of indexes provided to the planner.
// If the planner still tries to use the user hint, we will get a 'bad hint' error.
t.find(queryA1, projectionA1).sort(sortA1).hint(indexA1).itcount();

// Clear filters
assert.commandWorked(t.runCommand('planCacheClearFilters'));
filters = getFilters();
assert.eq(0, filters.length, 'filters not cleared after successful planCacheClearFilters command');

// Plans should be removed after clearing filters
assert(!planCacheContains(shape), 'plan cache for query shape not flushed after clearing filters');

print('Plan details before setting filter = ' + tojson(planBeforeSetFilter.details, '', true));
print('Plan details after setting filter = ' + tojson(planAfterSetFilter.details, '', true));

//
// explain.filterSet
// cursor.explain() should indicate if index filter has been applied.
// The following 3 runners should always provide a value for 'filterSet':
// - SingleSolutionRunner
// - MultiPlanRunner
// - CachedPlanRuner
//

// No filter set.

t.getPlanCache().clear();
// SingleSolutionRunner
assert.eq(false, t.find({z: 1}).explain().filterSet,
          'missing or invalid filterSet field in SingleSolutionRunner explain');
// MultiPlanRunner
assert.eq(false, t.find(queryA1, projectionA1).sort(sortA1).explain().filterSet,
          'missing or invalid filterSet field in MultiPlanRunner explain');
// CachedPlanRunner
assert.eq(false, t.find(queryA1, projectionA1).sort(sortA1).explain().filterSet,
          'missing or invalid filterSet field in CachedPlanRunner explain');

// Add index filter.
assert.commandWorked(t.runCommand('planCacheSetFilter',
    {query: queryA1, sort: sortA1, projection: projectionA1, indexes: [indexA1B1, indexA1C1]}));
// Index filter with non-existent index key pattern to force use of single solution runner.
assert.commandWorked(t.runCommand('planCacheSetFilter', {query: {z: 1}, indexes: [{z: 1}]}));

t.getPlanCache().clear();
// SingleSolutionRunner
assert.eq(true, t.find({z: 1}).explain().filterSet,
       'missing or invalid filterSet field in SingleSolutionRunner explain');
// MultiPlanRunner
assert.eq(true, t.find(queryA1, projectionA1).sort(sortA1).explain().filterSet,
       'missing or invalid filterSet field in MultiPlanRunner explain');
// CachedPlanRunner
assert.eq(true, t.find(queryA1, projectionA1).sort(sortA1).explain().filterSet,
       'missing or invalid filterSet field in CachedPlanRunner explain');