diff options
author | Bernard Gorman <bernard.gorman@gmail.com> | 2018-09-25 22:31:45 +0100 |
---|---|---|
committer | Bernard Gorman <bernard.gorman@gmail.com> | 2018-10-08 11:38:15 +0100 |
commit | 1981c02a8bb76d5f6ab30a512c4f894a05452d3f (patch) | |
tree | 9ebb36422599556414b390d9aa21c2a3ae65667b /jstests | |
parent | ac5594c1fc13fdb62b3bf22a5d6f3124e57b84f5 (diff) | |
download | mongo-1981c02a8bb76d5f6ab30a512c4f894a05452d3f.tar.gz |
SERVER-37132 Negation of $in with regex can incorrectly plan from the cache, leading to missing query results
(cherry picked from commit ba38c66d9483d2fb8a644772fa5dd0fff78a3cc9)
Diffstat (limited to 'jstests')
-rw-r--r-- | jstests/noPassthroughWithMongod/plan_cache_not_in_regex.js | 54 |
1 files changed, 54 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..c17794b6d73 --- /dev/null +++ b/jstests/noPassthroughWithMongod/plan_cache_not_in_regex.js @@ -0,0 +1,54 @@ +/** + * 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 key = {query: query, sort: sort, projection: proj}; + const plans = coll.getPlanCache().getPlansByQuery(key).plans; + assert.gt(plans.length, 0, `Expected cached plans, 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.commandWorked(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(coll.getDB(), 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 |