mirror of https://github.com/mongodb/mongo
245 lines
8.4 KiB
JavaScript
245 lines
8.4 KiB
JavaScript
/**
|
|
* Confirm the correctness of left-to-right associativity for arithmetic operations that take
|
|
* multiple arguments.
|
|
* @tags: [
|
|
* do_not_wrap_aggregations_in_facets,
|
|
* requires_pipeline_optimization,
|
|
* ]
|
|
*/
|
|
import {assertArrayEq, getExplainedPipelineFromAggregation} from "jstests/aggregation/extras/utils.js";
|
|
import {FixtureHelpers} from "jstests/libs/fixture_helpers.js";
|
|
|
|
// TODO(SERVER-18047): Remove database creation once explain behavior is unified between replica
|
|
// sets and sharded clusters.
|
|
if (FixtureHelpers.isMongos(db) || TestData.testingReplicaSetEndpoint) {
|
|
// Create database
|
|
assert.commandWorked(db.adminCommand({"enableSharding": db.getName()}));
|
|
}
|
|
|
|
(function () {
|
|
const collName = jsTest.name();
|
|
const coll = db[collName];
|
|
coll.drop();
|
|
|
|
const $x = "$x"; // fieldpath to "block" constant folding
|
|
|
|
/**
|
|
* Verify constant folding with explain output.
|
|
* @param {(number | number[])[]} input Input arithmetic parameters, optionally nested deeply.
|
|
* @param {number[] | number} expectedOutput Expected output parameters after constant folding, or a
|
|
* scalar if the operation was calculated statically.
|
|
* @param {string} message error message
|
|
* @returns true if the explain output matches expectedOutput, and an assertion failure otherwise.
|
|
*/
|
|
function assertConstantFoldingResultForOp(op, input, expectedOutput, message) {
|
|
const buildExpressionFromArguments = (arr, op) => {
|
|
if (Array.isArray(arr)) {
|
|
return {[op]: arr.map((elt) => buildExpressionFromArguments(elt, op))};
|
|
} else if (typeof arr === "string" || arr instanceof String) {
|
|
return arr;
|
|
} else {
|
|
return {$const: arr};
|
|
}
|
|
};
|
|
const expected = buildExpressionFromArguments(expectedOutput, op);
|
|
|
|
let processedPipeline = getExplainedPipelineFromAggregation(db, db[collName], [
|
|
{$group: {_id: buildExpressionFromArguments(input, op), sum: {$sum: 1}}},
|
|
]);
|
|
|
|
assert(processedPipeline[0] && processedPipeline[0].$group);
|
|
assert.eq(processedPipeline[0].$group._id, expected, message);
|
|
|
|
return true;
|
|
}
|
|
|
|
function assertConstantFoldingResults(input, addOutput, multiplyOutput, message) {
|
|
assertConstantFoldingResultForOp("$add", input, addOutput, message);
|
|
assertConstantFoldingResultForOp("$multiply", input, multiplyOutput, message);
|
|
}
|
|
|
|
// Totally fold constants.
|
|
assertConstantFoldingResults([1, 2, 3], 6, 6, "All constants should fold.");
|
|
assertConstantFoldingResults(
|
|
[[1, 2], 3, 4, 5],
|
|
15,
|
|
120,
|
|
"Nested operations with all constants should be folded away.",
|
|
);
|
|
|
|
// Left-associative test cases.
|
|
assertConstantFoldingResults(
|
|
[1, 2, $x],
|
|
[3, $x],
|
|
[2, $x],
|
|
"Constants should fold left-to-right before the first non-constant.",
|
|
);
|
|
assertConstantFoldingResults(
|
|
[$x, 1, 2],
|
|
[$x, 1, 2],
|
|
[$x, 1, 2],
|
|
"Constants should not fold left-to-right after the first non-constant.",
|
|
);
|
|
assertConstantFoldingResults([1, $x, 2], [1, $x, 2], [1, $x, 2], "Constants should not fold across non-constants.");
|
|
|
|
assertConstantFoldingResults(
|
|
[5, 2, $x, 3, 4],
|
|
[7, $x, 3, 4],
|
|
[10, $x, 3, 4],
|
|
"Constants should fold up until a non-constant.",
|
|
);
|
|
|
|
assertConstantFoldingResults(
|
|
[$x, 1, 2, 3],
|
|
[$x, 1, 2, 3],
|
|
[$x, 1, 2, 3],
|
|
"Non-constant at start of operand list blocks folding constants.",
|
|
);
|
|
|
|
assertConstantFoldingResults(
|
|
[[1, 2, $x], 3, 4, $x, 5],
|
|
[[3, $x], 3, 4, $x, 5],
|
|
[[2, $x], 3, 4, $x, 5],
|
|
"Nested operation folds as expected.",
|
|
);
|
|
|
|
assertConstantFoldingResults(
|
|
[1, 2, [1, 2, $x], 3, 4, $x, 5],
|
|
[3, [3, $x], 3, 4, $x, 5],
|
|
[2, [2, $x], 3, 4, $x, 5],
|
|
"Nested operation folds along with outer operation following left-associative rules.",
|
|
);
|
|
|
|
assertConstantFoldingResults(
|
|
[1, 2, [1, 2, $x, 5, 6], 3, 4, 5],
|
|
[3, [3, $x, 5, 6], 3, 4, 5],
|
|
[2, [2, $x, 5, 6], 3, 4, 5],
|
|
"Nested operation folds along and outer operation does not fold past inner expression even without toplevel fieldpaths.",
|
|
);
|
|
|
|
assertConstantFoldingResults(
|
|
[1, 2, $x, 4, [1, 2, $x, 5, 6], 3, 4, 5],
|
|
[3, $x, 4, [3, $x, 5, 6], 3, 4, 5],
|
|
[2, $x, 4, [2, $x, 5, 6], 3, 4, 5],
|
|
"Nested operation folds along and even when fieldpath exists before it.",
|
|
);
|
|
})();
|
|
|
|
// Mixing $add and $multiply
|
|
(function () {
|
|
const collName = jsTest.name();
|
|
const coll = db[collName];
|
|
coll.drop();
|
|
|
|
const assertFoldedResult = (expr, expected, message) => {
|
|
let processedPipeline = getExplainedPipelineFromAggregation(db, db[collName], [
|
|
{$group: {_id: expr, sum: {$sum: 1}}},
|
|
]);
|
|
const wrapLits = (arr) => {
|
|
if (Array.isArray(arr)) {
|
|
return arr.map(wrapLits);
|
|
} else if (typeof arr === "object") {
|
|
let out = {};
|
|
Object.keys(arr).forEach((k) => {
|
|
out[k] = wrapLits(arr[k]);
|
|
});
|
|
return out;
|
|
} else if (typeof arr === "string" || arr instanceof String) {
|
|
return arr;
|
|
} else {
|
|
return {$const: arr};
|
|
}
|
|
};
|
|
|
|
assert(processedPipeline[0] && processedPipeline[0].$group);
|
|
assert.eq(processedPipeline[0].$group._id, wrapLits(expected), message);
|
|
};
|
|
|
|
assertFoldedResult(
|
|
{$add: [1, 2, {$multiply: [3, 4, "$x", 5, 6]}, 6, 7]},
|
|
{$add: [3, {$multiply: [12, "$x", 5, 6]}, 6, 7]},
|
|
"Multiply inside add will fold as much as it can.",
|
|
);
|
|
|
|
assertFoldedResult(
|
|
{$multiply: [1, 2, {$add: [3, 4, "$x", 5, 6]}, 6, 7]},
|
|
{$multiply: [2, {$add: [7, "$x", 5, 6]}, 6, 7]},
|
|
"Add inside multiply will fold as much as it can.",
|
|
);
|
|
|
|
assertFoldedResult(
|
|
{$add: [1, 2, {$multiply: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
|
|
{$add: [369, "$x", 7, 8]},
|
|
"Multiply without fieldpath will fold away and add will continue folding.",
|
|
);
|
|
|
|
assertFoldedResult(
|
|
{$multiply: [1, 2, {$add: [3, 4, 5, 6]}, 6, "$x", 7, 8]},
|
|
{$multiply: [216, "$x", 7, 8]},
|
|
"Add without fieldpath will fold away and multiply will continue folding.",
|
|
);
|
|
|
|
assertFoldedResult(
|
|
{$add: [1, 2, "$x", {$multiply: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
|
|
{$add: [3, "$x", {$multiply: [12, "$x", 5, 6]}, 6, 7, 8]},
|
|
"Constant folding nested $multiply proceeds even after outer $add stops folding.",
|
|
);
|
|
|
|
assertFoldedResult(
|
|
{$multiply: [1, 2, "$x", {$add: [3, 4, "$x", 5, 6]}, 6, 7, 8]},
|
|
{$multiply: [2, "$x", {$add: [7, "$x", 5, 6]}, 6, 7, 8]},
|
|
"Constant folding nested $add proceeds even after outer multiply stops folding.",
|
|
);
|
|
})();
|
|
|
|
// Regression tests for BFs related to SERVER-63099.
|
|
(function () {
|
|
const coll = db[jsTest.name()];
|
|
coll.drop();
|
|
|
|
const makePipeline = (id) => [{$group: {_id: id, sum: {$sum: 1}}}];
|
|
|
|
// Non-optimized comparisons -- make sure that non-optimized pipelines will give the same result as
|
|
// optimized ones.
|
|
// This is a regression test for BF-24149.
|
|
coll.insert({_id: 0, v: NumberDecimal("917.6875119062092")});
|
|
coll.insert({_id: 1, v: NumberDecimal("927.3345924210555")});
|
|
|
|
const idToString = (d) => d._id.toJSON().$numberDecimal;
|
|
|
|
assertArrayEq({
|
|
actual: coll
|
|
.aggregate(makePipeline({$multiply: [-3.14159265859, "$v", -314159255]}))
|
|
.toArray()
|
|
.map(idToString),
|
|
expected: ["915242528741.9469524422272990976000", "905721242210.0453137831269007622941"],
|
|
});
|
|
|
|
// BF-24945
|
|
coll.drop();
|
|
coll.insert({x: 0, y: 4.1});
|
|
assert(
|
|
numberDecimalsEqual(
|
|
coll
|
|
.aggregate(
|
|
makePipeline({
|
|
$multiply: [NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$x", "$y"],
|
|
}),
|
|
)
|
|
.toArray()[0]._id,
|
|
NumberDecimal(0),
|
|
),
|
|
);
|
|
assertArrayEq({
|
|
actual: coll
|
|
.aggregate(
|
|
makePipeline({
|
|
$multiply: [NumberDecimal("-9.999999999999999999999999999999999E+6144"), "$y", "$x"],
|
|
}),
|
|
)
|
|
.toArray()
|
|
.map(idToString),
|
|
expected: ["NaN"],
|
|
});
|
|
})();
|