summaryrefslogtreecommitdiff
path: root/jstests/aggregation/sources/setWindowFields
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/aggregation/sources/setWindowFields')
-rw-r--r--jstests/aggregation/sources/setWindowFields/comprehensive_parse.js238
-rw-r--r--jstests/aggregation/sources/setWindowFields/derivative.js29
-rw-r--r--jstests/aggregation/sources/setWindowFields/integral.js27
-rw-r--r--jstests/aggregation/sources/setWindowFields/window_functions_on_timeseries_coll.js4
4 files changed, 267 insertions, 31 deletions
diff --git a/jstests/aggregation/sources/setWindowFields/comprehensive_parse.js b/jstests/aggregation/sources/setWindowFields/comprehensive_parse.js
new file mode 100644
index 00000000000..6c5632798b9
--- /dev/null
+++ b/jstests/aggregation/sources/setWindowFields/comprehensive_parse.js
@@ -0,0 +1,238 @@
+(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,
+ 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'
+};
+
+// 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) {
+ // 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;
+ }
+
+ 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')) {
+ // Integral requires a sort.
+ if (sortType == 'none') {
+ return ErrorCodes.FailedToParse;
+ }
+
+ // Integral requires single column sort
+ if (sortType == 'multi') {
+ return ErrorCodes.FailedToParse;
+ }
+
+ 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;
+ }
+ } 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;
+ }
+
+ 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);
+ }
+}
+})();
diff --git a/jstests/aggregation/sources/setWindowFields/derivative.js b/jstests/aggregation/sources/setWindowFields/derivative.js
index f125bf96872..fcacf07447b 100644
--- a/jstests/aggregation/sources/setWindowFields/derivative.js
+++ b/jstests/aggregation/sources/setWindowFields/derivative.js
@@ -167,9 +167,9 @@ assert.docEq(result, [
{time: 20, y: 30, dy: (30 - 12) / (20 - 10)},
]);
-// 'outputUnit' only supports 'week' and smaller.
+// 'unit' only supports 'week' and smaller.
coll.drop();
-function derivativeStage(outputUnit) {
+function derivativeStage(unit) {
const stage = {
$setWindowFields: {
sortBy: {time: 1},
@@ -183,15 +183,14 @@ function derivativeStage(outputUnit) {
}
}
};
- if (outputUnit) {
- stage.$setWindowFields.output.dy.$derivative.outputUnit = outputUnit;
+ if (unit) {
+ stage.$setWindowFields.output.dy.$derivative.unit = unit;
}
return stage;
}
-function explainUnit(outputUnit) {
- return coll.runCommand({
- explain: {aggregate: coll.getName(), cursor: {}, pipeline: [derivativeStage(outputUnit)]}
- });
+function explainUnit(unit) {
+ return coll.runCommand(
+ {explain: {aggregate: coll.getName(), cursor: {}, pipeline: [derivativeStage(unit)]}});
}
assert.commandFailedWithCode(explainUnit('year'), 5490704);
assert.commandFailedWithCode(explainUnit('quarter'), 5490704);
@@ -203,7 +202,7 @@ assert.commandWorked(explainUnit('minute'));
assert.commandWorked(explainUnit('second'));
assert.commandWorked(explainUnit('millisecond'));
-// When the time field is numeric, 'outputUnit' is not allowed.
+// When the time field is numeric, 'unit' is not allowed.
coll.drop();
assert.commandWorked(coll.insert([
{time: 0, y: 100},
@@ -219,7 +218,7 @@ assert.sameMembers(result, [
{time: 2, y: 100, dy: 0},
]);
-// When the time field is a Date, 'outputUnit' is required.
+// When the time field is a Date, 'unit' is required.
coll.drop();
assert.commandWorked(coll.insert([
{time: ISODate("2020-01-01T00:00:00.000Z"), y: 5},
@@ -240,7 +239,7 @@ assert.sameMembers(result, [
// The change per minute is 60*1000 larger than the change per millisecond.
result = coll.aggregate([derivativeStage('minute'), {$unset: "_id"}]).toArray();
assert.sameMembers(result, [
- // 'outputUnit' applied to an ISODate expresses the output in terms of that unit.
+ // 'unit' applied to an ISODate expresses the output in terms of that unit.
{time: ISODate("2020-01-01T00:00:00.000Z"), y: 5, dy: null},
{time: ISODate("2020-01-01T00:00:00.001Z"), y: 4, dy: -1 * 60 * 1000},
{time: ISODate("2020-01-01T00:00:00.002Z"), y: 6, dy: +2 * 60 * 1000},
@@ -262,7 +261,7 @@ result = coll.aggregate([
sortBy: {time: 1},
output: {
dy: {
- $derivative: {input: "$y", outputUnit: 'millisecond'},
+ $derivative: {input: "$y", unit: 'millisecond'},
window: {documents: [-1, 0]}
},
}
@@ -279,7 +278,7 @@ assert.sameMembers(result, [
]);
// When the sortBy field is a mixture of dates and numbers, it's an error:
-// whether or not you specify outputUnit, either the date or the number values
+// whether or not you specify unit, either the date or the number values
// will be an invalid type.
coll.drop();
assert.commandWorked(coll.insert([
@@ -338,7 +337,7 @@ result = coll.aggregate([
sortBy: {time: 1},
output: {
dy: {
- $derivative: {input: "$y", outputUnit: 'second'},
+ $derivative: {input: "$y", unit: 'second'},
window: {
documents: ['unbounded', 'unbounded'],
}
@@ -372,7 +371,7 @@ result = coll.aggregate([
sortBy: {time: 1},
output: {
dy: {
- $derivative: {input: "$y", outputUnit: 'second'},
+ $derivative: {input: "$y", unit: 'second'},
window: {range: [-10, 0], unit: 'second'}
},
}
diff --git a/jstests/aggregation/sources/setWindowFields/integral.js b/jstests/aggregation/sources/setWindowFields/integral.js
index 196c3d463a8..a62033e2b4f 100644
--- a/jstests/aggregation/sources/setWindowFields/integral.js
+++ b/jstests/aggregation/sources/setWindowFields/integral.js
@@ -91,9 +91,9 @@ const resultDesc = coll.aggregate([
.toArray();
assert.sameMembers(result, resultDesc);
-// 'outputUnit' only supports 'week' and smaller.
+// 'unit' only supports 'week' and smaller.
coll.drop();
-function explainUnit(outputUnit) {
+function explainUnit(unit) {
return coll.runCommand({
explain: {
aggregate: coll.getName(),
@@ -105,7 +105,7 @@ function explainUnit(outputUnit) {
integral: {
$integral: {
input: "$y",
- outputUnit: outputUnit,
+ unit: unit,
},
window: {documents: [-1, 1]}
},
@@ -125,7 +125,7 @@ assert.commandWorked(explainUnit('minute'));
assert.commandWorked(explainUnit('second'));
assert.commandWorked(explainUnit('millisecond'));
-// Test if 'outputUnit' is specified. Date type input is supported.
+// Test if 'unit' is specified. Date type input is supported.
coll.drop();
assert.commandWorked(coll.insert([
{x: ISODate("2020-01-01T00:00:00.000Z"), y: 0},
@@ -134,19 +134,18 @@ assert.commandWorked(coll.insert([
{x: ISODate("2020-01-01T00:00:00.006Z"), y: 6},
]));
-const pipelineWithOutputUnit = [
+const pipelineWithUnit = [
{
$setWindowFields: {
sortBy: {x: 1},
output: {
- integral:
- {$integral: {input: "$y", outputUnit: 'second'}, window: {documents: [-1, 1]}},
+ integral: {$integral: {input: "$y", unit: 'second'}, window: {documents: [-1, 1]}},
}
}
},
{$unset: "_id"},
];
-result = coll.aggregate(pipelineWithOutputUnit).toArray();
+result = coll.aggregate(pipelineWithUnit).toArray();
assert.sameMembers(result, [
// We should scale the result by 'millisecond/second'.
{x: ISODate("2020-01-01T00:00:00.000Z"), y: 0, integral: 0.002},
@@ -155,7 +154,7 @@ assert.sameMembers(result, [
{x: ISODate("2020-01-01T00:00:00.006Z"), y: 6, integral: 0.010},
]);
-const pipelineWithNoOutputUnit = [
+const pipelineWithNoUnit = [
{
$setWindowFields: {
sortBy: {x: 1},
@@ -166,8 +165,8 @@ const pipelineWithNoOutputUnit = [
},
{$unset: "_id"},
];
-// 'outputUnit' is only valid if the 'sortBy' values are ISODate objects.
-// Dates are only valid if 'outputUnit' is specified.
+// 'unit' is only valid if the 'sortBy' values are ISODate objects.
+// Dates are only valid if 'unit' is specified.
coll.drop();
assert.commandWorked(coll.insert([
{x: 0, y: 100},
@@ -177,14 +176,14 @@ assert.commandWorked(coll.insert([
]));
assert.commandFailedWithCode(db.runCommand({
aggregate: "setWindowFields_integral",
- pipeline: pipelineWithOutputUnit,
+ pipeline: pipelineWithUnit,
cursor: {},
}),
5423901);
assert.commandFailedWithCode(db.runCommand({
aggregate: "setWindowFields_integral",
- pipeline: pipelineWithNoOutputUnit,
+ pipeline: pipelineWithNoUnit,
cursor: {},
}),
5423902);
@@ -234,7 +233,7 @@ function runRangeBasedIntegral(bounds) {
sortBy: {time: 1},
output: {
integral: {
- $integral: {input: "$y", outputUnit: "second"},
+ $integral: {input: "$y", unit: "second"},
window: {range: bounds, unit: "second"}
},
}
diff --git a/jstests/aggregation/sources/setWindowFields/window_functions_on_timeseries_coll.js b/jstests/aggregation/sources/setWindowFields/window_functions_on_timeseries_coll.js
index a9de018218b..29b8717fdec 100644
--- a/jstests/aggregation/sources/setWindowFields/window_functions_on_timeseries_coll.js
+++ b/jstests/aggregation/sources/setWindowFields/window_functions_on_timeseries_coll.js
@@ -139,7 +139,7 @@ assertExplainBehaviorAndCorrectResults(
posAvgTemp: {$avg: "$temperature", window: {documents: [-1, 1]}},
timeAvgTemp: {$avg: "$temperature", window: {range: [-5, 0], unit: "day"}},
tempRateOfChange: {
- $derivative: {input: "$temperature", outputUnit: "hour"},
+ $derivative: {input: "$temperature", unit: "hour"},
window: {documents: [-1, 0]}
}
}
@@ -338,4 +338,4 @@ assertExplainBehaviorAndCorrectResults(
{_id: 5, rank: 2},
{_id: 3, rank: 3},
]);
-})(); \ No newline at end of file
+})();