Files
mongo/jstests/aggregation/sources/setWindowFields/time.js
2021-06-01 14:50:37 +00:00

200 lines
7.3 KiB
JavaScript

/**
* Test time-based window bounds.
*/
(function() {
"use strict";
load("jstests/aggregation/extras/window_function_helpers.js");
const coll = db.setWindowFields_time;
coll.drop();
// Create an ISODate that occurs 'seconds' seconds after a fixed date.
function makeDate(seconds) {
let result = ISODate('2021-01-01T00:00:00Z');
result.setUTCMilliseconds(seconds * 1000);
return result;
}
function makeDates(arrayOfSeconds) {
return arrayOfSeconds.map(seconds => makeDate(seconds));
}
assert.commandWorked(coll.insert([
{x: makeDate(0)},
{x: makeDate(1)},
{x: makeDate(1.5)},
{x: makeDate(2)},
{x: makeDate(3)},
{x: makeDate(42)},
{x: makeDate(42)},
{x: makeDate(43)},
]));
// Make a setWindowFields stage with the given bounds.
function range(lower, upper, unit = 'second') {
return {
$setWindowFields: {
partitionBy: "$partition",
sortBy: {x: 1},
output: {
y: {$push: "$x", window: {range: [lower, upper], unit}},
}
}
};
}
// Run the pipeline, and unset _id.
function run(pipeline) {
return coll
.aggregate([
...pipeline,
{$unset: '_id'},
])
.toArray();
}
// The documents are not evenly spaced, so the window varies in size.
assert.sameMembers(run([range(-1, 0)]), [
{x: makeDate(0), y: makeDates([0])},
{x: makeDate(1), y: makeDates([0, 1])},
{x: makeDate(1.5), y: makeDates([1, 1.5])},
{x: makeDate(2), y: makeDates([1, 1.5, 2])},
{x: makeDate(3), y: makeDates([2, 3])},
// '0' means the current document and those that tie with it.
{x: makeDate(42), y: makeDates([42, 42])},
{x: makeDate(42), y: makeDates([42, 42])},
{x: makeDate(43), y: makeDates([42, 42, 43])},
]);
// Bounds can be specified with different units.
assert.sameMembers(run([range(-1000, 0, 'millisecond')]), [
{x: makeDate(0), y: makeDates([0])},
{x: makeDate(1), y: makeDates([0, 1])},
{x: makeDate(1.5), y: makeDates([1, 1.5])},
{x: makeDate(2), y: makeDates([1, 1.5, 2])},
{x: makeDate(3), y: makeDates([2, 3])},
// '0' means the current document and those that tie with it.
{x: makeDate(42), y: makeDates([42, 42])},
{x: makeDate(42), y: makeDates([42, 42])},
{x: makeDate(43), y: makeDates([42, 42, 43])},
]);
// Fractional units are not allowed.
let error;
error = assert.throws(() => run([range(-1.5, 0, 'second')]));
assert.commandFailedWithCode(error, ErrorCodes.FailedToParse);
assert.includes(error.message, 'range-based bounds must be an integer');
// One or both endpoints can be unbounded.
assert.sameMembers(run([range('unbounded', 0)]), [
{x: makeDate(0), y: makeDates([0])},
{x: makeDate(1), y: makeDates([0, 1])},
{x: makeDate(1.5), y: makeDates([0, 1, 1.5])},
{x: makeDate(2), y: makeDates([0, 1, 1.5, 2])},
{x: makeDate(3), y: makeDates([0, 1, 1.5, 2, 3])},
// '0' means current document and those that tie with it.
{x: makeDate(42), y: makeDates([0, 1, 1.5, 2, 3, 42, 42])},
{x: makeDate(42), y: makeDates([0, 1, 1.5, 2, 3, 42, 42])},
{x: makeDate(43), y: makeDates([0, 1, 1.5, 2, 3, 42, 42, 43])},
]);
assert.sameMembers(run([range(0, 'unbounded')]), [
{x: makeDate(0), y: makeDates([0, 1, 1.5, 2, 3, 42, 42, 43])},
{x: makeDate(1), y: makeDates([1, 1.5, 2, 3, 42, 42, 43])},
{x: makeDate(1.5), y: makeDates([1.5, 2, 3, 42, 42, 43])},
{x: makeDate(2), y: makeDates([2, 3, 42, 42, 43])},
{x: makeDate(3), y: makeDates([3, 42, 42, 43])},
// '0' means current document and those that tie with it.
{x: makeDate(42), y: makeDates([42, 42, 43])},
{x: makeDate(42), y: makeDates([42, 42, 43])},
{x: makeDate(43), y: makeDates([43])},
]);
assert.sameMembers(run([range('unbounded', 'unbounded')]), [
{x: makeDate(0), y: makeDates([0, 1, 1.5, 2, 3, 42, 42, 43])},
{x: makeDate(1), y: makeDates([0, 1, 1.5, 2, 3, 42, 42, 43])},
{x: makeDate(1.5), y: makeDates([0, 1, 1.5, 2, 3, 42, 42, 43])},
{x: makeDate(2), y: makeDates([0, 1, 1.5, 2, 3, 42, 42, 43])},
{x: makeDate(3), y: makeDates([0, 1, 1.5, 2, 3, 42, 42, 43])},
{x: makeDate(42), y: makeDates([0, 1, 1.5, 2, 3, 42, 42, 43])},
{x: makeDate(42), y: makeDates([0, 1, 1.5, 2, 3, 42, 42, 43])},
{x: makeDate(43), y: makeDates([0, 1, 1.5, 2, 3, 42, 42, 43])},
]);
// Unlike '0', 'current' always means the current document.
assert.sameMembers(run([range('current', 'current'), {$match: {x: makeDate(42)}}]), [
{x: makeDate(42), y: makeDates([42])},
{x: makeDate(42), y: makeDates([42])},
]);
assert.sameMembers(run([range('current', +1), {$match: {x: makeDate(42)}}]), [
{x: makeDate(42), y: makeDates([42, 42, 43])},
{x: makeDate(42), y: makeDates([42, 43])},
]);
assert.sameMembers(run([range(-40, 'current'), {$match: {x: makeDate(42)}}]), [
{x: makeDate(42), y: makeDates([2, 3, 42])},
{x: makeDate(42), y: makeDates([2, 3, 42, 42])},
]);
// The window can be empty even if it's unbounded on one side.
assert.sameMembers(run([range('unbounded', -40)]), [
{x: makeDate(0), y: makeDates([])},
{x: makeDate(1), y: makeDates([])},
{x: makeDate(1.5), y: makeDates([])},
{x: makeDate(2), y: makeDates([])},
{x: makeDate(3), y: makeDates([])},
{x: makeDate(42), y: makeDates([0, 1, 1.5, 2])},
{x: makeDate(42), y: makeDates([0, 1, 1.5, 2])},
{x: makeDate(43), y: makeDates([0, 1, 1.5, 2, 3])},
]);
assert.sameMembers(run([range(+40, 'unbounded')]), [
{x: makeDate(0), y: makeDates([42, 42, 43])},
{x: makeDate(1), y: makeDates([42, 42, 43])},
{x: makeDate(1.5), y: makeDates([42, 42, 43])},
{x: makeDate(2), y: makeDates([42, 42, 43])},
{x: makeDate(3), y: makeDates([43])},
{x: makeDate(42), y: makeDates([])},
{x: makeDate(42), y: makeDates([])},
{x: makeDate(43), y: makeDates([])},
]);
// Time-based windows reset between partitions.
assert.commandWorked(coll.updateMany({}, {$set: {partition: "A"}}));
assert.commandWorked(coll.insert([
{partition: "B", x: makeDate(43)},
{partition: "B", x: makeDate(44)},
{partition: "B", x: makeDate(45)},
]));
assert.sameMembers(run([range(-5, 0)]), [
{partition: "A", x: makeDate(0), y: makeDates([0])},
{partition: "A", x: makeDate(1), y: makeDates([0, 1])},
{partition: "A", x: makeDate(1.5), y: makeDates([0, 1, 1.5])},
{partition: "A", x: makeDate(2), y: makeDates([0, 1, 1.5, 2])},
{partition: "A", x: makeDate(3), y: makeDates([0, 1, 1.5, 2, 3])},
{partition: "A", x: makeDate(42), y: makeDates([42, 42])},
{partition: "A", x: makeDate(42), y: makeDates([42, 42])},
{partition: "A", x: makeDate(43), y: makeDates([42, 42, 43])},
{partition: "B", x: makeDate(43), y: makeDates([43])},
{partition: "B", x: makeDate(44), y: makeDates([43, 44])},
{partition: "B", x: makeDate(45), y: makeDates([43, 44, 45])},
]);
assert.commandWorked(coll.deleteMany({partition: "B"}));
assert.commandWorked(coll.updateMany({}, [{$unset: 'partition'}]));
// If the sortBy is a non-Date, we throw an error.
assert.commandWorked(coll.insert([
{},
]));
error = assert.throws(() => {
run([range('unbounded', 'unbounded')]);
});
assert.commandFailedWithCode(error, 5429513);
assert.commandWorked(coll.remove({x: {$exists: false}}));
assert.commandWorked(coll.insert([
{x: 0},
]));
error = assert.throws(() => {
run([range('unbounded', 'unbounded')]);
});
assert.commandFailedWithCode(error, 5429513);
})();