diff options
Diffstat (limited to 'jstests/aggregation/sources/setWindowFields')
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 +})(); |