summaryrefslogtreecommitdiff
path: root/jstests
diff options
context:
space:
mode:
authorAlya Berciu <alya.berciu@mongodb.com>2022-11-04 12:26:45 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-11-04 13:01:14 +0000
commitca6499eb6111f89758fcb71b319e8b5d0cd03a20 (patch)
tree620b426592d3883cfb1d21b6a3e6e8c65aec65fb /jstests
parentf06a898557f951bf48569012c4372e770d1c0267 (diff)
downloadmongo-ca6499eb6111f89758fcb71b319e8b5d0cd03a20.tar.gz
SERVER-70436 Handle covered $or null queries with regex
Diffstat (limited to 'jstests')
-rw-r--r--jstests/core/cover_null_queries.js322
-rw-r--r--jstests/core/null_query_semantics.js164
2 files changed, 440 insertions, 46 deletions
diff --git a/jstests/core/cover_null_queries.js b/jstests/core/cover_null_queries.js
index 7054a16039d..999ba3440a7 100644
--- a/jstests/core/cover_null_queries.js
+++ b/jstests/core/cover_null_queries.js
@@ -3,7 +3,9 @@
* @tags: [
* assumes_unsharded_collection,
* requires_non_retryable_writes,
- * requires_fcv_62,
+ * requires_fcv_60,
+ * # This test could produce unexpected explain output if additional indexes are created.
+ * assumes_no_implicit_index_creation,
* ]
*/
(function() {
@@ -58,12 +60,22 @@ function validateStages({cmdObj, expectedStages, isAgg}) {
*/
function validateFindCmdOutputAndPlan({filter, projection, expectedStages, expectedOutput}) {
const cmdObj = {find: coll.getName(), filter: filter, projection: projection};
+
+ // Compare index output with expected output.
if (expectedOutput) {
const res = assert.commandWorked(coll.runCommand(cmdObj));
const ouputArray = new DBCommandCursor(coll.getDB(), res).toArray();
assert(arrayEq(expectedOutput, ouputArray), ouputArray);
}
+
+ // Validate explain.
validateStages({cmdObj, expectedStages});
+
+ // Verify that we get the same output as we expect without an index.
+ const noIndexCmdObj = Object.assign(cmdObj, {hint: {$natural: 1}});
+ const resNoIndex = assert.commandWorked(coll.runCommand(noIndexCmdObj));
+ const noIndexOutArr = new DBCommandCursor(coll.getDB(), resNoIndex).toArray();
+ assert(arrayEq(expectedOutput, noIndexOutArr), noIndexOutArr);
}
/**
@@ -72,10 +84,18 @@ function validateFindCmdOutputAndPlan({filter, projection, expectedStages, expec
* are present in the plan returned.
*/
function validateSimpleCountCmdOutputAndPlan({filter, expectedStages, expectedCount}) {
+ // Compare index output with expected output.
const cmdObj = {count: coll.getName(), query: filter};
const res = assert.commandWorked(coll.runCommand(cmdObj));
assert.eq(res.n, expectedCount);
+
+ // Validate explain.
validateStages({cmdObj, expectedStages});
+
+ // Verify that we get the same output with and without an index.
+ const noIndexCmdObj = Object.assign(cmdObj, {hint: {$natural: 1}});
+ const resNoIndex = assert.commandWorked(coll.runCommand(noIndexCmdObj));
+ assert.eq(resNoIndex.n, expectedCount);
}
/**
@@ -89,11 +109,33 @@ function validateCountAggCmdOutputAndPlan({filter, expectedStages, expectedCount
pipeline: pipeline || [{$match: filter}, {$count: "count"}],
cursor: {},
};
+
+ // Compare index output with expected output.
const cmdRes = assert.commandWorked(coll.runCommand(cmdObj));
const countRes = cmdRes.cursor.firstBatch;
assert.eq(countRes.length, 1, cmdRes);
assert.eq(countRes[0].count, expectedCount, countRes);
+
+ // Validate explain.
validateStages({cmdObj, expectedStages, isAgg: true});
+
+ // Verify that we get the same output as we expect without an index.
+ const noIndexCmdObj = Object.assign(cmdObj, {hint: {$natural: 1}});
+ const resNoIndex = assert.commandWorked(coll.runCommand(noIndexCmdObj));
+ const countResNoIndex = resNoIndex.cursor.firstBatch;
+ assert.eq(countResNoIndex.length, 1, cmdRes);
+ assert.eq(countResNoIndex[0].count, expectedCount, countRes);
+}
+
+/**
+ * Same as above, but uses a $group count.
+ */
+function validateGroupCountAggCmdOutputAndPlan({filter, expectedStages, expectedCount}) {
+ validateCountAggCmdOutputAndPlan({
+ expectedStages,
+ expectedCount,
+ pipeline: [{$match: filter}, {$group: {_id: 0, count: {$count: {}}}}]
+ });
}
function getExpectedStagesIndexScanAndFetch(extraStages) {
@@ -162,7 +204,7 @@ validateFindCmdOutputAndPlan({
expectedStages: {"IXSCAN": 1, "FETCH": 0, "PROJECTION_COVERED": 1},
});
-// Same as above, but special case for null and empty array predicate.
+// We can cover a $in with null and an empty array predicate.
validateFindCmdOutputAndPlan({
filter: {a: {$in: [null, []]}},
projection: {_id: 1},
@@ -175,6 +217,21 @@ validateSimpleCountCmdOutputAndPlan({
expectedStages: {"IXSCAN": 1, "FETCH": 0},
});
+// We cannot cover a $in with null and an array predicate.
+// TODO SERVER-71058: It should be possible to cover this case and the more general case of matching
+// an array on a non-multikey index.
+validateFindCmdOutputAndPlan({
+ filter: {a: {$in: [null, ["a"]]}},
+ projection: {_id: 1},
+ expectedOutput: [{_id: 3}, {_id: 4}, {_id: 6}, {_id: 7}],
+ expectedStages: {"IXSCAN": 1, "FETCH": 1, "PROJECTION_SIMPLE": 1},
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {a: {$in: [null, ["a"]]}},
+ expectedCount: 4,
+ expectedStages: {"IXSCAN": 1, "FETCH": 1, "COUNT": 1},
+});
+
// Verify that a more complex projection that only relies on the _id field does not need a FETCH.
validateFindCmdOutputAndPlan({
filter: {a: null},
@@ -701,4 +758,265 @@ validateCountAggCmdOutputAndPlan({
expectedCount: 4,
expectedStages: {"OR": 1, "COUNT_SCAN": 2, "IXSCAN": 0, "FETCH": 0},
});
+
+// Validate that we can use the optimization when we have regex without array elements in a $in or
+// $or. See SERVER-70436 for more details.
+coll.drop();
+
+assert.commandWorked(coll.insertMany([
+ {_id: 1, a: '123456'},
+ {_id: 2, a: '1234567'},
+ {_id: 3, a: ' 12345678'},
+ {_id: 4, a: '444456'},
+ {_id: 5, a: ''},
+ {_id: 6, a: null},
+ {_id: 7},
+]));
+
+assert.commandWorked(coll.createIndex({a: 1, _id: 1}));
+
+// TODO SERVER-70998: Can apply optimization in case without regex; however, we still can't use a
+// COUNT_SCAN in this case.
+validateFindCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: ""}]},
+ projection: {_id: 1},
+ expectedOutput: [{_id: 5}, {_id: 6}, {_id: 7}],
+ expectedStages: {"IXSCAN": 1, "FETCH": 0, "PROJECTION_COVERED": 1},
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: ""}]},
+ expectedCount: 3,
+ expectedStages: {"COUNT": 1, "IXSCAN": 1, "FETCH": 0},
+});
+validateGroupCountAggCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: ""}]},
+ expectedCount: 3,
+ expectedStages: {"IXSCAN": 1, "FETCH": 0},
+});
+
+// Can still apply optimization when we have regex.
+validateFindCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: {$regex: "^$"}}]},
+ projection: {_id: 1},
+ expectedOutput: [{_id: 5}, {_id: 6}, {_id: 7}],
+ expectedStages: {"IXSCAN": 1, "FETCH": 0, "PROJECTION_COVERED": 1},
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: {$regex: "^$"}}]},
+ expectedCount: 3,
+ expectedStages: {"IXSCAN": 1, "FETCH": 0, "COUNT": 1},
+});
+validateGroupCountAggCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: {$regex: "^$"}}]},
+ expectedCount: 3,
+ expectedStages: {"IXSCAN": 1, "FETCH": 0},
+});
+
+// Now test case with a multikey index. We can't leverage the optimization here.
+assert.commandWorked(coll.insert({_id: 8, a: [1, 2, 3]}));
+assert.commandWorked(coll.insert({_id: 9, a: []}));
+
+validateFindCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: []}, {a: {$regex: "^$"}}]},
+ projection: {_id: 1},
+ expectedOutput: [{_id: 5}, {_id: 6}, {_id: 7}, {_id: 9}],
+ expectedStages: {"IXSCAN": 1, "FETCH": 1, "PROJECTION_SIMPLE": 1},
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: []}, {a: {$regex: "^$"}}]},
+ expectedCount: 4,
+ expectedStages: {"COUNT": 1, "IXSCAN": 1, "FETCH": 1},
+});
+validateGroupCountAggCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: []}, {a: {$regex: "^$"}}]},
+ expectedCount: 4,
+ expectedStages: {"IXSCAN": 1, "FETCH": 1},
+});
+
+// We also shouldn't cover queries on multikey indexes where $in includes an array, as we will still
+// need a filter after the IXSCAN to correctly return
+validateFindCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: []}, {a: [2]}]},
+ projection: {_id: 1},
+ expectedOutput: [{_id: 6}, {_id: 7}, {_id: 9}],
+ expectedStages: {"IXSCAN": 1, "FETCH": 1},
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: []}, {a: [2]}]},
+ expectedCount: 3,
+ expectedStages: {"IXSCAN": 1, "FETCH": 1},
+});
+validateGroupCountAggCmdOutputAndPlan({
+ filter: {$or: [{a: null}, {a: []}, {a: [2]}]},
+ expectedCount: 3,
+ expectedStages: {"IXSCAN": 1, "FETCH": 1},
+});
+
+// Validate that when we have a dotted path, we return the correct results for null queries.
+coll.drop();
+assert.commandWorked(coll.insertMany([
+ {_id: 1, a: 1},
+ {_id: 2, a: null},
+ {_id: 3},
+ {_id: 4, a: {b: 1}},
+ {_id: 5, a: {b: null}},
+ {_id: 6, a: {c: 1}},
+]));
+assert.commandWorked(coll.createIndex({"a.b": 1, _id: 1}));
+
+validateFindCmdOutputAndPlan({
+ filter: {"a.b": null},
+ projection: {_id: 1},
+ expectedOutput: [{_id: 1}, {_id: 2}, {_id: 3}, {_id: 5}, {_id: 6}],
+ expectedStages: {"IXSCAN": 1, "PROJECTION_COVERED": 1, "FETCH": 0},
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {"a.b": null},
+ expectedCount: 5,
+ expectedStages: {"OR": 1, "COUNT_SCAN": 2, "IXSCAN": 0, "FETCH": 0},
+});
+validateGroupCountAggCmdOutputAndPlan({
+ filter: {"a.b": null},
+ expectedCount: 5,
+ expectedStages: {"OR": 1, "COUNT_SCAN": 2, "IXSCAN": 0, "FETCH": 0},
+});
+
+validateFindCmdOutputAndPlan({
+ filter: {a: {b: null}},
+ projection: {_id: 1},
+ expectedOutput: [{_id: 5}],
+ expectedStages: {"COLLSCAN": 1, "PROJECTION_SIMPLE": 1},
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {a: {b: null}},
+ expectedCount: 1,
+ expectedStages: {"COLLSCAN": 1},
+});
+validateGroupCountAggCmdOutputAndPlan({
+ filter: {a: {b: null}},
+ expectedCount: 1,
+ expectedStages: {"COLLSCAN": 1},
+});
+
+// Still need fetch if we don't have a sufficiently restrictive projection.
+validateFindCmdOutputAndPlan({
+ filter: {"a.b": null},
+ projection: {_id: 1, a: 1},
+ expectedOutput: [
+ {_id: 1, a: 1},
+ {_id: 2, a: null},
+ {_id: 3},
+ {_id: 5, a: {b: null}},
+ {_id: 6, a: {c: 1}},
+ ],
+ expectedStages: {"IXSCAN": 1, "FETCH": 1},
+});
+
+// Make index multikey, and test case where field b is nested in an array.
+assert.commandWorked(coll.insertMany([
+ {_id: 7, a: [{b: null}]},
+ {_id: 8, a: [{b: []}]},
+ {_id: 9, a: [{b: [1, 2, 3]}]},
+ {_id: 10, a: [{b: 123}]},
+ {_id: 11, a: [{c: 123}]},
+ {_id: 12, a: []},
+ {_id: 13, a: [{}]},
+ {_id: 14, a: [1, 2, 3]},
+ {_id: 15, a: [{b: 1}, {c: 2}, {b: 3}]},
+ {_id: 16, a: [null]},
+]));
+
+validateFindCmdOutputAndPlan({
+ filter: {"a.b": null},
+ projection: {_id: 1},
+ expectedOutput: [
+ {_id: 1},
+ {_id: 2},
+ {_id: 3},
+ {_id: 5},
+ {_id: 6},
+ {_id: 7},
+ {_id: 11},
+ {_id: 13},
+ {_id: 15}
+ ],
+ expectedStages: {"IXSCAN": 1, "PROJECTION_SIMPLE": 1, "FETCH": 1},
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {"a.b": null},
+ expectedCount: 9,
+ expectedStages: {"COUNT": 1, "IXSCAN": 1, "FETCH": 1},
+});
+validateGroupCountAggCmdOutputAndPlan({
+ filter: {"a.b": null},
+ expectedCount: 9,
+ expectedStages: {"IXSCAN": 1, "FETCH": 1},
+});
+
+validateFindCmdOutputAndPlan({
+ filter: {a: {b: null}},
+ projection: {_id: 1},
+ expectedOutput: [{_id: 5}, {_id: 7}],
+ expectedStages: {
+ "COLLSCAN": 1,
+ "PROJECTION_SIMPLE": 1,
+ },
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {a: {b: null}},
+ expectedCount: 2,
+ expectedStages: {"COLLSCAN": 1},
+});
+validateGroupCountAggCmdOutputAndPlan({
+ filter: {a: {b: null}},
+ expectedCount: 2,
+ expectedStages: {"COLLSCAN": 1},
+});
+
+validateFindCmdOutputAndPlan({
+ filter: {a: [{b: null}]},
+ projection: {_id: 1},
+ expectedOutput: [{_id: 7}],
+ expectedStages: {"COLLSCAN": 1, "PROJECTION_SIMPLE": 1},
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {a: [{b: null}]},
+ expectedCount: 1,
+ expectedStages: {"COLLSCAN": 1},
+});
+validateGroupCountAggCmdOutputAndPlan({
+ filter: {a: [{b: null}]},
+ expectedCount: 1,
+ expectedStages: {"COLLSCAN": 1},
+});
+
+// We still need a FETCH for composite paths, because both {a: [1,2,3]} and {"a.b": null} generate
+// null index keys, but the former should not match the predicate below.
+validateFindCmdOutputAndPlan({
+ filter: {"a.b": {$in: [null, []]}},
+ projection: {_id: 1},
+ expectedOutput: [
+ {_id: 1},
+ {_id: 2},
+ {_id: 3},
+ {_id: 5},
+ {_id: 6},
+ {_id: 7},
+ {_id: 8},
+ {_id: 11},
+ {_id: 13},
+ {_id: 15}
+ ],
+ expectedStages: {"IXSCAN": 1, "PROJECTION_SIMPLE": 1, "FETCH": 1},
+});
+validateSimpleCountCmdOutputAndPlan({
+ filter: {"a.b": {$in: [null, []]}},
+ expectedCount: 10,
+ expectedStages: {"IXSCAN": 1, "FETCH": 1},
+});
+validateGroupCountAggCmdOutputAndPlan({
+ filter: {"a.b": {$in: [null, []]}},
+ expectedCount: 10,
+ expectedStages: {"IXSCAN": 1, "FETCH": 1},
+});
})();
diff --git a/jstests/core/null_query_semantics.js b/jstests/core/null_query_semantics.js
index cf4cc836412..1d84dd0888e 100644
--- a/jstests/core/null_query_semantics.js
+++ b/jstests/core/null_query_semantics.js
@@ -47,6 +47,9 @@ function testNullSemantics(coll) {
[{_id: "a_null", a: null}, {_id: "a_undefined", a: undefined}, {_id: "no_a"}];
assert(resultsEq(expected, noProjectResults), tojson(noProjectResults));
+ const count = coll.count({a: {$eq: null}});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find({a: {$eq: null}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
@@ -63,6 +66,9 @@ function testNullSemantics(coll) {
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+ const count = coll.count({a: {$ne: null}});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find({a: {$ne: null}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
@@ -78,6 +84,9 @@ function testNullSemantics(coll) {
{_id: "no_a"},
];
+ const count = coll.count(query);
+ assert.eq(count, expected.length);
+
assert(resultsEq(noProjectResults, expected), noProjectResults);
const projectResults = coll.find(query, projectToOnlyA).toArray();
@@ -95,6 +104,9 @@ function testNullSemantics(coll) {
{_id: "no_a"},
];
+ const count = coll.count(query);
+ assert.eq(count, expected.length);
+
assert(resultsEq(noProjectResults, expected), noProjectResults);
const projectResults = coll.find(query, projectToOnlyA).toArray();
@@ -112,6 +124,9 @@ function testNullSemantics(coll) {
{_id: "a_subobject_b_undefined", a: {b: undefined}},
];
+ const count = coll.count(query);
+ assert.eq(count, expected.length);
+
assert(resultsEq(noProjectResults, expected), noProjectResults);
const projectResults = coll.find(query, projectToOnlyA).toArray();
@@ -132,6 +147,9 @@ function testNullSemantics(coll) {
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+ const count = coll.count(query);
+ assert.eq(count, expected.length);
+
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), projectResults);
}());
@@ -143,6 +161,9 @@ function testNullSemantics(coll) {
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+ const count = coll.count({a: {$exists: false}});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find({a: {$exists: false}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
@@ -150,17 +171,19 @@ function testNullSemantics(coll) {
// Test the semantics of the query {"a.b": {$eq: null}}.
(function testDottedEqualsNull() {
const noProjectResults = coll.find({"a.b": {$eq: null}}).toArray();
- assert(resultsEq(noProjectResults,
- [
- {_id: "a_empty_subobject", a: {}},
- {_id: "a_null", a: null},
- {_id: "a_number", a: 4},
- {_id: "a_subobject_b_null", a: {b: null}},
- {_id: "a_subobject_b_undefined", a: {b: undefined}},
- {_id: "a_undefined", a: undefined},
- {_id: "no_a"}
- ]),
- tojson(noProjectResults));
+ const expected = [
+ {_id: "a_empty_subobject", a: {}},
+ {_id: "a_null", a: null},
+ {_id: "a_number", a: 4},
+ {_id: "a_subobject_b_null", a: {b: null}},
+ {_id: "a_subobject_b_undefined", a: {b: undefined}},
+ {_id: "a_undefined", a: undefined},
+ {_id: "no_a"}
+ ];
+ assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+
+ const count = coll.count({"a.b": {$eq: null}});
+ assert.eq(count, expected.length);
const projectResults = coll.find({"a.b": {$eq: null}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults,
@@ -171,8 +194,11 @@ function testNullSemantics(coll) {
// Test the semantics of the query {"a.b": {$ne: null}}.
(function testDottedNotEqualsNull() {
const noProjectResults = coll.find({"a.b": {$ne: null}}).toArray();
- assert(resultsEq(noProjectResults, [{_id: "a_subobject_b_not_null", a: {b: "hi"}}]),
- tojson(noProjectResults));
+ const expected = [{_id: "a_subobject_b_not_null", a: {b: "hi"}}];
+ assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+
+ const count = coll.count({"a.b": {$ne: null}});
+ assert.eq(count, expected.length);
const projectResults = coll.find({"a.b": {$ne: null}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults, [{a: {b: "hi"}}]), tojson(projectResults));
@@ -189,6 +215,9 @@ function testNullSemantics(coll) {
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+ const count = coll.count({"a.b": {$exists: false}});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find({"a.b": {$exists: false}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults, [{}, {a: {}}, {}, {}, {}]), tojson(projectResults));
}());
@@ -208,6 +237,9 @@ function testNullSemantics(coll) {
`Expected no results for query ${tojson(elemMatchQuery)}, got ` +
tojson(noProjectResults));
+ const count = coll.count(elemMatchQuery);
+ assert.eq(count, 0);
+
let projectResults = coll.find(elemMatchQuery, projectToOnlyA).toArray();
assert(resultsEq(projectResults, []),
`Expected no results for query ${tojson(elemMatchQuery)}, got ` +
@@ -256,6 +288,9 @@ function testNullSemantics(coll) {
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+ const count = coll.count({a: {$eq: null}});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find({a: {$eq: null}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
@@ -280,6 +315,9 @@ function testNullSemantics(coll) {
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+ const count = coll.count({a: {$ne: null}});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find({a: {$ne: null}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
@@ -303,6 +341,8 @@ function testNullSemantics(coll) {
{_id: "a_value_array_no_nulls", a: [1, "string", 4]},
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+ const count = coll.count({a: {$not: {$gte: null}}});
+ assert.eq(count, expected.length);
}());
// Test the semantics of the query {a: {$in: [null, <number>]}}.
@@ -320,6 +360,9 @@ function testNullSemantics(coll) {
assert(resultsEq(noProjectResults, expected), noProjectResults);
+ const count = coll.count({a: {$in: [null, 75]}});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), projectResults);
}());
@@ -339,6 +382,9 @@ function testNullSemantics(coll) {
assert(resultsEq(noProjectResults, expected), noProjectResults);
+ const count = coll.count({$or: [{a: null}, {a: 75}]});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), projectResults);
}());
@@ -366,6 +412,9 @@ function testNullSemantics(coll) {
assert(resultsEq(noProjectResults, expected), noProjectResults);
+ const count = coll.count({a: {$nin: [null, 75]}});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), projectResults);
}());
@@ -401,6 +450,9 @@ function testNullSemantics(coll) {
assert(resultsEq(noProjectResults, expected), noProjectResults);
+ const count = coll.count({a: {$nin: [null, []]}});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), projectResults);
}());
@@ -426,6 +478,9 @@ function testNullSemantics(coll) {
assert(resultsEq(noProjectResults, expected), noProjectResults);
+ const count = coll.count(query);
+ assert.eq(count, expected.length);
+
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), projectResults);
}());
@@ -441,6 +496,9 @@ function testNullSemantics(coll) {
];
assert(resultsEq(noProjectResults, expectedEqualToNull), tojson(noProjectResults));
+ const count = coll.count({a: {$elemMatch: {$eq: null}}});
+ assert.eq(count, expectedEqualToNull.length);
+
let projectResults = coll.find({a: {$elemMatch: {$eq: null}}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expectedEqualToNull)),
tojson(projectResults));
@@ -473,25 +531,23 @@ function testNullSemantics(coll) {
// value for "b".
(function testDottedEqualsNull() {
const noProjectResults = coll.find({"a.b": {$eq: null}}).toArray();
- assert(
- resultsEq(noProjectResults,
- [
- {_id: "a_empty_subobject", a: {}},
- {_id: "a_null", a: null},
- {_id: "a_number", a: 4},
- {_id: "a_subobject_b_null", a: {b: null}},
- {_id: "a_subobject_b_undefined", a: {b: undefined}},
- {_id: "a_undefined", a: undefined},
- {_id: "no_a"},
- {
- _id: "a_object_array_all_b_nulls",
- a: [{b: null}, {b: undefined}, {b: null}, {}]
- },
- {_id: "a_object_array_some_b_nulls", a: [{b: null}, {b: 3}, {b: null}]},
- {_id: "a_object_array_some_b_undefined", a: [{b: undefined}, {b: 3}]},
- {_id: "a_object_array_some_b_missing", a: [{b: 3}, {}]},
- ]),
- tojson(noProjectResults));
+ const expected = [
+ {_id: "a_empty_subobject", a: {}},
+ {_id: "a_null", a: null},
+ {_id: "a_number", a: 4},
+ {_id: "a_subobject_b_null", a: {b: null}},
+ {_id: "a_subobject_b_undefined", a: {b: undefined}},
+ {_id: "a_undefined", a: undefined},
+ {_id: "no_a"},
+ {_id: "a_object_array_all_b_nulls", a: [{b: null}, {b: undefined}, {b: null}, {}]},
+ {_id: "a_object_array_some_b_nulls", a: [{b: null}, {b: 3}, {b: null}]},
+ {_id: "a_object_array_some_b_undefined", a: [{b: undefined}, {b: 3}]},
+ {_id: "a_object_array_some_b_missing", a: [{b: 3}, {}]},
+ ];
+ assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+
+ const count = coll.count({"a.b": {$eq: null}});
+ assert.eq(count, expected.length);
const projectResults = coll.find({"a.b": {$eq: null}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults,
@@ -514,18 +570,20 @@ function testNullSemantics(coll) {
// Test the semantics of the query {"a.b": {$ne: null}}.
(function testDottedNotEqualsNull() {
const noProjectResults = coll.find({"a.b": {$ne: null}}).toArray();
- assert(resultsEq(noProjectResults,
- [
- {_id: "a_subobject_b_not_null", a: {b: "hi"}},
- {_id: "a_double_array", a: [[]]},
- {_id: "a_empty_array", a: []},
- {_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]},
- {_id: "a_value_array_all_nulls", a: [null, null]},
- {_id: "a_value_array_no_nulls", a: [1, "string", 4]},
- {_id: "a_value_array_with_null", a: [1, "string", null, 4]},
- {_id: "a_value_array_with_undefined", a: [1, "string", undefined, 4]}
- ]),
- tojson(noProjectResults));
+ const expected = [
+ {_id: "a_subobject_b_not_null", a: {b: "hi"}},
+ {_id: "a_double_array", a: [[]]},
+ {_id: "a_empty_array", a: []},
+ {_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]},
+ {_id: "a_value_array_all_nulls", a: [null, null]},
+ {_id: "a_value_array_no_nulls", a: [1, "string", 4]},
+ {_id: "a_value_array_with_null", a: [1, "string", null, 4]},
+ {_id: "a_value_array_with_undefined", a: [1, "string", undefined, 4]}
+ ];
+ assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
+
+ const count = coll.count({"a.b": {$ne: null}});
+ assert.eq(count, expected.length);
const projectResults = coll.find({"a.b": {$ne: null}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults,
@@ -560,6 +618,9 @@ function testNullSemantics(coll) {
{_id: "a_object_array_some_b_missing", a: [{b: 3}, {}]},
];
+ const count = coll.count({"a.b": {$in: [null, 75]}});
+ assert.eq(count, expected.length);
+
assert(resultsEq(noProjectResults, expected), noProjectResults);
}());
@@ -581,6 +642,9 @@ function testNullSemantics(coll) {
{_id: "a_object_array_some_b_missing", a: [{b: 3}, {}]},
];
+ const count = coll.count({$or: [{"a.b": null}, {"a.b": 75}]});
+ assert.eq(count, expected.length);
+
assert(resultsEq(noProjectResults, expected), noProjectResults);
}());
@@ -601,6 +665,9 @@ function testNullSemantics(coll) {
assert(resultsEq(noProjectResults, expected), noProjectResults);
+ const count = coll.count({"a.b": {$nin: [null, 75]}});
+ assert.eq(count, expected.length);
+
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), projectResults);
}());
@@ -619,6 +686,9 @@ function testNullSemantics(coll) {
{_id: "a_value_array_with_undefined", a: [1, "string", undefined, 4]},
];
+ const count = coll.count({"a.b": {$nin: [null, /^str.*/]}});
+ assert.eq(count, expected.length);
+
assert(resultsEq(noProjectResults, expected), noProjectResults);
const projectResults = coll.find(query, projectToOnlyA).toArray();
@@ -631,6 +701,9 @@ function testNullSemantics(coll) {
let results = coll.find({"a.b": {$elemMatch: {$eq: null}}}).toArray();
assert(resultsEq(results, []), tojson(results));
+ const count = coll.count({"a.b": {$elemMatch: {$eq: null}}});
+ assert.eq(count, 0);
+
results = coll.find({"a.b": {$elemMatch: {$ne: null}}}).toArray();
assert(resultsEq(results, []), tojson(results));
}());
@@ -648,6 +721,9 @@ function testNullSemantics(coll) {
];
assert(resultsEq(noProjectResults, expectedEqualToNull), tojson(noProjectResults));
+ const count = coll.count({a: {$elemMatch: {b: {$eq: null}}}});
+ assert.eq(count, expectedEqualToNull.length);
+
let projectResults =
coll.find({a: {$elemMatch: {b: {$eq: null}}}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults,