summaryrefslogtreecommitdiff
path: root/jstests/core/query/query_hash_stability.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/core/query/query_hash_stability.js')
-rw-r--r--jstests/core/query/query_hash_stability.js124
1 files changed, 124 insertions, 0 deletions
diff --git a/jstests/core/query/query_hash_stability.js b/jstests/core/query/query_hash_stability.js
new file mode 100644
index 00000000000..aa0a3399e70
--- /dev/null
+++ b/jstests/core/query/query_hash_stability.js
@@ -0,0 +1,124 @@
+/**
+ * Test that 'queryHash' and 'planCacheKey' from explain() output have sensible values
+ * across catalog changes.
+ * @tags: [
+ * assumes_read_concern_local,
+ * requires_fcv_51,
+ * # The test expects the plan cache key on a given node to remain stable. However, the plan
+ * # cache key is allowed to change between versions. Therefore, this test cannot run in
+ * # passthroughs that do upgrade/downgrade.
+ * cannot_run_during_upgrade_downgrade,
+ * ]
+ */
+(function() {
+"use strict";
+load('jstests/libs/fixture_helpers.js'); // For and isMongos().
+load("jstests/libs/sbe_util.js"); // For checkSBEEnabled.
+
+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
+});
+
+// SBE's planCacheKey encoding encodes "collection version" which will be increased after dropping
+// an index.
+if (!checkSBEEnabled(db, ["featureFlagSbeFull"])) {
+ // The 'planCacheKey' should be the same as what it was before we dropped the index.
+ assertPlanCacheField({
+ firstExplain: initialExplain,
+ secondExplain: postDropExplain,
+ planCacheField: 'planCacheKey',
+ expectedToMatch: true
+ });
+}
+})();