summaryrefslogtreecommitdiff
path: root/jstests/core/match_numeric_components.js
diff options
context:
space:
mode:
authorTimour Katchaounov <timour.katchaounov@mongodb.com>2021-11-22 11:41:21 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-11-22 12:25:57 +0000
commit90699509e15b33fda10832e79efcd158aee1f0eb (patch)
tree06b9188ed370cb2e971ff1891bddc95223099c8b /jstests/core/match_numeric_components.js
parentb4aca2363c2e59789643c9132e764e72e86b3996 (diff)
downloadmongo-90699509e15b33fda10832e79efcd158aee1f0eb.tar.gz
SERVER-57588 Inconsistent query results when an array position is indexed whose value is an array
Diffstat (limited to 'jstests/core/match_numeric_components.js')
-rw-r--r--jstests/core/match_numeric_components.js96
1 files changed, 96 insertions, 0 deletions
diff --git a/jstests/core/match_numeric_components.js b/jstests/core/match_numeric_components.js
index 07a58507b8e..7f20b3710ab 100644
--- a/jstests/core/match_numeric_components.js
+++ b/jstests/core/match_numeric_components.js
@@ -350,4 +350,100 @@ assert.commandWorked(coll2.insert(kRegexDocs));
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);
+}
})();