312 lines
8.7 KiB
JavaScript
312 lines
8.7 KiB
JavaScript
/**
|
|
* Test that $sum works as a window function.
|
|
*/
|
|
import {documentEq} from "jstests/aggregation/extras/utils.js";
|
|
import {seedWithTickerData, testAccumAgainstGroup} from "jstests/aggregation/extras/window_function_helpers.js";
|
|
|
|
const coll = db[jsTestName()];
|
|
coll.drop();
|
|
|
|
// Create a collection of tickers and prices.
|
|
const nDocsPerTicker = 10;
|
|
seedWithTickerData(coll, nDocsPerTicker);
|
|
|
|
// Run the suite of partition and bounds tests against the $sum function.
|
|
testAccumAgainstGroup(coll, "$sum", 0);
|
|
|
|
coll.drop();
|
|
|
|
const nDocs = 10;
|
|
for (let i = 0; i < nDocs; i++) {
|
|
assert.commandWorked(
|
|
coll.insert({
|
|
one: i,
|
|
two: i * 2,
|
|
simpleArr: [1, 2, 3],
|
|
docArr: [{first: i}, {second: 0}],
|
|
nestedDoc: {1: {2: {3: 1}}},
|
|
mixed: i % 2 ? null : i,
|
|
}),
|
|
);
|
|
}
|
|
|
|
const origDocs = coll.find().sort({_id: 1});
|
|
function verifyResults(results, valueFunction) {
|
|
for (let i = 0; i < results.length; i++) {
|
|
// Use Object.assign to make a copy instead of pass a reference.
|
|
const correctDoc = valueFunction(i, Object.assign({}, origDocs[i]));
|
|
assert(
|
|
documentEq(correctDoc, results[i]),
|
|
"Got: " + tojson(results[i]) + "\nExpected: " + tojson(correctDoc) + "\n at position " + i + "\n",
|
|
);
|
|
}
|
|
}
|
|
|
|
function firstSum(topNum) {
|
|
return (topNum * (topNum + 1)) / 2;
|
|
}
|
|
|
|
function secondSum(topNum) {
|
|
return topNum + topNum * topNum;
|
|
}
|
|
|
|
const sortStage = {
|
|
$sort: {_id: 1},
|
|
};
|
|
|
|
// Test using $sum to count.
|
|
let result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {a: {$sum: 1, window: {documents: ["unbounded", "current"]}}},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
baseObj.a = num + 1;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test that we can sum over one field.
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {a: {$sum: "$one", window: {documents: ["unbounded", "current"]}}},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
baseObj.a = firstSum(num);
|
|
return baseObj;
|
|
});
|
|
|
|
// Test that we can sum over two fields.
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {
|
|
a: {$sum: "$one", window: {documents: ["unbounded", "current"]}},
|
|
b: {$sum: "$two", window: {documents: ["unbounded", "current"]}},
|
|
},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
baseObj.a = firstSum(num);
|
|
baseObj.b = secondSum(num);
|
|
return baseObj;
|
|
});
|
|
|
|
// Test with unbounded and left shifted window
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {out: {$sum: "$one", window: {documents: ["unbounded", -2]}}},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
baseObj.out = firstSum(Math.max(num - 2, 0));
|
|
return baseObj;
|
|
});
|
|
|
|
// Test with additional expression on window function
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {out: {$sum: {$abs: "$one"}, window: {documents: ["unbounded", "current"]}}},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
baseObj.out = firstSum(num);
|
|
return baseObj;
|
|
});
|
|
|
|
// Test that we can overwrite an existing field.
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {one: {$sum: "$one", window: {documents: ["unbounded", "current"]}}},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
baseObj.one = firstSum(num);
|
|
return baseObj;
|
|
});
|
|
|
|
// Test that we can set a sub-field in each document in an array.
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {"docArr.a": {$sum: "$one", window: {documents: ["unbounded", "current"]}}},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
baseObj.docArr = [
|
|
{first: baseObj.docArr[0].first, a: firstSum(num)},
|
|
{second: 0, a: firstSum(num)},
|
|
];
|
|
return baseObj;
|
|
});
|
|
|
|
// Test that we can set multiple numeric sub-fields in each element in an array.
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {
|
|
"docArr.1": {$sum: "$one", window: {documents: ["unbounded", "current"]}},
|
|
"docArr.2": {$sum: "$one", window: {documents: ["unbounded", "current"]}},
|
|
"simpleArr.1": {$sum: "$one", window: {documents: ["unbounded", "current"]}},
|
|
},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
const newObj = {1: firstSum(num)};
|
|
baseObj.docArr = [
|
|
{first: baseObj.docArr[0].first, 1: firstSum(num), 2: firstSum(num)},
|
|
{second: baseObj.docArr[1].second, 1: firstSum(num), 2: firstSum(num)},
|
|
];
|
|
baseObj.simpleArr = Array.apply(null, Array(baseObj.simpleArr.length)).map((_) => newObj);
|
|
return baseObj;
|
|
});
|
|
|
|
// Test that we can set a nested field.
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {"a.b": {$sum: "$one", window: {documents: ["unbounded", "current"]}}},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
baseObj.a = {b: firstSum(num)};
|
|
return baseObj;
|
|
});
|
|
|
|
// Test that we can set multiple fields/sub-fields of different types at once.
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {
|
|
"a": {$sum: "$one", window: {documents: ["unbounded", "current"]}},
|
|
"newField.a": {$sum: "$two", window: {documents: ["unbounded", "current"]}},
|
|
"simpleArr.0.b": {$sum: "$one", window: {documents: ["unbounded", "current"]}},
|
|
"nestedDoc.1.2.a": {$sum: "$one", window: {documents: ["unbounded", "current"]}},
|
|
},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
const newObj = {0: {b: firstSum(num)}};
|
|
baseObj.a = firstSum(num);
|
|
baseObj.newField = {a: secondSum(num)};
|
|
baseObj.simpleArr = Array.apply(null, Array(baseObj.simpleArr.length)).map((_) => newObj);
|
|
baseObj.nestedDoc = {1: {2: {3: 1, a: firstSum(num)}}};
|
|
return baseObj;
|
|
});
|
|
|
|
// Test $sum over a non-removable lookahead window.
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {one: {$sum: "$one", window: {documents: ["unbounded", 1]}}},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
baseObj.one = firstSum(num);
|
|
if (num < nDocs - 1) baseObj.one += num + 1;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test $sum over a non-removable window whose upper bound is behind the current.
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {one: {$sum: "$one", window: {documents: ["unbounded", -1]}}},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
baseObj.one = firstSum(num);
|
|
// Subtract the "current" value from the accumulation.
|
|
baseObj.one -= num;
|
|
return baseObj;
|
|
});
|
|
|
|
// Test that non-numeric types do not contribute to the sum.
|
|
result = coll
|
|
.aggregate([
|
|
sortStage,
|
|
{
|
|
$setWindowFields: {
|
|
sortBy: {one: 1},
|
|
output: {
|
|
mixedTypeSum: {$sum: "$mixed", window: {documents: ["unbounded", "current"]}},
|
|
},
|
|
},
|
|
},
|
|
])
|
|
.toArray();
|
|
verifyResults(result, function (num, baseObj) {
|
|
// The 'mixed' field contains alternating null and integers, manually calculate the running sum
|
|
// for each document.
|
|
baseObj.mixedTypeSum = 0;
|
|
for (let i = 0; i <= num; i++) {
|
|
baseObj.mixedTypeSum += i % 2 ? 0 : i;
|
|
}
|
|
return baseObj;
|
|
});
|