summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoshua <80741223+jlap199@users.noreply.github.com>2022-09-23 22:45:01 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-09-23 23:18:32 +0000
commit05cda545eb5d1911753cbd3c8004aac9308d890c (patch)
treebe8a8479ec486e97e32172946c50801e71e5c7cf
parent6552887793ccf7dc0c52ff7e19167f10990b6567 (diff)
downloadmongo-05cda545eb5d1911753cbd3c8004aac9308d890c.tar.gz
SERVER-68115 Do not drop empty path component from elemMatch path during index selection
-rwxr-xr-xbuildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml6
-rw-r--r--etc/backports_required_for_multiversion_tests.yml4
-rw-r--r--jstests/core/elemmatch_index.js (renamed from jstests/core/index_elemmatch2.js)48
-rw-r--r--jstests/core/index_elemmatch1.js39
-rw-r--r--src/mongo/db/query/planner_ixselect.cpp20
5 files changed, 58 insertions, 59 deletions
diff --git a/buildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml
index 4e07925f87b..52c443237e4 100755
--- a/buildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml
+++ b/buildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml
@@ -256,9 +256,6 @@ selector:
- jstests/core/index_check6.js
- jstests/core/index_check7.js
- jstests/core/index_decimal.js
- - jstests/core/index_elemmatch1.js
- - jstests/core/index_elemmatch2.js
- - jstests/core/index_elemmatch2.js
- jstests/core/index_filter_commands.js
- jstests/core/index_filter_on_hidden_index.js
- jstests/core/index_multiple_compatibility.js
@@ -506,8 +503,6 @@ selector:
- jstests/core/in7.js
- jstests/core/index13.js
- jstests/core/index_check2.js
- - jstests/core/index_elemmatch1.js
- - jstests/core/index_elemmatch2.js
- jstests/core/indexl.js
- jstests/core/json_schema/misc_validation.js
- jstests/core/ne_array.js
@@ -756,7 +751,6 @@ selector:
- jstests/core/geonear_key.js
- jstests/core/getmore_invalidated_documents.js
- jstests/core/hidden_index.js
- - jstests/core/index_elemmatch2.js
- jstests/core/index_partial_2dsphere.js
- jstests/core/json_schema/misc_validation.js
- jstests/core/list_collections_filter.js
diff --git a/etc/backports_required_for_multiversion_tests.yml b/etc/backports_required_for_multiversion_tests.yml
index 1aaec206c81..14075f14b23 100644
--- a/etc/backports_required_for_multiversion_tests.yml
+++ b/etc/backports_required_for_multiversion_tests.yml
@@ -300,6 +300,8 @@ last-continuous:
test_file: jstests/sharding/read_write_concern_defaults_application.js
- ticket: SERVER-68932
test_file: jstests/sharding/resharding_critical_section_metrics.js
+ - ticket: SERVER-68115
+ test_file: jstests/core/elemmatch_index.js
# Tests that should only be excluded from particular suites should be listed under that suite.
suites:
@@ -731,6 +733,8 @@ last-lts:
test_file: jstests/replsets/tenant_migration_concurrent_writes_on_donor_util.js
- ticket: SERVER-69348
test_file: jstests/sharding/read_write_concern_defaults_application.js
+ - ticket: SERVER-68115
+ test_file: jstests/core/elemmatch_index.js
# Tests that should only be excluded from particular suites should be listed under that suite.
suites:
diff --git a/jstests/core/index_elemmatch2.js b/jstests/core/elemmatch_index.js
index 33988cb945e..a1941620a48 100644
--- a/jstests/core/index_elemmatch2.js
+++ b/jstests/core/elemmatch_index.js
@@ -2,6 +2,7 @@
* Test that queries containing $elemMatch correctly use an index if each child expression is
* compatible with the index.
* @tags: [
+ * assumes_balancer_off,
* assumes_read_concern_local,
* ]
*/
@@ -64,4 +65,51 @@ assertIndexResults(coll, {a: {$elemMatch: {$not: {$in: [/^a/]}}}}, false, 3);
coll.dropIndexes();
assert.commandWorked(coll.createIndex({a: 1}));
assertIndexResults(coll, {a: {$elemMatch: {$not: {$in: [/^a/]}}}}, false, 3);
+
+(function() {
+assert(coll.drop());
+assert.commandWorked(coll.insert({a: [{b: {c: "x"}}]}));
+assert.commandWorked(coll.createIndex({"a.b.c": 1}));
+
+// Tests $elemMatch with path components that are empty strings. The system should not attempt to
+// use the index for these queries.
+assertIndexResults(coll, {"": {$elemMatch: {"a.b.c": "x"}}}, false, 0);
+assertIndexResults(coll, {"": {$all: [{$elemMatch: {"a.b.c": "x"}}]}}, false, 0);
+assertIndexResults(coll, {a: {$elemMatch: {"": {$elemMatch: {"b.c": "x"}}}}}, false, 0);
+
+// Tests $elemMatch with supporting index and no path components that are empty strings.
+assertIndexResults(coll, {a: {$elemMatch: {"b.c": "x"}}}, true, 1);
+assertIndexResults(coll, {a: {$all: [{$elemMatch: {"b.c": "x"}}]}}, true, 1);
+})();
+
+(function() {
+const coll = db.index_elemmatch1;
+coll.drop();
+
+let x = 0;
+let y = 0;
+const bulk = coll.initializeUnorderedBulkOp();
+for (let a = 0; a < 10; a++) {
+ for (let b = 0; b < 10; b++) {
+ bulk.insert({a: a, b: b % 10, arr: [{x: x++ % 10, y: y++ % 10}]});
+ }
+}
+assert.commandWorked(bulk.execute());
+
+assert.commandWorked(coll.createIndex({a: 1, b: 1}));
+assert.commandWorked(coll.createIndex({"arr.x": 1, a: 1}));
+
+const query = {
+ a: 5,
+ b: {$in: [1, 3, 5]},
+ arr: {$elemMatch: {x: 5, y: 5}}
+};
+
+const count = coll.find(query).itcount();
+assert.eq(count, 1);
+
+const explain = coll.find(query).hint({"arr.x": 1, a: 1}).explain("executionStats");
+assert.commandWorked(explain);
+assert.eq(count, explain.executionStats.totalKeysExamined, explain);
+})();
})();
diff --git a/jstests/core/index_elemmatch1.js b/jstests/core/index_elemmatch1.js
deleted file mode 100644
index 6277ca9c42d..00000000000
--- a/jstests/core/index_elemmatch1.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Tests find with $elemMatch when supporting indexes are in place.
- * @tags: [
- * assumes_balancer_off,
- * assumes_read_concern_local,
- * ]
- */
-(function() {
-"use strict";
-
-const coll = db.index_elemmatch1;
-coll.drop();
-
-let x = 0;
-let y = 0;
-const bulk = coll.initializeUnorderedBulkOp();
-for (let a = 0; a < 10; a++) {
- for (let b = 0; b < 10; b++) {
- bulk.insert({a: a, b: b % 10, arr: [{x: x++ % 10, y: y++ % 10}]});
- }
-}
-assert.commandWorked(bulk.execute());
-
-assert.commandWorked(coll.createIndex({a: 1, b: 1}));
-assert.commandWorked(coll.createIndex({"arr.x": 1, a: 1}));
-
-const query = {
- a: 5,
- b: {$in: [1, 3, 5]},
- arr: {$elemMatch: {x: 5, y: 5}}
-};
-
-const count = coll.find(query).itcount();
-assert.eq(count, 1);
-
-const explain = coll.find(query).hint({"arr.x": 1, a: 1}).explain("executionStats");
-assert.commandWorked(explain);
-assert.eq(count, explain.executionStats.totalKeysExamined, explain);
-})();
diff --git a/src/mongo/db/query/planner_ixselect.cpp b/src/mongo/db/query/planner_ixselect.cpp
index 9e52daeb6ea..bb7c7cec186 100644
--- a/src/mongo/db/query/planner_ixselect.cpp
+++ b/src/mongo/db/query/planner_ixselect.cpp
@@ -257,15 +257,11 @@ void QueryPlannerIXSelect::getFields(const MatchExpression* node,
// Leaf nodes with a path and some array operators.
if (Indexability::nodeCanUseIndexOnOwnField(node)) {
out->insert(prefix + node->path().toString());
- } else if (Indexability::arrayUsesIndexOnChildren(node)) {
+ } else if (Indexability::arrayUsesIndexOnChildren(node) && !node->path().empty()) {
// If the array uses an index on its children, it's something like
// {foo : {$elemMatch: {bar: 1}}}, in which case the predicate is really over foo.bar.
- //
- // When we have {foo: {$all: [{$elemMatch: {a: 1}}], the path of the embedded elemMatch
- // is empty. We don't want to append a dot in that case as the field would be foo..a.
- if (!node->path().empty()) {
- prefix += node->path().toString() + ".";
- }
+ // Note we skip empty path components since they are not allowed in index key patterns.
+ prefix += node->path().toString() + ".";
for (size_t i = 0; i < node->numChildren(); ++i) {
getFields(node->getChild(i), prefix, out);
@@ -783,7 +779,8 @@ void QueryPlannerIXSelect::_rateIndices(MatchExpression* node,
childRt->path = rt->path;
node->getChild(0)->setTag(childRt);
}
- } else if (Indexability::arrayUsesIndexOnChildren(node)) {
+ } else if (Indexability::arrayUsesIndexOnChildren(node) && !node->path().empty()) {
+ // Note we skip empty path components since they are not allowed in index key patterns.
const auto newPath = prefix + node->path().toString();
ElemMatchContext newContext;
// Note this StringData is unowned and references the string declared on the stack here.
@@ -794,12 +791,7 @@ void QueryPlannerIXSelect::_rateIndices(MatchExpression* node,
// If the array uses an index on its children, it's something like
// {foo: {$elemMatch: {bar: 1}}}, in which case the predicate is really over foo.bar.
- //
- // When we have {foo: {$all: [{$elemMatch: {a: 1}}], the path of the embedded elemMatch
- // is empty. We don't want to append a dot in that case as the field would be foo..a.
- if (!node->path().empty()) {
- prefix += node->path().toString() + ".";
- }
+ prefix += node->path().toString() + ".";
for (size_t i = 0; i < node->numChildren(); ++i) {
_rateIndices(node->getChild(i), prefix, indices, collator, newContext);
}