mirror of https://github.com/mongodb/mongo
271 lines
10 KiB
JavaScript
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);
|
|
}());
|