mongo/jstests/aggregation/expressions/expression_trigonometric.js

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