mongo/jstests/core/positional_projection.js

271 lines
10 KiB
JavaScript

// Tests for $ projection operator.
// @tags: [
// requires_getmore,
// ]
(function() {
"use strict";
load("jstests/libs/sbe_assert_error_override.js"); // Override error-code-checking APIs.
const coll = db.positional_projection;
coll.drop();
function testSingleDocument(query, projection, input, expectedOutput) {
assert.commandWorked(coll.insert(input));
const actualOutput = coll.findOne(query, projection);
delete actualOutput._id;
assert.eq(actualOutput, expectedOutput);
assert(coll.drop());
}
// Single object match.
testSingleDocument({'x.a': 2},
{'x.$': 1},
{
x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}],
y: [{aa: 1, bb: 2}, {aa: 2, cc: 3}, {aa: 1, dd: 5}]
},
{x: [{a: 2, c: 3}]});
testSingleDocument({'x.a': 1},
{'x.$': 1},
{
x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}],
y: [{aa: 1, bb: 2}, {aa: 2, cc: 3}, {aa: 1, dd: 5}]
},
{x: [{a: 1, b: 2}]});
testSingleDocument({'x.a': 1}, {'x.$': 1}, {x: [{a: 1, b: 3}, {a: -6, c: 3}]}, {x: [{a: 1, b: 3}]});
testSingleDocument({'y.dd': 5},
{'y.$': 1},
{
x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}],
y: [{aa: 1, bb: 2}, {aa: 2, cc: 3}, {aa: 1, dd: 5}]
},
{y: [{aa: 1, dd: 5}]});
// Nested paths.
testSingleDocument(
{'x.y.a': 1}, {'x.y.$': 1}, {x: {y: [{a: 1, b: 1}, {a: 1, b: 2}]}}, {x: {y: [{a: 1, b: 1}]}});
// Positional projection on non-existent field.
testSingleDocument({},
{'g.$': 1},
{
x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}],
y: [{aa: 1, bb: 2}, {aa: 2, cc: 3}, {aa: 1, dd: 5}]
},
{});
testSingleDocument({'a.b': 1}, {'g.$': 1}, {a: {b: 1}}, {});
// Usually, if query predicate does not find matching array element in the input document,
// positional projection throws. But if positional projection cannot find specified path, query
// should not throw.
testSingleDocument({x: 1}, {'a.$': 1}, {x: 1}, {});
// Test that if positional projection operator cannot find specified path, existing part of it is
// returned.
testSingleDocument({x: {$gt: 0}},
{'a.b.c.$': 1},
{x: [-1, 1, 2], a: {b: {d: [1, 2, 3], f: 456}, e: 123}},
{a: {b: {}}});
// Test that only relevant part of specified path is extracted even in case of multiple nested
// arrays.
testSingleDocument(
{x: 1}, {'a.b.c.$': 1}, {x: [1], a: [{b: [[[{c: 1, d: 2}]]]}]}, {a: [{b: [[[{c: 1}]]]}]});
// Test that if path specified for positional projection operator does not contain array, it is
// returned unchanged.
testSingleDocument({x: {$gt: 0}}, {'a.b.c.$': 1}, {x: [-1, 1, 2], a: {b: {c: {d: {e: 1}}}}}, {
a: {b: {c: {d: {e: 1}}}}
});
testSingleDocument(
{x: {$gt: 0}}, {'a.b.c.$': 1}, {x: [-1, 1, 2], a: {b: {c: 123}}}, {a: {b: {c: 123}}});
// Test that positional projection is applied to the first array on the dotted path.
// NOTE: Even though the positional projection is specified for the path 'a.b.c', it is applied to
// the first array it meets along this path. For instance, if value on a path 'a' or 'a.b' is an
// array, positional projection operator is applied only for this array.
testSingleDocument(
{'x.y.z': 1},
{'a.b.c.$': 1},
{x: {y: {z: [0, 1, 2]}}, a: [{b: {c: [1, 2, 3]}}, {b: {c: [4, 5, 6]}}, {b: {c: [7, 8, 9]}}]},
{a: [{b: {c: [4, 5, 6]}}]});
testSingleDocument(
{'x.y.z': 1},
{'a.b.c.$': 1},
{x: {y: {z: [0, 1, 2]}}, a: {b: [{c: [1, 2, 3]}, {c: [4, 5, 6]}, {c: [7, 8, 9]}]}},
{a: {b: [{c: [4, 5, 6]}]}});
testSingleDocument({'x.y.z': 1}, {'a.b.c.$': 1}, {x: {y: {z: [0, 1, 2]}}, a: {b: {c: [1, 2, 3]}}}, {
a: {b: {c: [2]}}
});
// Test $elemMatch in query document with positional projection.
testSingleDocument({x: {$elemMatch: {$gt: 1, $lt: 3}}},
{'x.$': 1},
{
x: [1, 2, 3],
},
{x: [2]});
testSingleDocument({x: {$elemMatch: {y: {$gt: 1}}}},
{'x.$': 1},
{
x: [{y: 1}, {y: 2}, {y: 3}],
},
{x: [{y: 2}]});
testSingleDocument({x: {$elemMatch: {a: 1, b: 2}}},
{'x.$': 1},
{x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}]},
{x: [{a: 1, b: 2}]});
// Test nested $elemMatch in the query document.
testSingleDocument({a: {$elemMatch: {$elemMatch: {$eq: 3}}}},
{"b.$": 1},
{a: [[1, 2], [3, 4]], b: [[5, 6], [7, 8]]},
{b: [[7, 8]]});
testSingleDocument({a: {$elemMatch: {p: {$elemMatch: {$eq: 2}}}}},
{'b.$': 1},
{a: [{p: [1, 2], q: [3, 4]}, {p: [5, 6], q: [7, 8]}], b: [11, 12]},
{b: [11]});
testSingleDocument({a: {$elemMatch: {p: {$elemMatch: {$eq: 6}}}}},
{'b.$': 1},
{a: [{p: [1, 2], q: [3, 4]}, {p: [5, 6], q: [7, 8]}], b: [11, 12]},
{b: [12]});
testSingleDocument({a: {$elemMatch: {q: {$elemMatch: {$eq: 3}}}}},
{'b.$': 1},
{a: [{p: [1, 2], q: [3, 4]}, {p: [5, 6], q: [7, 8]}], b: [11, 12]},
{b: [11]});
testSingleDocument({a: {$elemMatch: {q: {$elemMatch: {$eq: 7}}}}},
{'b.$': 1},
{a: [{p: [1, 2], q: [3, 4]}, {p: [5, 6], q: [7, 8]}], b: [11, 12]},
{b: [12]});
// Regular index test.
assert.commandWorked(coll.createIndex({'y.d': 1}));
testSingleDocument({'y.d': 4},
{'y.$': 1},
{x: [{a: 1, b: 2}, {a: 3, b: 4}], y: [{c: 1, d: 2}, {c: 3, d: 4}]},
{y: [{c: 3, d: 4}]});
// Covered index test.
assert.commandWorked(coll.createIndex({covered: 1}));
testSingleDocument({'covered.dd': 5},
{'covered.$': 1},
{
x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}],
covered: [{aa: 1, bb: 2}, {aa: 2, cc: 3}, {aa: 1, dd: 5}]
},
{covered: [{aa: 1, dd: 5}]});
// Positional projection must use the index from the last child of $and operator.
testSingleDocument(
{$and: [{a: 1}, {a: 2}, {a: 3}]}, {'b.$': 1}, {a: [1, 2, 3], b: [4, 5, 6]}, {b: [6]});
testSingleDocument(
{$and: [{a: 3}, {a: 2}, {a: 1}]}, {'b.$': 1}, {a: [1, 2, 3], b: [4, 5, 6]}, {b: [4]});
// Positional projection must use the first matching index both for $in, and for $or
// equivalent to an $in.
testSingleDocument({a: {$in: [2, 3]}}, {'b.$': 1}, {a: [1, 2, 3], b: [4, 5, 6]}, {b: [5]});
testSingleDocument({$or: [{a: 2}, {a: 3}]}, {'b.$': 1}, {a: [1, 2, 3], b: [4, 5, 6]}, {b: [5]});
// SERVER-61839: Test out some cases involving $exists and $type where we've had bugs in the past.
testSingleDocument(
{a: {$elemMatch: {y: {$exists: true}}}}, {"a.$": 1}, {a: [{y: 1}, {y: 2}]}, {a: [{y: 1}]});
testSingleDocument(
{a: {$elemMatch: {y: {$type: ["array"]}}}}, {"a.$": 1}, {a: [{y: 1}, {y: []}]}, {a: [{y: []}]});
testSingleDocument({a: {$elemMatch: {y: {$type: ["array", "double"]}}}},
{"a.$": 1},
{a: [{y: 1}, {y: []}]},
{a: [{y: 1}]});
// Tests involving getMore. Test the $-positional operator across multiple batches.
assert.commandWorked(coll.insert([
{
_id: 0,
group: 3,
x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}],
y: [{aa: 1, bb: 2}, {aa: 2, cc: 3}, {aa: 1, dd: 5}]
},
{_id: 1, group: 3, x: [{a: 1, b: 3}, {a: -6, c: 3}]},
]));
let it = coll.find({'x.b': 2}, {'x.$': 1}).sort({_id: 1}).batchSize(1);
while (it.hasNext()) {
const currentDocument = it.next();
assert.eq(2, currentDocument.x[0].b);
}
assert(coll.drop());
// Multiple array fields in the query document with positional projection may result in "undefined
// behaviour" according to the documentation. Here we test that at least there is no error/segfault
// for such queries.
assert.commandWorked(coll.insert({a: [1, 2, 3], b: [4, 5, 6], c: [7, 8, 9]}));
assert.doesNotThrow(() => coll.find({a: 2, b: 5}, {"c.$": 1}));
assert(coll.drop());
// Tests with invalid positional projection operator.
assert.commandWorked(coll.insert([{x: [{a: 1, b: 2}, {a: 2, c: 3}, {a: 1, d: 5}], y: [{aa: 1}]}]));
// Positional projection cannot be used with $slice.
let err = assert.throws(() => coll.find({x: 1}, {'x.$': {'$slice': 1}}).toArray());
assert.commandFailedWithCode(err, 31271);
// Positional projection cannot be used with exclusion projection.
err = assert.throws(() => coll.find({'x.a': 2}, {'x.$': 1, y: 0}).sort({x: 1}).toArray());
assert.commandFailedWithCode(err, 31254);
// There can be only one positional projection in the query.
err = assert.throws(() => coll.find({'x.a': 1, 'y.aa': 1}, {'x.$': 1, 'y.$': 1}).toArray());
assert.commandFailedWithCode(err, 31276);
// Test queries where no array index for positional projection is recorded.
err = assert.throws(() => coll.find({}, {'x.$': 1}).toArray());
assert.commandFailedWithCode(err, 51246);
assert(coll.drop());
assert.commandWorked(coll.insert({a: [1, 2, 3], b: [4, 5, 6], c: [7, 8, 9]}));
err = assert.throws(() => coll.find({b: [4, 5, 6]}, {"c.$": 1}).toArray());
assert.commandFailedWithCode(err, 51246);
// $or with different fields, $nor and $not operators disable positional projection index
// recording for its children.
err = assert.throws(() => coll.find({$or: [{a: 1}, {b: 5}]}, {'a.$': 1}).toArray());
assert.commandFailedWithCode(err, 51246);
err = assert.throws(() => coll.find({$or: [{a: 0}, {b: 5}]}, {'a.$': 1}).toArray());
assert.commandFailedWithCode(err, 51246);
err = assert.throws(() => coll.find({$nor: [{a: -1}, {a: -2}]}, {'a.$': 1}).toArray());
assert.commandFailedWithCode(err, 51246);
assert.throws(function() {
coll.find({'x.a': 1, 'y.aa': 1}, {'x.$': 1, 'y.$': 1}).toArray();
}, []);
err = assert.throws(function() {
coll.find({}, {".$": 1}).toArray();
}, []);
assert.commandFailedWithCode(err, 5392900);
err = assert.throws(
() => coll.find({a: {$not: {$not: {$elemMatch: {$eq: 1}}}}}, {'a.$': 1}).toArray());
assert.commandFailedWithCode(err, 51246);
}());