Files
mongo/jstests/aggregation/sources/setWindowFields/sum.js
Zac 591928c619 SERVER-108478 JS formatted by prettier and remove clang-format (#39656)
GitOrigin-RevId: 6c8f6aded47f260aa4f7c231b17dae3302cb1e04
2025-08-21 17:27:09 +00:00

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;
});