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
|
/**
* Test that 'queryHash' and 'planCacheKey' from explain() output have sensible values
* across catalog changes.
* @tags: [
* assumes_read_concern_local,
* ]
*/
(function() {
"use strict";
load('jstests/libs/fixture_helpers.js'); // For and isMongos().
const collName = "query_hash_stability";
const coll = db[collName];
coll.drop();
// Be sure the collection exists.
assert.commandWorked(coll.insert({x: 5}));
/**
* Given two explain plans (firstExplain, secondExplain), this function makes assertions about their
* 'planCacheField' values (in particular, whether they are 'expectedToMatch').
*/
let assertPlanCacheField = function(
{firstExplain, secondExplain, planCacheField, expectedToMatch}) {
let compareFn = function(first, second) {
assert.eq(typeof (first), "string");
assert.eq(typeof (second), "string");
assert.eq(first === second,
expectedToMatch,
"Mismatch for field " + planCacheField + " when comparing " +
tojson(firstExplain) + " with " + tojson(secondExplain));
};
// SERVER-56980: When running in a sharded environment, we group the values for 'planCacheField'
// by shard. This is because in a multi-version environment, we want to ensure that we are
// comparing the results produced by the same shard in the event that the planCacheKey format
// changed in between versions.
if (FixtureHelpers.isMongos(db)) {
let buildShardMap = function(shardedPlan) {
let explainMap = {};
for (const shard of shardedPlan.queryPlanner.winningPlan.shards) {
explainMap[shard.shardName] = shard[planCacheField];
}
return explainMap;
};
const firstExplainMap = buildShardMap(firstExplain);
const secondExplainMap = buildShardMap(secondExplain);
// Should have the same number of elements.
assert.eq(Object.keys(firstExplainMap).length,
Object.keys(secondExplainMap).length,
"Expected " + tojson(firstExplainMap) + " and " + tojson(secondExplainMap) +
" to have the same number of elements");
// Match the values for 'planCacheField' for each shard.
for (const shardName of Object.keys(firstExplainMap)) {
const firstPlanCacheValue = firstExplainMap[shardName];
const secondPlanCacheValue = secondExplainMap[shardName];
compareFn(firstPlanCacheValue, secondPlanCacheValue);
}
} else {
const first = firstExplain['queryPlanner'][planCacheField];
const second = secondExplain['queryPlanner'][planCacheField];
compareFn(first, second);
}
};
const query = {
x: 3
};
const initialExplain = coll.find(query).explain();
// Add a sparse index.
assert.commandWorked(coll.createIndex({x: 1}, {sparse: true}));
const withIndexExplain = coll.find(query).explain();
// 'queryHash' shouldn't change across catalog changes.
assertPlanCacheField({
firstExplain: initialExplain,
secondExplain: withIndexExplain,
planCacheField: 'queryHash',
expectedToMatch: true
});
// We added an index so the plan cache key changed.
assertPlanCacheField({
firstExplain: initialExplain,
secondExplain: withIndexExplain,
planCacheField: 'planCacheKey',
expectedToMatch: false
});
// Drop the index.
assert.commandWorked(coll.dropIndex({x: 1}));
const postDropExplain = coll.find(query).explain();
// 'queryHash' shouldn't change across catalog changes.
assertPlanCacheField({
firstExplain: initialExplain,
secondExplain: postDropExplain,
planCacheField: 'queryHash',
expectedToMatch: true
});
// The 'planCacheKey' should be the same as what it was before we dropped the index.
assertPlanCacheField({
firstExplain: initialExplain,
secondExplain: postDropExplain,
planCacheField: 'planCacheKey',
expectedToMatch: true
});
})();
|