summaryrefslogtreecommitdiff
path: root/jstests/noPassthroughWithMongod
diff options
context:
space:
mode:
authorBernard Gorman <bernard.gorman@gmail.com>2018-09-25 22:31:45 +0100
committerBernard Gorman <bernard.gorman@gmail.com>2018-10-23 23:19:25 +0100
commit91c7456f24342e2b11b9cd486f7e8bc5d8b1f90e (patch)
treeb90ea7b58bac0d8577562c4254fef7faf61ebb5d /jstests/noPassthroughWithMongod
parentb0d12219828e72dbc59ede95cc267a8c4eafbc2f (diff)
downloadmongo-91c7456f24342e2b11b9cd486f7e8bc5d8b1f90e.tar.gz
SERVER-37132 Negation of $in with regex can incorrectly plan from the cache, leading to missing query results
(cherry picked from commit e786e3a313b75a1fe8aa233ed09da2d2efbaf613)
Diffstat (limited to 'jstests/noPassthroughWithMongod')
-rw-r--r--jstests/noPassthroughWithMongod/plan_cache_not_in_regex.js55
1 files changed, 55 insertions, 0 deletions
diff --git a/jstests/noPassthroughWithMongod/plan_cache_not_in_regex.js b/jstests/noPassthroughWithMongod/plan_cache_not_in_regex.js
new file mode 100644
index 00000000000..1343c40e8ec
--- /dev/null
+++ b/jstests/noPassthroughWithMongod/plan_cache_not_in_regex.js
@@ -0,0 +1,55 @@
+/**
+ * Tests that a $not-$in-$regex query, which cannot be supported by an index, cannot incorrectly
+ * hijack the cached plan for an earlier $not-$in query.
+ */
+(function() {
+ "use strict";
+
+ load('jstests/libs/analyze_plan.js'); // For isCollScan.
+
+ const coll = db.plan_cache_not_in_regex;
+ coll.drop();
+
+ // Helper function which obtains the cached plan, if any, for a given query shape.
+ function getPlanForCacheEntry(query, proj, sort) {
+ const cacheDetails =
+ coll.getPlanCache().getPlansByQuery({query: query, sort: sort, projection: proj});
+ const plans = cacheDetails.filter(plan => plan.feedback.nfeedback >= 0);
+ assert.eq(plans.length, 1, `Expected one cached plan, found: ${tojson(plans)}`);
+ return plans.shift();
+ }
+
+ // Insert a document containing a field 'a', and create two indexes that can support queries on
+ // this field. This is to ensure that the plan we choose will be cached, since if only a single
+ // index is available, the solution will not be cached.
+ assert.writeOK(coll.insert({a: "foo"}));
+ assert.commandWorked(coll.createIndex({a: 1}));
+ assert.commandWorked(coll.createIndex({a: 1, b: 1}));
+
+ // Repeat the test for query, query with projection, and query with projection and sort.
+ for (let [proj, sort] of[[{}, {}], [{_id: 0, a: 1}, {}], [{_id: 0, a: 1}, {a: 1}]]) {
+ // Perform a plain $not-$in query on 'a' and confirm that the plan is cached.
+ const queryShape = {a: {$not: {$in: [32, 33]}}};
+ assert.eq(1, coll.find(queryShape, proj).sort(sort).itcount());
+ let cacheEntry = getPlanForCacheEntry(queryShape, proj, sort);
+ assert(cacheEntry);
+
+ // If the cached plan is inactive, perform the same query to activate it.
+ if (cacheEntry.isActive === false) {
+ assert.eq(1, coll.find(queryShape, proj).sort(sort).itcount());
+ cacheEntry = getPlanForCacheEntry(queryShape, proj, sort);
+ assert(cacheEntry);
+ assert(cacheEntry.isActive);
+ }
+
+ // Now perform a $not-$in-$regex query, confirm that it obtains the correct results, and
+ // that it used a COLLSCAN rather than planning from the cache.
+ const explainOutput = assert.commandWorked(
+ coll.find({a: {$not: {$in: [34, /bar/]}}}).explain("executionStats"));
+ assert(isCollscan(explainOutput.queryPlanner.winningPlan));
+ assert.eq(1, explainOutput.executionStats.nReturned);
+
+ // Flush the plan cache before the next iteration.
+ coll.getPlanCache().clear();
+ }
+})(); \ No newline at end of file