mirror of https://github.com/mongodb/mongo
288 lines
12 KiB
JavaScript
288 lines
12 KiB
JavaScript
// SERVER-32930: Basic integration tests for trigonometric aggregation expressions.
|
|
|
|
import "jstests/libs/query/sbe_assert_error_override.js";
|
|
|
|
import {assertErrorCode} from "jstests/aggregation/extras/utils.js";
|
|
|
|
const coll = db.expression_trigonometric;
|
|
coll.drop();
|
|
|
|
// Constructs and inserts a document containing 'val', which are the arguments to trigonometric
|
|
// aggregation expression 'op'. If an aggregation expression has constant arguments, it will be
|
|
// constant folded and will not be evaluated during query execution. Embedding in a document ensures
|
|
// we evaluate expressions during query execution. eg. {$acos: NumberInt(1)} -> {$acos: "$y"} on
|
|
// document {"y" : NumberInt(1)}
|
|
function convertToOpOnDocument(op, val) {
|
|
let nonOptimizedOp = {};
|
|
if (Array.isArray(val) && val.length === 2) {
|
|
nonOptimizedOp = {[op]: ["$y", "$x"]};
|
|
assert.commandWorked(coll.insert({"y": val[0], "x": val[1]}));
|
|
} else {
|
|
nonOptimizedOp = {[op]: "$y"};
|
|
assert.commandWorked(coll.insert({"y": val}));
|
|
}
|
|
return nonOptimizedOp;
|
|
}
|
|
|
|
function testOp(op, val, expResult) {
|
|
const nonOptimizedOp = convertToOpOnDocument(op, val);
|
|
const pipeline = [{$project: {_id: 0, result: nonOptimizedOp}}];
|
|
assert.eq(coll.aggregate(pipeline).toArray(), [{result: expResult}]);
|
|
assert(coll.drop());
|
|
}
|
|
|
|
// Helper for testing that the aggregation expression 'op' returns expResult, approximately,
|
|
// since NumberDecimal has so many representations for a given number (0 versus 0e-40 for
|
|
// instance).
|
|
function testOpApprox(op, val, expResult) {
|
|
const nonOptimizedOp = convertToOpOnDocument(op, val);
|
|
const pipeline = [{$project: {_id: 0, result: nonOptimizedOp}}];
|
|
const res = coll.aggregate(pipeline).toArray();
|
|
const {result} = res[0];
|
|
const pipeline2 = {
|
|
$project: {
|
|
difference: {$abs: {$convert: {input: {$subtract: [result, expResult]}, to: "double"}}},
|
|
},
|
|
};
|
|
const res2 = coll.aggregate(pipeline2).toArray();
|
|
const {difference} = res2[0];
|
|
assert.lt(difference, 0.00000005);
|
|
assert(coll.drop());
|
|
}
|
|
|
|
function testErrorCode(op, val, expErrorCode) {
|
|
const nonOptimizedOp = convertToOpOnDocument(op, val);
|
|
const pipeline = [{$project: {_id: 0, result: nonOptimizedOp}}];
|
|
assertErrorCode(coll, pipeline, expErrorCode);
|
|
assert(coll.drop());
|
|
}
|
|
|
|
// Simple successful int input.
|
|
testOp("$acos", NumberInt(1), 0);
|
|
testOp("$acosh", NumberInt(1), 0);
|
|
testOp("$asin", NumberInt(0), 0);
|
|
testOp("$asinh", NumberInt(0), 0);
|
|
testOp("$atan", NumberInt(0), 0);
|
|
testOp("$atan2", [NumberInt(0), NumberInt(1)], 0);
|
|
testOp("$atan2", [NumberInt(0), NumberInt(0)], 0);
|
|
testOp("$atanh", NumberInt(0), 0);
|
|
testOp("$cos", NumberInt(0), 1);
|
|
testOp("$cosh", NumberInt(0), 1);
|
|
testOp("$sin", NumberInt(0), 0);
|
|
testOp("$sinh", NumberInt(0), 0);
|
|
testOp("$tan", NumberInt(0), 0);
|
|
testOp("$tanh", NumberInt(0), 0);
|
|
testOp("$degreesToRadians", NumberInt(0), 0);
|
|
testOp("$radiansToDegrees", NumberInt(0), 0);
|
|
|
|
// Simple successful long input.
|
|
testOp("$acos", NumberLong(1), 0);
|
|
testOp("$acosh", NumberLong(1), 0);
|
|
testOp("$asin", NumberLong(0), 0);
|
|
testOp("$asinh", NumberLong(0), 0);
|
|
testOp("$atan", NumberLong(0), 0);
|
|
testOp("$atan2", [NumberLong(0), NumberLong(1)], 0);
|
|
testOp("$atan2", [NumberLong(0), NumberLong(0)], 0);
|
|
testOp("$atanh", NumberLong(0), 0);
|
|
testOp("$cos", NumberLong(0), 1);
|
|
testOp("$cosh", NumberLong(0), 1);
|
|
testOp("$sin", NumberLong(0), 0);
|
|
testOp("$sinh", NumberLong(0), 0);
|
|
testOp("$tan", NumberLong(0), 0);
|
|
testOp("$tanh", NumberLong(0), 0);
|
|
testOp("$degreesToRadians", NumberLong(0), 0);
|
|
testOp("$radiansToDegrees", NumberLong(0), 0);
|
|
|
|
// Simple successful double input.
|
|
testOp("$acos", 1, 0);
|
|
testOp("$acosh", 1, 0);
|
|
testOp("$asin", 0, 0);
|
|
testOp("$asinh", 0, 0);
|
|
testOp("$atan", 0, 0);
|
|
testOp("$atan2", [0, 1], 0);
|
|
testOp("$atan2", [0, 0], 0);
|
|
testOp("$atanh", 0, 0);
|
|
testOp("$cos", 0, 1);
|
|
testOp("$cosh", 0, 1);
|
|
testOp("$sin", 0, 0);
|
|
testOp("$sinh", 0, 0);
|
|
testOp("$tan", 0, 0);
|
|
testOp("$tanh", 0, 0);
|
|
testOp("$degreesToRadians", 0, 0);
|
|
testOp("$radiansToDegrees", 0, 0);
|
|
|
|
// Simple successful decimal input.
|
|
testOpApprox("$acos", NumberDecimal(1), NumberDecimal(0));
|
|
testOpApprox("$acosh", NumberDecimal(1), NumberDecimal(0));
|
|
testOpApprox("$asin", NumberDecimal(0), NumberDecimal(0));
|
|
testOpApprox("$asinh", NumberDecimal(0), NumberDecimal(0));
|
|
testOpApprox("$atan", NumberDecimal(0), NumberDecimal(0));
|
|
testOpApprox("$atan2", [NumberDecimal(0), 1], NumberDecimal(0));
|
|
testOpApprox("$atan2", [NumberDecimal(0), 0], NumberDecimal(0));
|
|
testOpApprox("$atanh", NumberDecimal(0), NumberDecimal(0));
|
|
testOpApprox("$cos", NumberDecimal(0), NumberDecimal(1));
|
|
testOpApprox("$cosh", NumberDecimal(0), NumberDecimal(1));
|
|
testOpApprox("$sin", NumberDecimal(0), NumberDecimal(0));
|
|
testOpApprox("$sinh", NumberDecimal(0), NumberDecimal(0));
|
|
testOpApprox("$tan", NumberDecimal(0), NumberDecimal(0));
|
|
testOpApprox("$tanh", NumberDecimal(0), NumberDecimal(0));
|
|
testOpApprox("$degreesToRadians", NumberDecimal(0), NumberDecimal(0));
|
|
testOpApprox("$radiansToDegrees", NumberDecimal(0), NumberDecimal(0));
|
|
|
|
// Infinity input produces out of bounds error.
|
|
testErrorCode("$acos", -Infinity, 50989);
|
|
testErrorCode("$acos", NumberDecimal("-Infinity"), 50989);
|
|
testErrorCode("$acos", Infinity, 50989);
|
|
testErrorCode("$acos", NumberDecimal("Infinity"), 50989);
|
|
|
|
testErrorCode("$acosh", -Infinity, 50989);
|
|
testErrorCode("$acosh", NumberDecimal("-Infinity"), 50989);
|
|
|
|
testErrorCode("$asin", -Infinity, 50989);
|
|
testErrorCode("$asin", NumberDecimal("-Infinity"), 50989);
|
|
testErrorCode("$asin", Infinity, 50989);
|
|
testErrorCode("$asin", NumberDecimal("Infinity"), 50989);
|
|
|
|
testErrorCode("$atanh", -Infinity, 50989);
|
|
testErrorCode("$atanh", NumberDecimal("-Infinity"), 50989);
|
|
testErrorCode("$atanh", Infinity, 50989);
|
|
testErrorCode("$atanh", NumberDecimal("Infinity"), 50989);
|
|
|
|
testErrorCode("$cos", -Infinity, 50989);
|
|
testErrorCode("$cos", NumberDecimal("-Infinity"), 50989);
|
|
testErrorCode("$cos", Infinity, 50989);
|
|
testErrorCode("$cos", NumberDecimal("Infinity"), 50989);
|
|
|
|
testErrorCode("$sin", -Infinity, 50989);
|
|
testErrorCode("$sin", NumberDecimal("-Infinity"), 50989);
|
|
testErrorCode("$sin", Infinity, 50989);
|
|
testErrorCode("$sin", NumberDecimal("Infinity"), 50989);
|
|
|
|
testErrorCode("$tan", -Infinity, 50989);
|
|
testErrorCode("$tan", NumberDecimal("-Infinity"), 50989);
|
|
testErrorCode("$tan", Infinity, 50989);
|
|
testErrorCode("$tan", NumberDecimal("Infinity"), 50989);
|
|
|
|
// Infinity input produces Infinity as output.
|
|
testOp("$acosh", NumberDecimal("Infinity"), NumberDecimal("Infinity"));
|
|
testOp("$acosh", Infinity, Infinity);
|
|
|
|
testOp("$asinh", NumberDecimal("Infinity"), NumberDecimal("Infinity"));
|
|
testOp("$asinh", NumberDecimal("-Infinity"), NumberDecimal("-Infinity"));
|
|
testOp("$asinh", Infinity, Infinity);
|
|
testOp("$asinh", -Infinity, -Infinity);
|
|
testOp("$cosh", NumberDecimal("Infinity"), NumberDecimal("Infinity"));
|
|
testOp("$cosh", NumberDecimal("-Infinity"), NumberDecimal("Infinity"));
|
|
testOp("$cosh", Infinity, Infinity);
|
|
testOp("$cosh", -Infinity, Infinity);
|
|
testOp("$sinh", NumberDecimal("Infinity"), NumberDecimal("Infinity"));
|
|
testOp("$sinh", NumberDecimal("-Infinity"), NumberDecimal("-Infinity"));
|
|
testOp("$sinh", Infinity, Infinity);
|
|
testOp("$sinh", -Infinity, -Infinity);
|
|
|
|
// Infinity produces finite output (due to asymptotic bounds).
|
|
testOpApprox("$atan", NumberDecimal("Infinity"), NumberDecimal(Math.PI / 2));
|
|
testOpApprox("$atan", NumberDecimal("-Infinity"), NumberDecimal(-Math.PI / 2));
|
|
testOpApprox("$atan", Infinity, Math.PI / 2);
|
|
testOpApprox("$atan", -Infinity, -Math.PI / 2);
|
|
|
|
testOpApprox("$atan2", [NumberDecimal("Infinity"), 0], NumberDecimal(Math.PI / 2));
|
|
testOpApprox("$atan2", [NumberDecimal("-Infinity"), 0], NumberDecimal(-Math.PI / 2));
|
|
testOpApprox("$atan2", [NumberDecimal("-Infinity"), NumberDecimal("Infinity")], NumberDecimal(-Math.PI / 4));
|
|
testOpApprox("$atan2", [NumberDecimal("-Infinity"), NumberDecimal("-Infinity")], NumberDecimal((-3 * Math.PI) / 4));
|
|
testOpApprox("$atan2", [NumberDecimal("0"), NumberDecimal("-Infinity")], NumberDecimal(Math.PI));
|
|
testOpApprox("$atan2", [NumberDecimal("0"), NumberDecimal("Infinity")], NumberDecimal(0));
|
|
|
|
testOp("$tanh", NumberDecimal("Infinity"), NumberDecimal("1"));
|
|
testOp("$tanh", NumberDecimal("-Infinity"), NumberDecimal("-1"));
|
|
|
|
// Finite input produces infinite outputs.
|
|
testOp("$atanh", NumberDecimal(1), NumberDecimal("Infinity"));
|
|
testOp("$atanh", NumberDecimal(-1), NumberDecimal("-Infinity"));
|
|
testOp("$atanh", 1, Infinity);
|
|
testOp("$atanh", -1, -Infinity);
|
|
|
|
testOp("$tanh", Infinity, 1);
|
|
testOp("$tanh", -Infinity, -1);
|
|
|
|
// Int argument out of bounds.
|
|
testErrorCode("$acos", NumberInt(-2), 50989);
|
|
testErrorCode("$acos", NumberInt(2), 50989);
|
|
testErrorCode("$asin", NumberInt(-2), 50989);
|
|
testErrorCode("$asin", NumberInt(2), 50989);
|
|
testErrorCode("$acosh", NumberInt(0), 50989);
|
|
testErrorCode("$atanh", NumberInt(2), 50989);
|
|
testErrorCode("$atanh", NumberInt(-2), 50989);
|
|
|
|
// Long argument out of bounds.
|
|
testErrorCode("$acos", NumberLong(-2), 50989);
|
|
testErrorCode("$acos", NumberLong(2), 50989);
|
|
testErrorCode("$asin", NumberLong(-2), 50989);
|
|
testErrorCode("$asin", NumberLong(2), 50989);
|
|
testErrorCode("$acosh", NumberLong(0), 50989);
|
|
testErrorCode("$atanh", NumberLong(2), 50989);
|
|
testErrorCode("$atanh", NumberLong(-2), 50989);
|
|
|
|
// Double argument out of bounds.
|
|
testErrorCode("$acos", -1.1, 50989);
|
|
testErrorCode("$acos", 1.1, 50989);
|
|
testErrorCode("$asin", -1.1, 50989);
|
|
testErrorCode("$asin", 1.1, 50989);
|
|
testErrorCode("$acosh", 0.9, 50989);
|
|
testErrorCode("$atanh", -1.00001, 50989);
|
|
testErrorCode("$atanh", 1.00001, 50989);
|
|
|
|
// Decimal argument out of bounds.
|
|
testErrorCode("$acos", NumberDecimal(-1.1), 50989);
|
|
testErrorCode("$acos", NumberDecimal(1.1), 50989);
|
|
testErrorCode("$asin", NumberDecimal(-1.1), 50989);
|
|
testErrorCode("$asin", NumberDecimal(1.1), 50989);
|
|
testErrorCode("$acosh", NumberDecimal(0.9), 50989);
|
|
testErrorCode("$atanh", NumberDecimal(-1.00001), 50989);
|
|
testErrorCode("$atanh", NumberDecimal(1.000001), 50989);
|
|
|
|
// Check NaN is preserved.
|
|
["$acos", "$asin", "$atan", "$cos", "$sin", "$tan"].forEach((op) => {
|
|
testOp([op], NaN, NaN);
|
|
testOp([op], NumberDecimal(NaN), NumberDecimal(NaN));
|
|
// Check the hyperbolic version of each function.
|
|
testOp([op + "h"], NaN, NaN);
|
|
testOp([op + "h"], NumberDecimal(NaN), NumberDecimal(NaN));
|
|
});
|
|
|
|
["$radiansToDegrees", "$degreesToRadians"].forEach((op) => {
|
|
testOp([op], NaN, NaN);
|
|
testOp([op], NumberDecimal(NaN), NumberDecimal(NaN));
|
|
testOp([op], -Infinity, -Infinity);
|
|
testOp([op], NumberDecimal(-Infinity), NumberDecimal(-Infinity));
|
|
testOp([op], Infinity, Infinity);
|
|
testOp([op], NumberDecimal(Infinity), NumberDecimal(Infinity));
|
|
});
|
|
|
|
testOp("$atan2", [NumberDecimal("NaN"), NumberDecimal("NaN")], NumberDecimal("NaN"));
|
|
testOp("$atan2", [NumberDecimal("NaN"), NumberDecimal("0")], NumberDecimal("NaN"));
|
|
testOp("$atan2", [NumberDecimal("0"), NumberDecimal("NaN")], NumberDecimal("NaN"));
|
|
|
|
// atan2 additional testing with unknown constants
|
|
testOpApprox("$atan2", [NumberInt(3), NumberInt(2)], NumberDecimal(0.9827937232));
|
|
testOpApprox("$atan2", [NumberInt(621), NumberInt(84)], NumberDecimal(1.4363466632));
|
|
|
|
// Non-numeric input.
|
|
testErrorCode("$acos", "string", 28765);
|
|
testErrorCode("$acosh", "string", 28765);
|
|
testErrorCode("$asin", "string", 28765);
|
|
testErrorCode("$asinh", "string", 28765);
|
|
testErrorCode("$atan", "string", 28765);
|
|
testErrorCode("$atan2", ["string", "string"], 51044);
|
|
testErrorCode("$atan2", ["string", 0.0], 51044);
|
|
testErrorCode("$atan2", [0.0, "string"], 51045);
|
|
testErrorCode("$atanh", "string", 28765);
|
|
testErrorCode("$cos", "string", 28765);
|
|
testErrorCode("$cosh", "string", 28765);
|
|
testErrorCode("$sin", "string", 28765);
|
|
testErrorCode("$sinh", "string", 28765);
|
|
testErrorCode("$tan", "string", 28765);
|
|
testErrorCode("$tanh", "string", 28765);
|
|
testErrorCode("$degreesToRadians", "string", 28765);
|
|
testErrorCode("$radiansToDegrees", "string", 28765);
|