mirror of https://github.com/mongodb/mongo
294 lines
12 KiB
JavaScript
294 lines
12 KiB
JavaScript
// SERVER-32930: Basic integration tests for trigonometric aggregation expressions.
|
|
|
|
(function() {
|
|
"use strict";
|
|
// For assertErrorCode.
|
|
load("jstests/aggregation/extras/utils.js");
|
|
load('jstests/libs/sbe_assert_error_override.js'); // Override error-code-checking APIs.
|
|
|
|
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);
|
|
}());
|