diff options
-rwxr-xr-x | buildscripts/resmokeconfig/suites/cst_jscore_passthrough.yml | 6 | ||||
-rw-r--r-- | etc/backports_required_for_multiversion_tests.yml | 4 | ||||
-rw-r--r-- | jstests/core/elemmatch_index.js (renamed from jstests/core/index_elemmatch2.js) | 48 | ||||
-rw-r--r-- | jstests/core/index_elemmatch1.js | 39 | ||||
-rw-r--r-- | src/mongo/db/query/planner_ixselect.cpp | 20 |
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); } |