summaryrefslogtreecommitdiff
path: root/src/mongo
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 /src/mongo
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 'src/mongo')
-rw-r--r--src/mongo/db/query/plan_cache.cpp7
-rw-r--r--src/mongo/db/query/plan_cache_test.cpp34
2 files changed, 41 insertions, 0 deletions
diff --git a/src/mongo/db/query/plan_cache.cpp b/src/mongo/db/query/plan_cache.cpp
index d34df64ab88..62a3dcc33fc 100644
--- a/src/mongo/db/query/plan_cache.cpp
+++ b/src/mongo/db/query/plan_cache.cpp
@@ -505,6 +505,13 @@ void PlanCache::encodeKeyForMatch(const MatchExpression* tree, StringBuilder* ke
encodeGeoNearMatchExpression(static_cast<const GeoNearMatchExpression*>(tree), keyBuilder);
}
+ // We must encode the presence of any regular expressions within MATCH_IN to ensure correctness.
+ if (MatchExpression::MATCH_IN == tree->matchType()) {
+ if (!static_cast<const InMatchExpression*>(tree)->getRegexes().empty()) {
+ encodeUserString("_re"_sd, keyBuilder);
+ }
+ }
+
// Encode indexability.
const IndexToDiscriminatorMap& discriminators =
_indexabilityState.getDiscriminators(tree->path());
diff --git a/src/mongo/db/query/plan_cache_test.cpp b/src/mongo/db/query/plan_cache_test.cpp
index bac9704e0cf..5be65b1b27d 100644
--- a/src/mongo/db/query/plan_cache_test.cpp
+++ b/src/mongo/db/query/plan_cache_test.cpp
@@ -1392,6 +1392,40 @@ TEST(PlanCacheTest, ComputeKeyGeoNear) {
"gnanrsp");
}
+TEST(PlanCacheTest, ComputeKeyMatchInDependsOnPresenceOfRegex) {
+ // Test that an $in containing a single regex is unwrapped to $regex.
+ testComputeKey("{a: {$in: [/foo/]}}", "{}", "{}", "rea");
+ testComputeKey("{a: {$in: [/foo/i]}}", "{}", "{}", "rea");
+
+ // Test that an $in with no regexes does not include any regex information.
+ testComputeKey("{a: {$in: [1, 'foo']}}", "{}", "{}", "ina");
+
+ // Test that an $in with a regex encodes the presence of the regex.
+ testComputeKey("{a: {$in: [1, /foo/]}}", "{}", "{}", "ina_re");
+
+ // Test that an $in with a regex and flags encodes the presence of the regex.
+ testComputeKey("{a: {$in: [1, /foo/is]}}", "{}", "{}", "ina_re");
+ testComputeKey("{a: {$in: [1, /foo/i]}}", "{}", "{}", "ina_re");
+
+ // Test that an $in with multiple regexes encodes their presence only once.
+ testComputeKey("{a: {$in: [1, /foo/i, /bar/m, /baz/s]}}", "{}", "{}", "ina_re");
+ testComputeKey(
+ "{a: {$in: [1, /foo/i, /bar/m, /baz/s, /qux/i, /quux/s]}}", "{}", "{}", "ina_re");
+ testComputeKey(
+ "{a: {$in: [1, /foo/ism, /bar/msi, /baz/im, /qux/si, /quux/im]}}", "{}", "{}", "ina_re");
+ testComputeKey(
+ "{a: {$in: [1, /foo/msi, /bar/ism, /baz/is, /qux/mi, /quux/im]}}", "{}", "{}", "ina_re");
+
+ // Test that $not-$in-$regex similarly records the presence of any regexes.
+ testComputeKey("{a: {$not: {$in: [1, 'foo']}}}", "{}", "{}", "nt[ina]");
+ testComputeKey("{a: {$not: {$in: [1, /foo/]}}}", "{}", "{}", "nt[ina_re]");
+ testComputeKey("{a: {$not: {$in: [1, /foo/i, /bar/i, /baz/msi]}}}", "{}", "{}", "nt[ina_re]");
+
+ // Test that a $not-$in containing a single regex is unwrapped to $not-$regex.
+ testComputeKey("{a: {$not: {$in: [/foo/]}}}", "{}", "{}", "nt[rea]");
+ testComputeKey("{a: {$not: {$in: [/foo/i]}}}", "{}", "{}", "nt[rea]");
+}
+
// When a sparse index is present, computeKey() should generate different keys depending on
// whether or not the predicates in the given query can use the index.
TEST(PlanCacheTest, ComputeKeySparseIndex) {