264 lines
8.6 KiB
JavaScript
264 lines
8.6 KiB
JavaScript
(function() {
|
|
"use strict";
|
|
|
|
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,
|
|
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 'SKIP', 'OK' or the expected integer
|
|
// error code.
|
|
function expectedResult(wfType, windowType, sortType, partitionType) {
|
|
// Static errors all come first.
|
|
|
|
// Skip range windows over dates or that are over descending windows.
|
|
if (windowType.endsWith('range')) {
|
|
if (sortType.endsWith('date') || sortType.startsWith('desc')) {
|
|
return 'SKIP';
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
} 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;
|
|
}
|
|
|
|
} 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') && (sortType == 'none' || sortType == 'multi')) {
|
|
// 'Range-based window require sortBy a single field'.
|
|
return 5339902;
|
|
}
|
|
|
|
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)
|
|
};
|
|
|
|
if (test.expectedResult == 'SKIP') {
|
|
continue;
|
|
}
|
|
|
|
yield test;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Run all the combinations generated in makeTests.
|
|
for (const test of makeTests()) {
|
|
if (test.expectedResult == ErrorCodes.OK) {
|
|
assert.commandWorked(
|
|
coll.runCommand({aggregate: coll.getName(), pipeline: [test.query], cursor: {}}));
|
|
} else {
|
|
assert.commandFailedWithCode(
|
|
coll.runCommand({aggregate: coll.getName(), pipeline: [test.query], cursor: {}}),
|
|
test.expectedResult);
|
|
}
|
|
}
|
|
})();
|