Files
mongo/jstests/aggregation/sources/documents/subpipeline_validation.js
Zac 591928c619 SERVER-108478 JS formatted by prettier and remove clang-format (#39656)
GitOrigin-RevId: 6c8f6aded47f260aa4f7c231b17dae3302cb1e04
2025-08-21 17:27:09 +00:00

264 lines
8.4 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]
import {resultsEq} from "jstests/aggregation/extras/utils.js";
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.
// $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: [{}, {}]}],
),
);
// $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},
],
},
],
),
);
// 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.
assert.throwsWithCode(
() => coll.aggregate([{$lookup: {from: "foo", pipeline: [{$documents: []}], as: "lustuff"}}]),
ErrorCodes.InvalidNamespace,
);
assert.throwsWithCode(
() =>
coll.aggregate([
{
$lookup: {
from: "foo",
let: {docs: "$docs"},
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",
},
},
]),
ErrorCodes.InvalidNamespace,
);
// To use $documents inside a $unionWith, there must not be a "coll" argument.
assert.throwsWithCode(
() => coll.aggregate([{$unionWith: {coll: "foo", pipeline: [{$documents: []}]}}]),
ErrorCodes.InvalidNamespace,
);
// Cannot use $documents inside of $facet.
assert.throwsWithCode(() => coll.aggregate([{$facet: {test: [{$documents: []}]}}]), 40600);