diff options
author | Timour Katchaounov <timour.katchaounov@mongodb.com> | 2021-11-22 11:41:21 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-01-12 07:48:16 +0000 |
commit | 4b8b91c2ff91f538eede6b6c875656d697a8a916 (patch) | |
tree | db56d29d45b7d79dc28ea1361fbee243366bab39 | |
parent | f2fc34bdec288cf3b90bf926d6a6c77631f4fa10 (diff) | |
download | mongo-4b8b91c2ff91f538eede6b6c875656d697a8a916.tar.gz |
SERVER-57588 Inconsistent query results when an array position is indexed whose value is an array
(cherry picked from commit 90699509e15b33fda10832e79efcd158aee1f0eb)
-rw-r--r-- | etc/backports_required_for_multiversion_tests.yml | 2 | ||||
-rw-r--r-- | jstests/core/match_numeric_components.js | 449 | ||||
-rw-r--r-- | src/mongo/db/query/planner_ixselect.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/query/query_planner_array_test.cpp | 41 |
4 files changed, 502 insertions, 0 deletions
diff --git a/etc/backports_required_for_multiversion_tests.yml b/etc/backports_required_for_multiversion_tests.yml index dd877b77bd3..82662d7f1f7 100644 --- a/etc/backports_required_for_multiversion_tests.yml +++ b/etc/backports_required_for_multiversion_tests.yml @@ -122,6 +122,8 @@ all: test_file: jstests/aggregation/range.js - ticket: SERVER-53335 test_file: jstests/sharding/collation_shard_targeting_hashed_shard_key.js + - ticket: SERVER-57588 + test_file: jstests/core/match_numeric_components.js - ticket: SERVER-60682 test_file: jstests/sharding/coordinate_txn_commit_with_tickets_exhausted.js - ticket: SERVER-60685 diff --git a/jstests/core/match_numeric_components.js b/jstests/core/match_numeric_components.js new file mode 100644 index 00000000000..7f20b3710ab --- /dev/null +++ b/jstests/core/match_numeric_components.js @@ -0,0 +1,449 @@ +/** + * Tests behavior of the match language when using numeric path components. + */ +(function() { +const coll = db.match_numeric_components; +coll.drop(); + +const kDocs = [ + {_id: 0, "a": 42}, + {_id: 1, "a": [42]}, + {_id: 2, "a": {"0": 42}}, + {_id: 3, "a": [[42]]}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 6, "a": {"0": {"0": 42}}}, + {_id: 7, "a": [[[42]]]}, + {_id: 8, "a": [[{"0": 42}]]}, + {_id: 9, "a": [{"0": [42]}]}, + {_id: 10, "a": [{"0": {"0": 42}}]}, + {_id: 11, "a": {"0": [[42]]}}, + {_id: 12, "a": {"0": [{"0": 42}]}}, + {_id: 13, "a": {"0": {"0": [42]}}}, + {_id: 14, "a": {"0": {"0": {"0": 42}}}}, + {_id: 15, "a": [[[[42]]]]}, + {_id: 16, "a": [[[{"0": 42}]]]}, + {_id: 17, "a": [[{"0": [42]}]]}, + {_id: 18, "a": [[{"0": {"0": 42}}]]}, + {_id: 19, "a": [{"0": [[42]]}]}, + {_id: 20, "a": [{"0": [{"0": 42}]}]}, + {_id: 21, "a": [{"0": {"0": [42]}}]}, + {_id: 22, "a": [{"0": {"0": {"0": 42}}}]}, + {_id: 23, "a": {"0": [[[42]]]}}, + {_id: 24, "a": {"0": [[{"0": 42}]]}}, + {_id: 25, "a": {"0": [{"0": [42]}]}}, + {_id: 26, "a": {"0": [{"0": {"0": 42}}]}}, + {_id: 27, "a": {"0": {"0": [[42]]}}}, + {_id: 28, "a": {"0": {"0": [{"0": 42}]}}}, + {_id: 29, "a": {"0": {"0": {"0": [42]}}}}, + {_id: 30, "a": {"0": {"0": {"0": {"0": 42}}}}}, +]; + +assert.commandWorked(coll.insert(kDocs)); + +{ + const res = coll.find({"a.0": 42}).toArray(); + const expected = [ + {_id: 1, "a": [42]}, + {_id: 2, "a": {"0": 42}}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 9, "a": [{"0": [42]}]} + ]; + + assert.sameMembers(res, expected); +} + +// Using $ne. +{ + const res = coll.find({"a.0": {$ne: 42}}).toArray(); + const expected = [ + {_id: 0, "a": 42}, + {_id: 3, "a": [[42]]}, + {_id: 6, "a": {"0": {"0": 42}}}, + {_id: 7, "a": [[[42]]]}, + {_id: 8, "a": [[{"0": 42}]]}, + {_id: 10, "a": [{"0": {"0": 42}}]}, + {_id: 11, "a": {"0": [[42]]}}, + {_id: 12, "a": {"0": [{"0": 42}]}}, + {_id: 13, "a": {"0": {"0": [42]}}}, + {_id: 14, "a": {"0": {"0": {"0": 42}}}}, + {_id: 15, "a": [[[[42]]]]}, + {_id: 16, "a": [[[{"0": 42}]]]}, + {_id: 17, "a": [[{"0": [42]}]]}, + {_id: 18, "a": [[{"0": {"0": 42}}]]}, + {_id: 19, "a": [{"0": [[42]]}]}, + {_id: 20, "a": [{"0": [{"0": 42}]}]}, + {_id: 21, "a": [{"0": {"0": [42]}}]}, + {_id: 22, "a": [{"0": {"0": {"0": 42}}}]}, + {_id: 23, "a": {"0": [[[42]]]}}, + {_id: 24, "a": {"0": [[{"0": 42}]]}}, + {_id: 25, "a": {"0": [{"0": [42]}]}}, + {_id: 26, "a": {"0": [{"0": {"0": 42}}]}}, + {_id: 27, "a": {"0": {"0": [[42]]}}}, + {_id: 28, "a": {"0": {"0": [{"0": 42}]}}}, + {_id: 29, "a": {"0": {"0": {"0": [42]}}}}, + {_id: 30, "a": {"0": {"0": {"0": {"0": 42}}}}}, + ]; + assert.sameMembers(res, expected); +} + +{ + const res = coll.find({"a.0.0": 42}).toArray(); + const expected = [ + {_id: 3, "a": [[42]]}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 6, "a": {"0": {"0": 42}}}, + {_id: 8, "a": [[{"0": 42}]]}, + {_id: 9, "a": [{"0": [42]}]}, + {_id: 10, "a": [{"0": {"0": 42}}]}, + {_id: 12, "a": {"0": [{"0": 42}]}}, + {_id: 13, "a": {"0": {"0": [42]}}}, + {_id: 17, "a": [[{"0": [42]}]]}, + {_id: 20, "a": [{"0": [{"0": 42}]}]}, + {_id: 21, "a": [{"0": {"0": [42]}}]}, + {_id: 25, "a": {"0": [{"0": [42]}]}} + ]; + + assert.sameMembers(res, expected); +} + +// Using a comparison. +{ + const res = coll.find({"a.0": {$gt: 41}}).toArray(); + const expected = [ + {_id: 1, "a": [42]}, + {_id: 2, "a": {"0": 42}}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 9, "a": [{"0": [42]}]} + ]; + + assert.sameMembers(res, expected); +} + +// Using $in. +{ + const res = coll.find({"a.0": {$in: [41, 42, 43]}}).toArray(); + const expected = [ + {_id: 1, "a": [42]}, + {_id: 2, "a": {"0": 42}}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 9, "a": [{"0": [42]}]} + ]; + + assert.sameMembers(res, expected); +} + +// Using $nin. +{ + const res = coll.find({"a.0": {$nin: [41, 42, 43]}}).toArray(); + const expected = [ + {_id: 0, "a": 42}, + {_id: 3, "a": [[42]]}, + {_id: 6, "a": {"0": {"0": 42}}}, + {_id: 7, "a": [[[42]]]}, + {_id: 8, "a": [[{"0": 42}]]}, + {_id: 10, "a": [{"0": {"0": 42}}]}, + {_id: 11, "a": {"0": [[42]]}}, + {_id: 12, "a": {"0": [{"0": 42}]}}, + {_id: 13, "a": {"0": {"0": [42]}}}, + {_id: 14, "a": {"0": {"0": {"0": 42}}}}, + {_id: 15, "a": [[[[42]]]]}, + {_id: 16, "a": [[[{"0": 42}]]]}, + {_id: 17, "a": [[{"0": [42]}]]}, + {_id: 18, "a": [[{"0": {"0": 42}}]]}, + {_id: 19, "a": [{"0": [[42]]}]}, + {_id: 20, "a": [{"0": [{"0": 42}]}]}, + {_id: 21, "a": [{"0": {"0": [42]}}]}, + {_id: 22, "a": [{"0": {"0": {"0": 42}}}]}, + {_id: 23, "a": {"0": [[[42]]]}}, + {_id: 24, "a": {"0": [[{"0": 42}]]}}, + {_id: 25, "a": {"0": [{"0": [42]}]}}, + {_id: 26, "a": {"0": [{"0": {"0": 42}}]}}, + {_id: 27, "a": {"0": {"0": [[42]]}}}, + {_id: 28, "a": {"0": {"0": [{"0": 42}]}}}, + {_id: 29, "a": {"0": {"0": {"0": [42]}}}}, + {_id: 30, "a": {"0": {"0": {"0": {"0": 42}}}}}, + ]; + + assert.sameMembers(res, expected); +} + +// Using $exists with true and false. +{ + let res = coll.find({"a.0": {$exists: false}}).toArray(); + let expected = [{_id: 0, "a": 42}]; + + assert.sameMembers(res, expected); + + res = coll.find({"a.0": {$exists: true}}).toArray(); + expected = [ + {_id: 1, "a": [42]}, + {_id: 2, "a": {"0": 42}}, + {_id: 3, "a": [[42]]}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 6, "a": {"0": {"0": 42}}}, + {_id: 7, "a": [[[42]]]}, + {_id: 8, "a": [[{"0": 42}]]}, + {_id: 9, "a": [{"0": [42]}]}, + {_id: 10, "a": [{"0": {"0": 42}}]}, + {_id: 11, "a": {"0": [[42]]}}, + {_id: 12, "a": {"0": [{"0": 42}]}}, + {_id: 13, "a": {"0": {"0": [42]}}}, + {_id: 14, "a": {"0": {"0": {"0": 42}}}}, + {_id: 15, "a": [[[[42]]]]}, + {_id: 16, "a": [[[{"0": 42}]]]}, + {_id: 17, "a": [[{"0": [42]}]]}, + {_id: 18, "a": [[{"0": {"0": 42}}]]}, + {_id: 19, "a": [{"0": [[42]]}]}, + {_id: 20, "a": [{"0": [{"0": 42}]}]}, + {_id: 21, "a": [{"0": {"0": [42]}}]}, + {_id: 22, "a": [{"0": {"0": {"0": 42}}}]}, + {_id: 23, "a": {"0": [[[42]]]}}, + {_id: 24, "a": {"0": [[{"0": 42}]]}}, + {_id: 25, "a": {"0": [{"0": [42]}]}}, + {_id: 26, "a": {"0": [{"0": {"0": 42}}]}}, + {_id: 27, "a": {"0": {"0": [[42]]}}}, + {_id: 28, "a": {"0": {"0": [{"0": 42}]}}}, + {_id: 29, "a": {"0": {"0": {"0": [42]}}}}, + {_id: 30, "a": {"0": {"0": {"0": {"0": 42}}}}}, + ]; + + assert.sameMembers(res, expected); +} + +// Using $elemMatch. +{ + const res = coll.find({a: {$elemMatch: {"0.0": {$eq: 42}}}}).toArray(); + const expected = [ + {_id: 7, "a": [[[42]]]}, + {_id: 8, "a": [[{"0": 42}]]}, + {_id: 9, "a": [{"0": [42]}]}, + {_id: 10, "a": [{"0": {"0": 42}}]}, + {_id: 16, "a": [[[{"0": 42}]]]}, + {_id: 17, "a": [[{"0": [42]}]]}, + {_id: 20, "a": [{"0": [{"0": 42}]}]}, + {_id: 21, "a": [{"0": {"0": [42]}}]}, + ]; + + assert.sameMembers(res, expected); +} + +// Using top-level $and. +{ + const res = coll.find({_id: {$lt: 15}, "a.0": {$gt: 41}}).toArray(); + const expected = [ + {_id: 1, "a": [42]}, + {_id: 2, "a": {"0": 42}}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 9, "a": [{"0": [42]}]}, + ]; + + assert.sameMembers(res, expected); +} + +// $all with equality +{ + const res = coll.find({"a.0": {$all: [42]}}).toArray(); + const expected = [ + {_id: 1, "a": [42]}, + {_id: 2, "a": {"0": 42}}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 9, "a": [{"0": [42]}]} + ]; + + assert.sameMembers(res, expected); +} + +// $all with $elemMatch +{ + const res = coll.find({"a.0": {$all: [{$elemMatch: {0: 42}}]}}).toArray(); + const expected = [ + {_id: 7, "a": [[[42]]]}, + {_id: 8, "a": [[{"0": 42}]]}, + {_id: 11, "a": {"0": [[42]]}}, + {_id: 12, "a": {"0": [{"0": 42}]}}, + {_id: 15, "a": [[[[42]]]]}, + {_id: 17, "a": [[{"0": [42]}]]}, + {_id: 19, "a": [{"0": [[42]]}]}, + {_id: 20, "a": [{"0": [{"0": 42}]}]}, + {_id: 23, "a": {"0": [[[42]]]}}, + {_id: 25, "a": {"0": [{"0": [42]}]}}, + ]; + + assert.sameMembers(res, expected); +} + +// Using an expression. +{ + const res = coll.find({"a.0": {$type: "number"}}).toArray(); + const expected = [ + {_id: 1, "a": [42]}, + {_id: 2, "a": {"0": 42}}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 9, "a": [{"0": [42]}]} + ]; + + assert.sameMembers(res, expected); +} + +{ + const res = coll.find({"a.0": {$mod: [42, 0]}}).toArray(); + const expected = [ + {_id: 1, "a": [42]}, + {_id: 2, "a": {"0": 42}}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 9, "a": [{"0": [42]}]} + ]; + + assert.sameMembers(res, expected); +} + +// In a $or. +{ + const res = coll.find({$or: [{"a.0": 42}, {"notARealField": 123}]}).toArray(); + const expected = [ + {_id: 1, "a": [42]}, + {_id: 2, "a": {"0": 42}}, + {_id: 4, "a": [{"0": 42}]}, + {_id: 5, "a": {"0": [42]}}, + {_id: 9, "a": [{"0": [42]}]} + ]; + + assert.sameMembers(res, expected); +} + +const coll2 = db.match_numeric_components2; +coll2.drop(); + +const kRegexDocs = + [{_id: 1, "b": "hello"}, {_id: 2, "b": {"0": "hello"}}, {_id: 3, "b": ["hello", "abc", "abc"]}]; + +assert.commandWorked(coll2.insert(kRegexDocs)); + +// Regexes are often something of a special case. +{ + const res = coll2.find({"b.0": {$regex: "hello"}}).toArray(); + const expected = [{_id: 2, "b": {"0": "hello"}}, {_id: 3, "b": ["hello", "abc", "abc"]}]; + + assert.sameMembers(res, expected); +} + +// $all with regexes. +{ + const res = coll2.find({"b.0": {$all: [/^hello/]}}).toArray(); + const expected = [{_id: 2, "b": {"0": "hello"}}, {_id: 3, "b": ["hello", "abc", "abc"]}]; + assert.sameMembers(res, expected); +} + +// $not with regex. +{ + const res = coll2.find({"b.0": {$not: /^h/}}).toArray(); + const expected = [{_id: 1, "b": "hello"}]; + assert.sameMembers(res, expected); +} + +// The tests below indirectly make sure that an index scan is not chosen when $elemMatch is +// against a indexed positional path component because it is not possible to generate index +// bounds from the $elemMatch conditions. If an index scan is chosen, then the corresponding +// queries would produce a wrong result. +// More precisely for an index like {"a.0": 1} and a document {a: [[1, 2, 3]]}, the nested array is +// not unwound during index key generation. That is, there is a single index key {"": [1, 2, 3]} +// rather than three separate index keys, {"": 1}, {"": 2}, {"": 3}. This precludes the ability to +// generate index bounds for $elemMatch predicates on "a.0" because "a.0" refers to the whole array +// [1, 2, 3], and not its individual members. +{ + // Test with $in. + assert(coll.drop()); + const expectedDoc = {_id: 42, "a": [["b"], ["c"]]}; + assert.commandWorked(coll.insert(expectedDoc)); + const query = {"a.0": {$elemMatch: {$in: ["b"]}}}; + + const res1 = coll.find(query).toArray(); + assert.commandWorked(coll.createIndex({"a.0": 1})); + const res2 = coll.find(query).toArray(); + assert.sameMembers([expectedDoc], res1); + assert.sameMembers([expectedDoc], res2); +} + +// Tests with equality. Add some data for the next few tests. +coll.drop(); +assert.commandWorked(coll.insert({_id: 0, f0: 'zz', f1: 5})); +assert.commandWorked(coll.insert({_id: 1, f0: 'zz', f1: [3, 5]})); +assert.commandWorked(coll.insert({_id: 4, f0: 'zz', f1: [3, 5, [7, 9]]})); +assert.commandWorked(coll.insert({_id: 2, f0: 'zz', f1: [[3, 5], [5, 7]]})); +assert.commandWorked(coll.insert({_id: 3, f0: 'zz', f1: [[[0], [3, 5]], [[0], [5, 7]]]})); + +{ + const res1 = coll.find({'f1.0': {$elemMatch: {$eq: 5}}}).toArray(); + const res2 = coll.find({'f1.0': {$elemMatch: {$eq: [3, 5]}}}).toArray(); + assert.commandWorked(coll.createIndex({'f1.0': 1})); + const res3 = coll.find({'f1.0': {$elemMatch: {$eq: 5}}}).toArray(); + const res4 = coll.find({'f1.0': {$elemMatch: {$eq: [3, 5]}}}).toArray(); + const expected1 = [{_id: 2, f0: 'zz', f1: [[3, 5], [5, 7]]}]; + const expected2 = [{_id: 3, f0: 'zz', f1: [[[0], [3, 5]], [[0], [5, 7]]]}]; + assert.sameMembers(expected1, res1); + assert.sameMembers(expected1, res3); + assert.sameMembers(expected2, res2); + assert.sameMembers(expected2, res4); + assert.commandWorked(coll.dropIndex({'f1.0': 1})); +} + +{ + // Compound index. + const res1 = coll.find({'f0': 'zz', 'f1.0': {$elemMatch: {$eq: 5}}}).toArray(); + assert.commandWorked(coll.createIndex({'f0': 1, 'f1.0': 1})); + const res2 = coll.find({'f0': 'zz', 'f1.0': {$elemMatch: {$eq: 5}}}).toArray(); + const expected = [{_id: 2, f0: 'zz', f1: [[3, 5], [5, 7]]}]; + assert.sameMembers(expected, res1); + assert.sameMembers(expected, res2); + assert.commandWorked(coll.dropIndex({'f0': 1, 'f1.0': 1})); +} + +{ + // Two-levels of array nesting. + const res1 = coll.find({'f1.0.1': {$elemMatch: {$eq: 3}}}).toArray(); + assert.commandWorked(coll.createIndex({'f1.0.1': 1})); + const res2 = coll.find({'f1.0.1': {$elemMatch: {$eq: 3}}}).toArray(); + const expected = [{_id: 3, f0: 'zz', f1: [[[0], [3, 5]], [[0], [5, 7]]]}]; + assert.sameMembers(expected, res1); + assert.sameMembers(expected, res2); +} + +{ + assert(coll.drop()); + assert.commandWorked(coll.createIndex({'f1.0': 1})); + assert.commandWorked(coll.insert({_id: 1, f1: [[42, 5], [77, 99]]})); + + const res1 = coll.find({'f1.0': {$elemMatch: {$eq: 5}}}).toArray(); + assert.sameMembers([{_id: 1, f1: [[42, 5], [77, 99]]}], res1); + + // Object with numeric field component, and no nested arrays. + assert.commandWorked(coll.insert({_id: 2, f1: {0: [42, 5], 1: [77, 99]}})); + const res2 = coll.find({'f1.0': {$elemMatch: {$eq: 5}}}).toArray(); + assert.sameMembers( + [{_id: 1, f1: [[42, 5], [77, 99]]}, {_id: 2, f1: {'0': [42, 5], '1': [77, 99]}}], res2); +} + +{ + assert(coll.drop()); + assert.commandWorked(coll.createIndex({'0': 1})); + + assert.commandWorked(coll.insert({_id: 1, '0': [42, 5]})); + const res1 = coll.find({'0': {$elemMatch: {$eq: 5}}}).toArray(); + assert.sameMembers([{'0': [42, 5], _id: 1}], res1); + + assert.commandWorked(coll.createIndex({'0.1': 1})); + assert.commandWorked(coll.insert({_id: 2, '0': {0: [42], 1: [5]}})); + const res2 = coll.find({'0.1': {$elemMatch: {$eq: 5}}}).toArray(); + assert.sameMembers([{'0': {'0': [42], '1': [5]}, _id: 2}], res2); +} +})(); diff --git a/src/mongo/db/query/planner_ixselect.cpp b/src/mongo/db/query/planner_ixselect.cpp index c6f1e093f12..3443ba16581 100644 --- a/src/mongo/db/query/planner_ixselect.cpp +++ b/src/mongo/db/query/planner_ixselect.cpp @@ -465,6 +465,16 @@ bool QueryPlannerIXSelect::_compatible(const BSONElement& keyPatternElt, newContext.fullPathToParentElemMatch = fullPathToNode; newContext.innermostParentElemMatch = static_cast<ElemMatchValueMatchExpression*>(node); + FieldRef path(fullPathToNode); + // If the index path has at least two components, and the last component of the path is + // numeric, this component could be an array index because the preceding path component + // may contain an array. Currently it is not known whether the preceding path component + // could be an array because indexes which positionally index array elements are not + // considered multikey. + if (path.numParts() > 1 && path.isNumericPathComponentStrict(path.numParts() - 1)) { + return false; + } + auto* children = node->getChildVector(); if (!std::all_of(children->begin(), children->end(), [&](MatchExpression* child) { const auto newPath = fullPathToNode.toString() + child->path(); diff --git a/src/mongo/db/query/query_planner_array_test.cpp b/src/mongo/db/query/query_planner_array_test.cpp index 718c6ad7444..a8ffff7fe58 100644 --- a/src/mongo/db/query/query_planner_array_test.cpp +++ b/src/mongo/db/query/query_planner_array_test.cpp @@ -2566,6 +2566,47 @@ TEST_F(QueryPlannerTest, "}}}}}}}"); } +// Test for older versions of indexes where it is possible to have empty MultikeyPaths, +// but still the index is considered multikey. In that case the index should not be +// considered to evaluate an $elemMatch predicate with a positional path component. +TEST_F(QueryPlannerTest, ElemMatchValueMultikeyIndexEmptyMultikeyPaths) { + addIndex(BSON("a.0" << 1), true); + runQuery(fromjson("{'a.0': {$elemMatch: {$eq: 42}}}")); + + assertNumSolutions(1U); + assertSolutionExists("{cscan: {dir: 1}}"); +} + +TEST_F(QueryPlannerTest, ElemMatchValuePositionalIndexPath1) { + MultikeyPaths multikeyPaths{std::set<size_t>{}}; + addIndex(BSON("f1.0" << 1), multikeyPaths); + runQuery(fromjson("{'f1.0': {$elemMatch: {$eq: 42}}}")); + + assertNumSolutions(1U); + assertSolutionExists("{cscan: {dir: 1}}"); +} + +TEST_F(QueryPlannerTest, ElemMatchValuePositionalIndexPath2) { + MultikeyPaths multikeyPaths{{0U}}; + addIndex(BSON("f1.0" << 1), multikeyPaths); + runQuery(fromjson("{'f1.0': {$elemMatch: {$eq: 42}}}")); + + assertNumSolutions(1U); + assertSolutionExists("{cscan: {dir: 1}}"); +} + +TEST_F(QueryPlannerTest, ElemMatchValuePositionalIndexPath3) { + MultikeyPaths multikeyPaths{std::set<size_t>{}}; + addIndex(BSON("0" << 1), multikeyPaths); + runQuery(fromjson("{'0': {$elemMatch: {$eq: 42}}}")); + + assertNumSolutions(2U); + assertSolutionExists("{cscan: {dir: 1}}"); + assertSolutionExists( + "{fetch: {node: {ixscan: {pattern: {'0': 1}, bounds: " + "{'0': [[42,42,true,true]]}}}}}"); +} + TEST_F(QueryPlannerTest, CompoundIndexBoundsDottedNotEqualsNullMultiKey) { const bool isMultiKey = true; addIndex(BSON("a.b" << 1 << "c.d" << 1), isMultiKey); |