143 lines
5.4 KiB
JavaScript
143 lines
5.4 KiB
JavaScript
/**
|
|
* Test that the 'n' family of accumulators work as window functions.
|
|
*/
|
|
(function() {
|
|
"use strict";
|
|
|
|
load("jstests/aggregation/extras/window_function_helpers.js");
|
|
|
|
const coll = db[jsTestName()];
|
|
coll.drop();
|
|
|
|
const needsSortBy = (op) => ({
|
|
$minN: false,
|
|
$maxN: false,
|
|
$firstN: false,
|
|
$lastN: false,
|
|
$topN: true,
|
|
$bottomN: true,
|
|
$top: true,
|
|
$bottom: true,
|
|
}[op]);
|
|
|
|
// A map from the accumulator name to a function that ignores the parameters it doesn't need and
|
|
// generates a valid accumulator spec.
|
|
const simple = (n, input) => ({n, input: "$" + input});
|
|
const topBottomN = (n, output) => ({n, output: "$" + output, sortBy: {[output]: 1}});
|
|
const topBottom = (n, output) => ({output: "$" + output, sortBy: {[output]: 1}});
|
|
const nAccumulators = {
|
|
$minN: simple,
|
|
$maxN: simple,
|
|
$firstN: simple,
|
|
$lastN: simple,
|
|
$topN: topBottomN,
|
|
$bottomN: topBottomN,
|
|
$top: topBottom,
|
|
$bottom: topBottom,
|
|
};
|
|
|
|
// Create a collection of tickers and prices.
|
|
const nDocsPerTicker = 10;
|
|
seedWithTickerData(coll, nDocsPerTicker);
|
|
|
|
for (const acc of Object.keys(nAccumulators)) {
|
|
for (const nValue of [4, 7, 12]) {
|
|
jsTestLog("Testing accumulator " + tojson(acc) + " with 'n' set to " + tojson(nValue));
|
|
const noValue = (acc === "$top" || acc === "$bottom") ? null : [];
|
|
testAccumAgainstGroup(coll, acc, noValue, nAccumulators[acc](nValue, "price"));
|
|
}
|
|
|
|
// Verify that the accumulator will not throw if the 'n' expression evaluates to a constant.
|
|
const pipeline = [
|
|
{
|
|
$setWindowFields: {
|
|
partitionBy: "$ticker",
|
|
sortBy: {_id: 1},
|
|
output: {res: {[acc]: nAccumulators[acc]({$add: [1, 2]}, "price")}}
|
|
},
|
|
},
|
|
];
|
|
|
|
assert.doesNotThrow(() => coll.aggregate(pipeline).toArray());
|
|
|
|
// Error cases.
|
|
function testError(accSpec, expectedCode) {
|
|
assert.throwsWithCode(() => coll.aggregate([{
|
|
$setWindowFields: {
|
|
partitionBy: "$ticket",
|
|
sortBy: {ts: 1},
|
|
output: {outputField: accSpec},
|
|
}
|
|
}]),
|
|
expectedCode);
|
|
}
|
|
|
|
// The parsers for $minN/$maxN and $firstN/$lastN will use different codes to indicate a
|
|
// parsing error due to a non-object specification.
|
|
const expectedCode = (acc === "$minN" || acc === "$maxN") ? 5787900 : 5787801;
|
|
|
|
if (!needsSortBy(acc)) {
|
|
// Invalid/missing accumulator specification.
|
|
testError({[acc]: "non object"}, expectedCode);
|
|
testError({window: {documents: [-1, 1]}}, ErrorCodes.FailedToParse);
|
|
testError({[acc]: {n: 2}, window: {documents: [-1, 1]}}, 5787907);
|
|
testError({[acc]: {input: "$foo"}, window: {documents: [-1, 1]}}, 5787906);
|
|
testError({[acc]: {input: "$foo", n: 2.1}, window: {documents: [-1, 1]}}, 5787903);
|
|
|
|
// Invalid window specification.
|
|
testError({[acc]: {input: "$foo", n: 2.0}, window: [-1, 1]}, ErrorCodes.FailedToParse);
|
|
|
|
// Non constant argument for 'n'.
|
|
testError({[acc]: {input: "$foo", n: "$a"}, window: {documents: [-1, 1]}}, 5787902);
|
|
|
|
// Can't reference partition key.
|
|
testError({[acc]: {input: "$foo", n: "$ticket"}, window: {documents: [-1, 1]}}, 5787902);
|
|
|
|
// n = 0
|
|
testError({[acc]: {input: "$foo", n: 0}, window: {documents: [-1, 1]}}, 5787908);
|
|
|
|
// n < 0
|
|
testError({[acc]: {input: "$foo", n: -100}, window: {documents: [-1, 1]}}, 5787908);
|
|
} else if (acc == "$topN" || acc == "$bottomN") {
|
|
// TODO SERVER-59327 combine error codes if we decide to use the same parser function.
|
|
// Invalid/missing accumulator specification.
|
|
const sortBy = {"foo": 1};
|
|
const output = "$foo";
|
|
testError({[acc]: "non object"}, 5788001);
|
|
testError({window: {documents: [-1, 1]}}, ErrorCodes.FailedToParse);
|
|
testError({[acc]: {n: 2, sortBy}, window: {documents: [-1, 1]}}, 5788004);
|
|
testError({[acc]: {output, sortBy}, window: {documents: [-1, 1]}}, 5788003);
|
|
testError({[acc]: {output, sortBy, n: 2.1}, window: {documents: [-1, 1]}}, 5787903);
|
|
|
|
// Missing sortBy.
|
|
testError({[acc]: {output, n: 2}, window: {documents: [-1, 1]}}, 5788005);
|
|
|
|
// Invalid window specification.
|
|
testError({[acc]: {output, sortBy, n: 2.0}, window: [-1, 1]}, ErrorCodes.FailedToParse);
|
|
|
|
// Non constant argument for 'n'.
|
|
testError({[acc]: {output, sortBy, n: "$a"}, window: {documents: [-1, 1]}}, 5787902);
|
|
|
|
// Can't reference partition key.
|
|
testError({[acc]: {output, sortBy, n: "$ticket"}, window: {documents: [-1, 1]}}, 5787902);
|
|
|
|
// n = 0
|
|
testError({[acc]: {output, sortBy, n: 0}, window: {documents: [-1, 1]}}, 5787908);
|
|
|
|
// n < 0
|
|
testError({[acc]: {output, sortBy, n: -100}, window: {documents: [-1, 1]}}, 5787908);
|
|
} else {
|
|
// $top/$bottom parsing tests.
|
|
const sortBy = {"foo": 1};
|
|
const output = "$foo";
|
|
testError({[acc]: "non object"}, 5788001);
|
|
testError({window: {documents: [-1, 1]}}, ErrorCodes.FailedToParse);
|
|
testError({[acc]: {sortBy}, window: {documents: [-1, 1]}}, 5788004);
|
|
testError({[acc]: {n: 2, output, sortBy}, window: {documents: [-1, 1]}}, 5788002);
|
|
|
|
// Missing sortBy.
|
|
testError({[acc]: {output}, window: {documents: [-1, 1]}}, 5788005);
|
|
}
|
|
}
|
|
})();
|