mongo/jstests/core/null_query_semantics.js

653 lines
31 KiB
JavaScript

// Tests the behavior of queries with a {$eq: null} or {$ne: null} predicate.
(function() {
"use strict";
load("jstests/aggregation/extras/utils.js"); // For 'resultsEq'.
const coll = db.not_equals_null;
coll.drop();
function extractAValues(results) {
return results.map(function(res) {
if (!res.hasOwnProperty("a")) {
return {};
}
return {a: res.a};
});
}
function testNotEqualsNullSemantics() {
// For the first portion of the test, only insert documents without arrays. This will avoid
// making the indexes multi-key, which may allow an index to be used to answer the queries.
assert.commandWorked(coll.insert([
{_id: "a_empty_subobject", a: {}},
{_id: "a_null", a: null},
{_id: "a_number", a: 4},
{_id: "a_subobject_b_not_null", a: {b: "hi"}},
{_id: "a_subobject_b_null", a: {b: null}},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
{_id: "a_undefined", a: undefined},
{_id: "no_a"},
]));
// Throughout this test we will run queries with a projection which may allow the planner to
// consider an index-only plan. Checking the results of those queries will test that the
// query system will never choose such an optimization if it is incorrect.
const projectToOnlyA = {_id: 0, a: 1};
const projectToOnlyADotB = {_id: 0, "a.b": 1};
// Test the semantics of the query {a: {$eq: null}}.
(function testBasicNullQuery() {
const noProjectResults = coll.find({a: {$eq: null}}).toArray();
const expected =
[{_id: "a_null", a: null}, {_id: "a_undefined", a: undefined}, {_id: "no_a"}];
assert(resultsEq(expected, noProjectResults), tojson(noProjectResults));
const projectResults = coll.find({a: {$eq: null}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
// Test the semantics of the query {a: {$ne: null}}.
(function testBasicNotEqualsNullQuery() {
const noProjectResults = coll.find({a: {$ne: null}}).toArray();
const expected = [
{_id: "a_empty_subobject", a: {}},
{_id: "a_number", a: 4},
{_id: "a_subobject_b_not_null", a: {b: "hi"}},
{_id: "a_subobject_b_null", a: {b: null}},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
const projectResults = coll.find({a: {$ne: null}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
// Test the semantics of the query {a: {$nin: [null, <number>]}}.
(function testNotInNullQuery() {
const query = {a: {$nin: [null, 4]}};
const noProjectResults = coll.find(query).toArray();
const expected = [
{_id: "a_empty_subobject", a: {}},
{_id: "a_subobject_b_not_null", a: {b: "hi"}},
{_id: "a_subobject_b_null", a: {b: null}},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
];
// TODO: SERVER-21929: $in may (or may not) miss fields with value "undefined".
const expectedWithUndefined = expected.concat([
{_id: "a_undefined", a: undefined},
]);
assert(resultsEq(noProjectResults, expected) ||
resultsEq(noProjectResults, expectedWithUndefined),
noProjectResults);
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)) ||
resultsEq(projectResults, extractAValues(expectedWithUndefined)),
projectResults);
}());
(function testNotInNullAndRegexQuery() {
// While $nin: [null, ...] can be indexed, $nin: [<regex>] cannot. Ensure that we get
// the correct results in this case.
const query = {a: {$nin: [null, /^hi.*/]}};
const noProjectResults = coll.find(query).toArray();
const expected = [
{_id: "a_empty_subobject", a: {}},
{_id: "a_empty_subobject", a: 4},
{_id: "a_subobject_b_not_null", a: {b: "hi"}},
{_id: "a_subobject_b_null", a: {b: null}},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
// TODO: SERVER-21929: $in may (or may not) miss fields with value "undefined".
{_id: "a_undefined", a: undefined},
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
(function testExistsFalse() {
const noProjectResults = coll.find({a: {$exists: false}}).toArray();
const expected = [
{_id: "no_a"},
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
const projectResults = coll.find({a: {$exists: false}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
// 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 projectResults = coll.find({"a.b": {$eq: null}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults,
[{a: {}}, {}, {}, {a: {b: null}}, {a: {b: undefined}}, {}, {}]),
tojson(projectResults));
}());
// 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 projectResults = coll.find({"a.b": {$ne: null}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults, [{a: {b: "hi"}}]), tojson(projectResults));
}());
(function testDottedExistsFalse() {
const noProjectResults = coll.find({"a.b": {$exists: false}}).toArray();
const expected = [
{_id: "no_a"},
{_id: "a_empty_subobject", a: {}},
{_id: "a_null", a: null},
{_id: "a_number", a: 4},
{_id: "a_undefined", a: undefined},
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
const projectResults = coll.find({"a.b": {$exists: false}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults, [{}, {a: {}}, {}, {}, {}]), tojson(projectResults));
}());
// Test similar queries, but with an $elemMatch. These queries should have no results since
// an $elemMatch requires an array.
(function testElemMatchQueriesWithNoArrays() {
for (let elemMatchQuery of [{a: {$elemMatch: {$eq: null}}},
{a: {$elemMatch: {$ne: null}}},
{"a.b": {$elemMatch: {$eq: null}}},
{"a.b": {$elemMatch: {$ne: null}}},
{a: {$elemMatch: {b: {$eq: null}}}},
{a: {$elemMatch: {b: {$ne: null}}}},
]) {
const noProjectResults = coll.find(elemMatchQuery).toArray();
assert(resultsEq(noProjectResults, []),
`Expected no results for query ${tojson(elemMatchQuery)}, got ` +
tojson(noProjectResults));
let projectResults = coll.find(elemMatchQuery, projectToOnlyA).toArray();
assert(resultsEq(projectResults, []),
`Expected no results for query ${tojson(elemMatchQuery)}, got ` +
tojson(projectResults));
projectResults = coll.find(elemMatchQuery, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults, []),
`Expected no results for query ${tojson(elemMatchQuery)}, got ` +
tojson(projectResults));
}
}());
// An index which includes "a" or a sub-path of "a" will become multi-key after this insert.
const writeResult = coll.insert([
{_id: "a_double_array", a: [[]]},
{_id: "a_empty_array", a: []},
{_id: "a_object_array_all_b_nulls", a: [{b: null}, {b: undefined}, {b: null}, {}]},
{_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]},
{_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}, {}]},
{_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]},
]);
if (writeResult.hasWriteErrors()) {
// We're testing a hashed index which is incompatible with arrays. Skip the multi-key
// portion of this test for this index.
assert.eq(writeResult.getWriteErrors().length, 1, tojson(writeResult));
assert.eq(writeResult.getWriteErrors()[0].code, 16766, tojson(writeResult));
return;
}
assert.commandWorked(writeResult);
// Test the semantics of the query {a: {$eq: null}}.
(function testBasicNullQuery() {
const noProjectResults = coll.find({a: {$eq: null}}).toArray();
const expected = [
{_id: "a_null", a: null},
{_id: "a_undefined", a: undefined},
{_id: "a_value_array_all_nulls", a: [null, null]},
{_id: "a_value_array_with_null", a: [1, "string", null, 4]},
{_id: "a_value_array_with_undefined", a: [1, "string", undefined, 4]},
{_id: "no_a"},
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
const projectResults = coll.find({a: {$eq: null}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
// Test the semantics of the query {a: {$ne: null}}.
(function testBasicNotEqualsNullQuery() {
const noProjectResults = coll.find({a: {$ne: null}}).toArray();
const expected = [
{_id: "a_double_array", a: [[]]},
{_id: "a_empty_array", a: []},
{_id: "a_empty_subobject", a: {}},
{_id: "a_number", a: 4},
{_id: "a_object_array_all_b_nulls", a: [{b: null}, {b: undefined}, {b: null}, {}]},
{_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]},
{_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}, {}]},
{_id: "a_subobject_b_not_null", a: {b: "hi"}},
{_id: "a_subobject_b_null", a: {b: null}},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
{_id: "a_value_array_no_nulls", a: [1, "string", 4]},
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
const projectResults = coll.find({a: {$ne: null}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), tojson(projectResults));
}());
// Test the semantics of the query {a: {$not: {$gte: null}}}.
(function testBasicNotEqualsGTENullQuery() {
const noProjectResults = coll.find({a: {$not: {$gte: null}}}).toArray();
const expected = [
{_id: "a_double_array", a: [[]]},
{_id: "a_empty_array", a: []},
{_id: "a_empty_subobject", a: {}},
{_id: "a_number", a: 4},
{_id: "a_object_array_all_b_nulls", a: [{b: null}, {b: undefined}, {b: null}, {}]},
{_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]},
{_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}, {}]},
{_id: "a_subobject_b_not_null", a: {b: "hi"}},
{_id: "a_subobject_b_null", a: {b: null}},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
{_id: "a_value_array_no_nulls", a: [1, "string", 4]},
];
assert(resultsEq(noProjectResults, expected), tojson(noProjectResults));
}());
// Test the semantics of the query {a: {$nin: [null, <number>]}}.
(function testNotInNullQuery() {
const query = {a: {$nin: [null, 75]}};
const noProjectResults = coll.find(query).toArray();
const expected = [
{_id: "a_empty_subobject", a: {}},
{_id: "a_number", a: 4},
{_id: "a_subobject_b_not_null", a: {b: "hi"}},
{_id: "a_subobject_b_null", a: {b: null}},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
{_id: "a_double_array", a: [[]]},
{_id: "a_empty_array", a: []},
{_id: "a_object_array_all_b_nulls", a: [{b: null}, {b: undefined}, {b: null}, {}]},
{_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]},
{_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}, {}]},
{_id: "a_value_array_no_nulls", a: [1, "string", 4]},
];
// TODO: SERVER-21929: $in may (or may not) miss fields with value "undefined".
const expectedWithUndefined = expected.concat([
{_id: "a_undefined", a: undefined},
{_id: "a_value_array_with_undefined", a: [1, "string", undefined, 4]},
]);
assert(resultsEq(noProjectResults, expected) ||
resultsEq(noProjectResults, expectedWithUndefined),
noProjectResults);
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)) ||
resultsEq(projectResults, extractAValues(expectedWithUndefined)),
projectResults);
}());
// Test the semantics of the query {a: {$nin: [null, []]}}.
(function testNotInNullAndEmptyArrayQuery() {
const query = {a: {$nin: [null, []]}};
const noProjectResults = coll.find(query).toArray();
const expected = [
// Documents excluded by the query predicate are commented
// out below.
{_id: "a_empty_subobject", a: {}},
//{_id: "a_null", a: null},
{_id: "a_number", a: 4},
{_id: "a_subobject_b_not_null", a: {b: "hi"}},
{_id: "a_subobject_b_null", a: {b: null}},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
// Semantic difference during backport: this document doesn't match "null" in this
// version (c.f. SERVER-21929).
{_id: "a_undefined", a: undefined},
//{_id: "no_a"},
//{_id: "a_double_array", a: [[]]},
//{_id: "a_empty_array", a: []},
{_id: "a_object_array_all_b_nulls", a: [{b: null}, {b: undefined}, {b: null}, {}]},
{_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]},
{_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}, {}]},
//{_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]},
// Semantic difference during backport: this document doesn't match "null" in this
// version (c.f. SERVER-21929).
{_id: "a_value_array_with_undefined", a: [1, "string", undefined, 4]},
];
assert(resultsEq(noProjectResults, expected), noProjectResults);
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)), projectResults);
}());
(function testNotInNullAndRegexQuery() {
const query = {a: {$nin: [null, /^str.*/]}};
const noProjectResults = coll.find(query).toArray();
const expected = [
{_id: "a_empty_subobject", a: {}},
{_id: "a_number", a: 4},
{_id: "a_subobject_b_not_null", a: {b: "hi"}},
{_id: "a_subobject_b_null", a: {b: null}},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
{_id: "a_double_array", a: [[]]},
{_id: "a_empty_array", a: []},
{_id: "a_object_array_all_b_nulls", a: [{b: null}, {b: undefined}, {b: null}, {}]},
{_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]},
{_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}, {}]},
];
// TODO: SERVER-21929: $in may (or may not) miss fields with value "undefined".
const expectedWithUndefined = expected.concat([
{_id: "a_undefined", a: undefined},
]);
assert(resultsEq(noProjectResults, expected) ||
resultsEq(noProjectResults, expectedWithUndefined),
noProjectResults);
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)) ||
resultsEq(projectResults, extractAValues(expectedWithUndefined)),
projectResults);
}());
// Test the results of similar queries with an $elemMatch.
(function testElemMatchValue() {
// Test $elemMatch with equality to null.
let noProjectResults = coll.find({a: {$elemMatch: {$eq: null}}}).toArray();
const expectedEqualToNull = [
{_id: "a_value_array_all_nulls", a: [null, null]},
{_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, expectedEqualToNull), tojson(noProjectResults));
let projectResults = coll.find({a: {$elemMatch: {$eq: null}}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expectedEqualToNull)),
tojson(projectResults));
// Test $elemMatch with not equal to null.
noProjectResults = coll.find({a: {$elemMatch: {$ne: null}}}).toArray();
const expectedNotEqualToNull = [
{_id: "a_double_array", a: [[]]},
{_id: "a_object_array_all_b_nulls", a: [{b: null}, {b: undefined}, {b: null}, {}]},
{_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]},
{_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}, {}]},
{_id: "a_value_array_no_nulls", a: [1, "string", 4]},
{_id: "a_value_array_with_undefined", a: [1, "string", undefined, 4]},
{_id: "a_value_array_with_null", a: [1, "string", null, 4]},
];
assert(resultsEq(noProjectResults, expectedNotEqualToNull), tojson(noProjectResults));
projectResults = coll.find({a: {$elemMatch: {$ne: null}}}, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expectedNotEqualToNull)),
tojson(projectResults));
}());
// Test the semantics of the query {"a.b": {$eq: null}}. The semantics here are to return
// those documents which have one of the following properties:
// - A non-object, non-array value for "a"
// - A subobject "a" with a missing, null, or undefined value for "b"
// - An array which has at least one object in it which has a missing, null, or undefined
// 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 projectResults = coll.find({"a.b": {$eq: null}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults,
[
{a: {}},
{},
{},
{a: {b: null}},
{a: {b: undefined}},
{},
{},
{a: [{b: null}, {b: undefined}, {b: null}, {}]},
{a: [{b: null}, {b: 3}, {b: null}]},
{a: [{b: undefined}, {b: 3}]},
{a: [{b: 3}, {}]},
]),
tojson(projectResults));
}());
// 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 projectResults = coll.find({"a.b": {$ne: null}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults,
[
{a: {b: "hi"}},
{a: [[]]},
{a: []},
{a: [{b: 1}, {b: 3}, {b: "string"}]},
{a: []},
{a: []},
{a: []},
{a: []}
]),
tojson(projectResults));
}());
// Test the semantics of the query {a.b: {$nin: [null, <number>]}}.
(function testDottedNotInNullQuery() {
const query = {"a.b": {$nin: [null, 75]}};
const noProjectResults = coll.find(query).toArray();
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]},
];
// TODO: SERVER-21929: $in may (or may not) miss fields with value "undefined".
const expectedWithUndefined = expected.concat([
{_id: "a_object_array_some_b_undefined", a: [{b: undefined}, {b: 3}]},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
]);
assert(resultsEq(noProjectResults, expected) ||
resultsEq(noProjectResults, expectedWithUndefined),
noProjectResults);
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)) ||
resultsEq(projectResults, extractAValues(expectedWithUndefined)),
projectResults);
}());
// Test the semantics of the query {a.b: {$nin: [null, <regex>]}}.
(function testDottedNotInNullAndRegexQuery() {
const query = {"a.b": {$nin: [null, /^str.*/]}};
const noProjectResults = coll.find(query).toArray();
const expected = [
{_id: "a_subobject_b_not_null", a: {b: "hi"}},
{_id: "a_double_array", a: [[]]},
{_id: "a_empty_array", a: []},
{_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]},
];
// TODO: SERVER-21929: $in may (or may not) miss fields with value "undefined".
const expectedWithUndefined = expected.concat([
{_id: "a_object_array_some_b_undefined", a: [{b: undefined}, {b: 3}]},
{_id: "a_subobject_b_undefined", a: {b: undefined}},
]);
assert(resultsEq(noProjectResults, expected) ||
resultsEq(noProjectResults, expectedWithUndefined),
noProjectResults);
const projectResults = coll.find(query, projectToOnlyA).toArray();
assert(resultsEq(projectResults, extractAValues(expected)) ||
resultsEq(projectResults, extractAValues(expectedWithUndefined)),
projectResults);
}());
// Test the results of similar dotted queries with an $elemMatch. These should have no
// results since none of our documents have an array at the path "a.b".
(function testDottedElemMatchValue() {
let results = coll.find({"a.b": {$elemMatch: {$eq: null}}}).toArray();
assert(resultsEq(results, []), tojson(results));
results = coll.find({"a.b": {$elemMatch: {$ne: null}}}).toArray();
assert(resultsEq(results, []), tojson(results));
}());
// Test null semantics within an $elemMatch object.
(function testElemMatchObject() {
// Test $elemMatch with equality to null.
let noProjectResults = coll.find({a: {$elemMatch: {b: {$eq: null}}}}).toArray();
const expectedEqualToNull = [
{_id: "a_double_array", 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, expectedEqualToNull), tojson(noProjectResults));
let projectResults =
coll.find({a: {$elemMatch: {b: {$eq: null}}}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults,
[
{a: [[]]},
{a: [{b: null}, {b: undefined}, {b: null}, {}]},
{a: [{b: null}, {b: 3}, {b: null}]},
{a: [{b: undefined}, {b: 3}]},
{a: [{b: 3}, {}]},
]),
tojson(projectResults));
// Test $elemMatch with not equal to null.
noProjectResults = coll.find({a: {$elemMatch: {b: {$ne: null}}}}).toArray();
const expectedNotEqualToNull = [
{_id: "a_object_array_no_b_nulls", a: [{b: 1}, {b: 3}, {b: "string"}]},
{_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, expectedNotEqualToNull), tojson(noProjectResults));
projectResults =
coll.find({a: {$elemMatch: {b: {$ne: null}}}}, projectToOnlyADotB).toArray();
assert(resultsEq(projectResults,
[
{a: [{b: 1}, {b: 3}, {b: "string"}]},
{a: [{b: null}, {b: 3}, {b: null}]},
{a: [{b: undefined}, {b: 3}]},
{a: [{b: 3}, {}]},
]),
tojson(projectResults));
}());
}
// Test without any indexes.
testNotEqualsNullSemantics(coll);
const keyPatterns = [
{keyPattern: {a: 1}},
{keyPattern: {a: -1}},
{keyPattern: {a: "hashed"}},
{keyPattern: {a: 1}, options: {partialFilterExpression: {a: {$exists: true}}}},
{keyPattern: {a: 1}, options: {sparse: true}},
{keyPattern: {"a.b": 1}},
{keyPattern: {_id: 1, "a.b": 1}},
{keyPattern: {"a.b": 1, _id: 1}},
{keyPattern: {"a.b": 1}, options: {partialFilterExpression: {a: {$exists: true}}}},
{keyPattern: {"a.b": 1, _id: 1}, options: {sparse: true}},
{keyPattern: {"$**": 1}},
{keyPattern: {"a.$**": 1}}
];
// Test with a variety of other indexes.
for (let indexSpec of keyPatterns) {
coll.drop();
jsTestLog(`Index spec: ${tojson(indexSpec)}`);
assert.commandWorked(coll.createIndex(indexSpec.keyPattern, indexSpec.options));
testNotEqualsNullSemantics(coll);
}
// Test that you cannot use a $ne: null predicate in a partial filter expression.
assert.commandFailedWithCode(coll.createIndex({a: 1}, {partialFilterExpression: {a: {$ne: null}}}),
ErrorCodes.CannotCreateIndex);
assert.commandFailedWithCode(
coll.createIndex({a: 1}, {partialFilterExpression: {a: {$elemMatch: {$ne: null}}}}),
ErrorCodes.CannotCreateIndex);
}());