Files
mongo/jstests/aggregation/sources/setWindowFields/comprehensive_parse.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

280 lines
9.3 KiB
JavaScript

import "jstests/libs/query/sbe_assert_error_override.js";
const coll = db[jsTestName()];
coll.drop();
const nDocs = 10;
let currentDate = new Date();
for (let i = 0; i < nDocs; i++) {
assert.commandWorked(
coll.insert({
a: i,
date: currentDate,
array: [],
partition: i % 2,
partitionSeq: Math.trunc(i / 2),
}),
);
const nextDate = currentDate.getDate() + 1;
currentDate.setDate(nextDate);
}
// The list of window functions to test.
const functions = {
sum: {$sum: "$a"},
avg: {$avg: "$a"},
stdDevSamp: {$stdDevSamp: "$a"},
stdDevPop: {$stdDevPop: "$a"},
min: {$min: "$a"},
max: {$max: "$a"},
count: {$count: {}},
derivative: {$derivative: {input: "$a"}},
derivative_date: {$derivative: {input: "$date", unit: "millisecond"}},
integral: {$integral: {input: "$a"}},
integral_date: {$integral: {input: "$a", unit: "millisecond"}},
covariancePop: {$covariancePop: ["$a", "$date"]},
covarianceSamp: {$covarianceSamp: ["$a", "$date"]},
expMovingAvgN: {$expMovingAvg: {input: "$a", N: 3}},
expMovingAvgAlpha: {$expMovingAvg: {input: "$a", alpha: 0.1}},
push: {$push: "$a"},
addToSet: {$addToSet: "$a"},
first: {$first: "$a"},
last: {$last: "$a"},
shift: {$shift: {output: "$a", by: 1, default: 0}},
documentNumber: {$documentNumber: {}},
rank: {$rank: {}},
denseRank: {$denseRank: {}},
};
// The list of window definitions to test.
const windows = {
none: null,
left_unbounded_doc: {documents: ["unbounded", "current"]},
right_unbounded_doc: {documents: ["current", "unbounded"]},
past_doc: {documents: [-1, "current"]},
future_doc: {documents: ["current", 1]},
centered_doc: {documents: [-1, 1]},
full_unbounded_range: {range: ["unbounded", "unbounded"]},
left_unbounded_range: {range: ["unbounded", "current"]},
right_unbounded_range: {range: ["current", "unbounded"]},
past_range: {range: [-2, "current"]},
future_range: {range: ["current", 2]},
centered_range: {range: [-2, 2]},
};
// The list of sort definitions to test.
const sortBys = {
none: null,
expr: {partitionSeq: {$meta: "randVal"}},
asc: {partitionSeq: 1},
desc: {partitionSeq: -1},
asc_date: {date: 1},
desc_date: {date: -1},
multi: {partitionSeq: 1, partition: 1},
};
// The list of partition definitions to test.
const partitionBys = {
none: null,
field: "$partition",
dynamic_array: "$array",
static_array: {$const: []},
};
// Given an element from each of the lists above, construct a
// $setWindowFields stage.
function constructQuery(wf, window, sortBy, partitionBy) {
let pathArg = Object.assign({}, wf);
if (window != null) {
Object.assign(pathArg, {window: window});
}
let setWindowFieldsArg = {};
if (sortBy != null) {
setWindowFieldsArg.sortBy = sortBy;
}
if (partitionBy != null) {
setWindowFieldsArg.partitionBy = partitionBy;
}
setWindowFieldsArg.output = {x: pathArg};
return {$setWindowFields: setWindowFieldsArg};
}
// Given an element of each of the lists above, what is the expected
// result. The output should be 'OK' or the expected integer
// error code.
function expectedResult(wfType, windowType, sortType, partitionType) {
// Static errors all come first.
// Derivative and integral require an ascending sort
// and an explicit window.
if (wfType.startsWith("derivative")) {
// Derivative requires a sort and an explicit window.
if (sortType == "none" || windowType == "none") {
return ErrorCodes.FailedToParse;
}
// Integral requires single column sort
if (sortType == "multi") {
return ErrorCodes.FailedToParse;
}
// '$derivative requires a non-expression sortBy'.
if (sortType == "expr") {
return ErrorCodes.FailedToParse;
}
} else if (wfType.startsWith("integral")) {
// Integral requires a sort.
if (sortType == "none") {
return ErrorCodes.FailedToParse;
}
// Integral requires single column sort
if (sortType == "multi") {
return ErrorCodes.FailedToParse;
}
// '$integral requires a non-expression sortBy'.
if (sortType == "expr") {
return ErrorCodes.FailedToParse;
}
} else if (wfType.startsWith("expMovingAvg")) {
// $expMovingAvg doesn't accept a window.
if (windowType != "none") {
return ErrorCodes.FailedToParse;
}
// $expMovingAvg requires a sort.
if (sortType == "none") {
return ErrorCodes.FailedToParse;
}
} else if (wfType == "documentNumber" || wfType == "rank" || wfType == "denseRank") {
if (windowType != "none") {
// Rank style window functions take no other arguments.
return 5371601;
}
if (sortType == "none") {
// %s must be specified with a top level sortBy expression with exactly one element.
return 5371602;
}
if (sortType == "multi") {
// %s must be specified with a top level sortBy expression with exactly one element.
return 5371602;
}
} else if (wfType == "shift") {
// $shift requires a sortBy and can't have defined a window.
if (sortType == "none" || windowType != "none") {
return ErrorCodes.FailedToParse;
}
}
// Document windows require a sortBy.
if (windowType.endsWith("doc") && sortType == "none") {
// 'Document-based bounds require a sortBy'.
return 5339901;
}
// Range based windows require a sort over a single field.
if (windowType.endsWith("range")) {
if (sortType == "none" || sortType == "multi") {
// 'Range-based window require sortBy a single field'.
return 5339902;
}
if (sortType == "expr") {
// 'Range-based bounds require a non-expression sortBy'
return 8947400;
}
if (sortType.startsWith("desc")) {
// 'Range-based bounds require an ascending sortBy'.
return 8947401;
}
if (sortType.endsWith("date") && !partitionType.endsWith("array")) {
// 'For windows that involve date or time ranges, a unit must be provided.'
return 5429413;
}
}
if (partitionType === "static_array") {
// When we parse $setWindowFields, we check whether partitionBy is a constant; if so we can
// drop the partitionBy clause. However, if the constant value is an array, we want to
// throw an error to make it clear that partitioning by an array is not supported.
return ErrorCodes.TypeMismatch;
}
// Dynamic errors all come after this point.
if (partitionType === "dynamic_array") {
// At runtime, we raise an error if partitionBy evaluates to an array. We chose not to
// support partitioning by an array because $sort (which has multikey semantics)
// doesn't partition arrays.
return ErrorCodes.TypeMismatch;
}
if (wfType.startsWith("derivative")) {
if (wfType == "derivative_date" && !sortType.endsWith("date")) {
// "$derivative with unit expects the sortBy field to be a Date".
return 5624900;
}
if (sortType.endsWith("date") && wfType != "derivative_date") {
// "$derivative where the sortBy is a Date requires a 'unit'.
return 5624901;
}
} else if (wfType.startsWith("integral")) {
if (wfType == "integral_date" && !sortType.endsWith("date")) {
// "$integral with unit expects the sortBy field to be a Date"
return 5423901;
}
if (sortType.endsWith("date") && wfType != "integral_date") {
// $integral where the sortBy is a Date requires a 'unit'
return 5423902;
}
}
return ErrorCodes.OK;
}
// Generate all combinations of the elements in the lists above,
// one element per list.
function* makeTests() {
for (const [wfType, wfDefinition] of Object.entries(functions)) {
for (const [windowType, windowDefinition] of Object.entries(windows)) {
for (const [sortType, sortDefinition] of Object.entries(sortBys)) {
for (const [partitionType, partitionDefinition] of Object.entries(partitionBys)) {
let test = {
query: constructQuery(wfDefinition, windowDefinition, sortDefinition, partitionDefinition),
expectedResult: expectedResult(wfType, windowType, sortType, partitionType),
};
yield test;
}
}
}
}
}
// Run all the combinations generated in makeTests.
for (const test of makeTests()) {
const errorMsg = "Command was: " + tojson(test.query);
if (test.expectedResult == ErrorCodes.OK) {
assert.commandWorked(
coll.runCommand({aggregate: coll.getName(), pipeline: [test.query], cursor: {}}),
errorMsg,
);
} else {
assert.commandFailedWithCode(
coll.runCommand({aggregate: coll.getName(), pipeline: [test.query], cursor: {}}),
test.expectedResult,
errorMsg,
);
}
}