mongo/jstests/aggregation/sources/documents/subpipeline_validation.js

216 lines
8.3 KiB
JavaScript

// Tests the validation logic for combinations of "collectionless" stages like $documents with or
// around sub-pipelines. For the cases that should be legal, we mostly just care the the command
// succeeds. However, we will use 'resultsEq' to test correct semantics while we are here, gaining
// more coverage.
// TODO SERVER-94226 consider extending this test to cases like $currentOp and $queryStats as well.
// This test uses stages like $documents which are not permitted inside a $facet stage.
// @tags: [do_not_wrap_aggregations_in_facets]
(function() {
"use strict";
load("jstests/aggregation/extras/utils.js"); // For 'resultsEq.'
load("jstests/libs/fixture_helpers.js"); // For 'isMongos.'
const coll = db[jsTestName()];
coll.drop();
const targetCollForMerge = db["target_coll"];
targetCollForMerge.drop();
assert.commandWorked(coll.insert({_id: 0, arr: [{}, {}]}));
{
// Tests for an aggregation over a collection (i.e. {aggregate: "collName"} commands) with a
// $documents stage used in a sub-pipeline. Each of these cases should be legal, which is most
// of the value of the assertion. We will use 'resultsEq' to test correct semantics while we are
// here.
// $lookup.
assert(resultsEq(coll.aggregate([
{$lookup: {
let: {documents: "$arr"},
pipeline: [
{$documents: "$$documents"},
],
as: "duplicated"
}},
]).toArray(), [{_id: 0, arr: [{}, {}], duplicated: [{}, {}]}]));
// $unionWith.
assert(resultsEq(coll.aggregate([
{
$unionWith: {
pipeline: [
{$documents: [{_id: "gen"}]},
],
}
},
])
.toArray(),
[{_id: 0, arr: [{}, {}]}, {_id: "gen"}]));
// Both, and more nesting.
assert(resultsEq(coll.aggregate([{
$unionWith: {
coll: coll.getName(),
pipeline: [{
$lookup: {
pipeline: [
{$documents: []},
{$unionWith: {coll: coll.getName(), pipeline: []}}
],
as: "nest"
}
}]
}
}])
.toArray(),
[
{_id: 0, arr: [{}, {}]},
{_id: 0, arr: [{}, {}], nest: [{_id: 0, arr: [{}, {}]}]}
]));
}
{
// Tests for a db-level aggregate (i.e. {aggregate: 1} commands) with sub-pipelines on regular
// collections.
// $facet
assert(resultsEq(db.aggregate([
{
$documents: [
{x: 1, y: 1, val: 1},
{x: 2, y: 2, val: 1},
{x: 3, y: 1, val: 2},
{x: 2, y: 2, val: 1}
]
},
{
$facet: {
sumByX: [{$group: {_id: "$x", sum: {$sum: "$val"}}}],
sumByY: [{$group: {_id: "$y", sum: {$sum: "$val"}}}]
}
}
]).toArray(),
[{
sumByX: [{_id: 1, sum: 1}, {_id: 2, sum: 2}, {_id: 3, sum: 2}],
sumByY: [{_id: 1, sum: 3}, {_id: 2, sum: 2}]
}]));
if (!FixtureHelpers.isMongos(db)) {
// This doesn't work on mongos in v7.0 and earlier - waiting for SERVER-65534.
// $lookup.
assert(resultsEq(db.aggregate([
{$documents: [{x: 1, arr: [{x: 2}]}, {y: 1, arr: []}]},
{$lookup: {
let: {documents: "$arr"},
pipeline: [
{$documents: "$$documents"},
],
as: "duplicated"
}},
]).toArray(),
[
{x: 1, arr: [{x: 2}], duplicated: [{x: 2}]},
{y: 1, arr: [], duplicated: []}
]));
// $merge.
assert.doesNotThrow(() => db.aggregate([
{
$documents: [
{_id: 2, x: "foo"},
{_id: 4, x: "bar"},
]
},
{
$merge: {
into: targetCollForMerge.getName(),
on: "_id",
whenMatched: [{$set: {x: {$setUnion: ["$x", "$$new.x"]}}}]
}
}
]));
assert(resultsEq(targetCollForMerge.find({}, {_id: 1}).toArray(), [{_id: 2}, {_id: 4}]));
// $unionWith
assert(resultsEq(db.aggregate([
{$documents: [{_id: 2}, {_id: 4}]},
{$unionWith: {coll: coll.getName(), pipeline: []}}
]).toArray(),
[{_id: 2}, {_id: 4}, {_id: 0, arr: [{}, {}]}]));
// All of the above, plus nesting.
const results =
db.aggregate([
{$documents: [{_id: "first"}]},
{
$unionWith: {
pipeline: [
{$documents: [{_id: "uw"}]},
{$unionWith: {pipeline: [{$documents: [{_id: "uw_2"}]}]}},
{
$facet: {
allTogether:
[{$group: {_id: null, all: {$addToSet: "$_id"}}}],
countEach: [{$group: {_id: "$_id", count: {$sum: 1}}}],
}
},
{
$lookup:
{pipeline: [{$documents: [{x: "lu1"}, {x: "lu2"}]}], as: "xs"}
},
{$set: {xs: {$map: {input: "$xs", in : "$$this.x"}}}}
]
},
},
]).toArray();
assert(resultsEq(results,
[
{_id: "first"},
{
allTogether: [{_id: null, all: ["uw", "uw_2"]}],
countEach: [{_id: "uw", count: 1}, {_id: "uw_2", count: 1}],
xs: ["lu1", "lu2"]
}
]),
results);
}
}
// Test for invalid combinations.
// To use $documents inside a $lookup, there must not be a "from" argument.
// As of SERVER-94144, this does not throw on 7.0 and older branches.
assert.doesNotThrow(
() => coll.aggregate([{$lookup: {from: "foo", pipeline: [{$documents: []}], as: "lustuff"}}]));
assert.doesNotThrow(
() => coll.aggregate([
{$lookup: {
from: "foo",
let: {docs: "$arr"},
pipeline: [
{$documents: "$$docs"},
{$lookup: {
from: "foo",
let: {x: "$x", y: "$y"},
pipeline: [
{$match: {$expr: {$and: [
{$eq: ["$x", "$$x"]},
{$eq: ["$y", "$$y"]}
]}}}
],
as: "doesnt_matter"
}}
],
as: "lustuff"
}}]));
// To use $documents inside a $unionWith, there must not be a "coll" argument.
// As of SERVER-94144, this does not throw on 7.0 and older branches.
assert.doesNotThrow(
() => coll.aggregate([{$unionWith: {coll: "foo", pipeline: [{$documents: []}]}}]));
// Cannot use $documents inside of $facet.
assert.throwsWithCode(() => coll.aggregate([{$facet: {test: [{$documents: []}]}}]), 40600);
})();