diff options
author | James Wahlin <james.wahlin@mongodb.com> | 2019-10-29 18:20:46 +0000 |
---|---|---|
committer | evergreen <evergreen@mongodb.com> | 2019-10-29 18:20:46 +0000 |
commit | ee7f31f5b340c84994bee9dc1e3fb37f2d6b8c2c (patch) | |
tree | 538cefdbed11b6da4edfd9a0464fa2847890729c /src/mongo/db/pipeline/expression_nary_test.cpp | |
parent | 7a1156e8b5d15b9437df47eb99d956a11c274bfe (diff) | |
download | mongo-ee7f31f5b340c84994bee9dc1e3fb37f2d6b8c2c.tar.gz |
SERVER-40933 Split up pipeline/expression_test.cpp
Diffstat (limited to 'src/mongo/db/pipeline/expression_nary_test.cpp')
-rw-r--r-- | src/mongo/db/pipeline/expression_nary_test.cpp | 1192 |
1 files changed, 1192 insertions, 0 deletions
diff --git a/src/mongo/db/pipeline/expression_nary_test.cpp b/src/mongo/db/pipeline/expression_nary_test.cpp new file mode 100644 index 00000000000..1506ee61c68 --- /dev/null +++ b/src/mongo/db/pipeline/expression_nary_test.cpp @@ -0,0 +1,1192 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/bson/bsonmisc.h" +#include "mongo/config.h" +#include "mongo/db/exec/document_value/document.h" +#include "mongo/db/exec/document_value/document_value_test_util.h" +#include "mongo/db/exec/document_value/value_comparator.h" +#include "mongo/db/jsobj.h" +#include "mongo/db/json.h" +#include "mongo/db/pipeline/expression.h" +#include "mongo/db/pipeline/expression_context_for_test.h" +#include "mongo/db/query/collation/collator_interface_mock.h" +#include "mongo/dbtests/dbtests.h" +#include "mongo/unittest/unittest.h" + +namespace ExpressionTests { +using boost::intrusive_ptr; +using std::numeric_limits; +using std::set; +using std::string; +using std::vector; + +/** A dummy child of ExpressionNary used for testing. */ +class Testable : public ExpressionNary { +public: + virtual Value evaluate(const Document& root, Variables* variables) const { + // Just put all the values in a list. + // By default, this is not associative/commutative so the results will change if + // instantiated as commutative or associative and operations are reordered. + vector<Value> values; + for (auto&& child : _children) + values.push_back(child->evaluate(root, variables)); + return Value(values); + } + + virtual const char* getOpName() const { + return "$testable"; + } + + virtual bool isAssociative() const { + return _isAssociative; + } + + virtual bool isCommutative() const { + return _isCommutative; + } + + void acceptVisitor(ExpressionVisitor* visitor) final { + return visitor->visit(this); + } + + static intrusive_ptr<Testable> create(bool associative, bool commutative) { + return new Testable(associative, commutative); + } + +private: + Testable(bool isAssociative, bool isCommutative) + : ExpressionNary( + boost::intrusive_ptr<ExpressionContextForTest>(new ExpressionContextForTest())), + _isAssociative(isAssociative), + _isCommutative(isCommutative) {} + bool _isAssociative; + bool _isCommutative; +}; + +namespace { + +/** Convert BSONObj to a BSONObj with our $const wrappings. */ +static BSONObj constify(const BSONObj& obj, bool parentIsArray = false) { + BSONObjBuilder bob; + for (BSONObjIterator itr(obj); itr.more(); itr.next()) { + BSONElement elem = *itr; + if (elem.type() == Object) { + bob << elem.fieldName() << constify(elem.Obj(), false); + } else if (elem.type() == Array && !parentIsArray) { + // arrays within arrays are treated as constant values by the real + // parser + bob << elem.fieldName() << BSONArray(constify(elem.Obj(), true)); + } else if (elem.fieldNameStringData() == "$const" || + (elem.type() == mongo::String && elem.valueStringDataSafe().startsWith("$"))) { + bob.append(elem); + } else { + bob.append(elem.fieldName(), BSON("$const" << elem)); + } + } + return bob.obj(); +} + +/** Convert Expression to BSON. */ +static BSONObj expressionToBson(const intrusive_ptr<Expression>& expression) { + return BSON("" << expression->serialize(false)).firstElement().embeddedObject().getOwned(); +} + +class ExpressionBaseTest : public unittest::Test { +public: + void addOperand(intrusive_ptr<ExpressionNary> expr, Value arg) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + expr->addOperand(ExpressionConstant::create(expCtx, arg)); + } +}; + +class ExpressionNaryTestOneArg : public ExpressionBaseTest { +public: + virtual void assertEvaluates(Value input, Value output) { + addOperand(_expr, input); + ASSERT_VALUE_EQ(output, _expr->evaluate({}, &_expr->getExpressionContext()->variables)); + ASSERT_EQUALS(output.getType(), + _expr->evaluate({}, &_expr->getExpressionContext()->variables).getType()); + } + + intrusive_ptr<ExpressionNary> _expr; +}; + +class ExpressionNaryTestTwoArg : public ExpressionBaseTest { +public: + virtual void assertEvaluates(Value input1, Value input2, Value output) { + addOperand(_expr, input1); + addOperand(_expr, input2); + ASSERT_VALUE_EQ(output, _expr->evaluate({}, &_expr->getExpressionContext()->variables)); + ASSERT_EQUALS(output.getType(), + _expr->evaluate({}, &_expr->getExpressionContext()->variables).getType()); + } + + intrusive_ptr<ExpressionNary> _expr; +}; + +/* ------------------------- NaryExpression -------------------------- */ + +class ExpressionNaryTest : public unittest::Test { +public: + virtual void setUp() override { + _notAssociativeNorCommutative = Testable::create(false, false); + _associativeOnly = Testable::create(true, false); + _associativeAndCommutative = Testable::create(true, true); + } + +protected: + void assertDependencies(const intrusive_ptr<Testable>& expr, + const BSONArray& expectedDependencies) { + DepsTracker dependencies; + expr->addDependencies(&dependencies); + BSONArrayBuilder dependenciesBson; + for (set<string>::const_iterator i = dependencies.fields.begin(); + i != dependencies.fields.end(); + ++i) { + dependenciesBson << *i; + } + ASSERT_BSONOBJ_EQ(expectedDependencies, dependenciesBson.arr()); + ASSERT_EQUALS(false, dependencies.needWholeDocument); + ASSERT_EQUALS(false, dependencies.getNeedsAnyMetadata()); + } + + void assertContents(const intrusive_ptr<Testable>& expr, const BSONArray& expectedContents) { + ASSERT_BSONOBJ_EQ(constify(BSON("$testable" << expectedContents)), expressionToBson(expr)); + } + + void addOperandArrayToExpr(const intrusive_ptr<Testable>& expr, const BSONArray& operands) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + VariablesParseState vps = expCtx->variablesParseState; + BSONObjIterator i(operands); + while (i.more()) { + BSONElement element = i.next(); + expr->addOperand(Expression::parseOperand(expCtx, element, vps)); + } + } + + intrusive_ptr<Testable> _notAssociativeNorCommutative; + intrusive_ptr<Testable> _associativeOnly; + intrusive_ptr<Testable> _associativeAndCommutative; +}; + +TEST_F(ExpressionNaryTest, AddedConstantOperandIsSerialized) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _notAssociativeNorCommutative->addOperand(ExpressionConstant::create(expCtx, Value(9))); + assertContents(_notAssociativeNorCommutative, BSON_ARRAY(9)); +} + +TEST_F(ExpressionNaryTest, AddedFieldPathOperandIsSerialized) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _notAssociativeNorCommutative->addOperand(ExpressionFieldPath::create(expCtx, "ab.c")); + assertContents(_notAssociativeNorCommutative, BSON_ARRAY("$ab.c")); +} + +TEST_F(ExpressionNaryTest, ValidateEmptyDependencies) { + assertDependencies(_notAssociativeNorCommutative, BSONArray()); +} + +TEST_F(ExpressionNaryTest, ValidateConstantExpressionDependency) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _notAssociativeNorCommutative->addOperand(ExpressionConstant::create(expCtx, Value(1))); + assertDependencies(_notAssociativeNorCommutative, BSONArray()); +} + +TEST_F(ExpressionNaryTest, ValidateFieldPathExpressionDependency) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _notAssociativeNorCommutative->addOperand(ExpressionFieldPath::create(expCtx, "ab.c")); + assertDependencies(_notAssociativeNorCommutative, BSON_ARRAY("ab.c")); +} + +TEST_F(ExpressionNaryTest, ValidateObjectExpressionDependency) { + BSONObj spec = BSON("" << BSON("a" + << "$x" + << "q" + << "$r")); + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + BSONElement specElement = spec.firstElement(); + VariablesParseState vps = expCtx->variablesParseState; + _notAssociativeNorCommutative->addOperand( + Expression::parseObject(expCtx, specElement.Obj(), vps)); + assertDependencies(_notAssociativeNorCommutative, + BSON_ARRAY("r" + << "x")); +} + +TEST_F(ExpressionNaryTest, SerializationToBsonObj) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _notAssociativeNorCommutative->addOperand(ExpressionConstant::create(expCtx, Value(5))); + ASSERT_BSONOBJ_EQ(BSON("foo" << BSON("$testable" << BSON_ARRAY(BSON("$const" << 5)))), + BSON("foo" << _notAssociativeNorCommutative->serialize(false))); +} + +TEST_F(ExpressionNaryTest, SerializationToBsonArr) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _notAssociativeNorCommutative->addOperand(ExpressionConstant::create(expCtx, Value(5))); + ASSERT_BSONOBJ_EQ(constify(BSON_ARRAY(BSON("$testable" << BSON_ARRAY(5)))), + BSON_ARRAY(_notAssociativeNorCommutative->serialize(false))); +} + +// Verify that the internal operands are optimized +TEST_F(ExpressionNaryTest, InternalOperandOptimizationIsDone) { + BSONArray spec = BSON_ARRAY(BSON("$and" << BSONArray()) << "$abc"); + addOperandArrayToExpr(_notAssociativeNorCommutative, spec); + assertContents(_notAssociativeNorCommutative, spec); + ASSERT(_notAssociativeNorCommutative == _notAssociativeNorCommutative->optimize()); + assertContents(_notAssociativeNorCommutative, BSON_ARRAY(true << "$abc")); +} + +// Verify that if all the operands are constants, the expression is replaced +// by a constant value equivalent to the expression applied to the operands. +TEST_F(ExpressionNaryTest, AllConstantOperandOptimization) { + BSONArray spec = BSON_ARRAY(1 << 2); + addOperandArrayToExpr(_notAssociativeNorCommutative, spec); + assertContents(_notAssociativeNorCommutative, spec); + intrusive_ptr<Expression> optimized = _notAssociativeNorCommutative->optimize(); + ASSERT(_notAssociativeNorCommutative != optimized); + ASSERT_BSONOBJ_EQ(BSON("$const" << BSON_ARRAY(1 << 2)), expressionToBson(optimized)); +} + +// Verify that the optimization of grouping constant and non-constant operands +// and then applying the expression to the constant operands to reduce them to +// one constant operand is only applied if the expression is associative and +// commutative. +TEST_F(ExpressionNaryTest, GroupingOptimizationOnNotCommutativeNorAssociative) { + BSONArray spec = BSON_ARRAY(55 << 66 << "$path"); + addOperandArrayToExpr(_notAssociativeNorCommutative, spec); + assertContents(_notAssociativeNorCommutative, spec); + intrusive_ptr<Expression> optimized = _notAssociativeNorCommutative->optimize(); + ASSERT(_notAssociativeNorCommutative == optimized); + assertContents(_notAssociativeNorCommutative, spec); +} + +TEST_F(ExpressionNaryTest, GroupingOptimizationOnAssociativeOnlyFrontOperands) { + BSONArray spec = BSON_ARRAY(55 << 66 << "$path"); + addOperandArrayToExpr(_associativeOnly, spec); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + assertContents(_associativeOnly, BSON_ARRAY(BSON_ARRAY(55 << 66) << "$path")); +} + +TEST_F(ExpressionNaryTest, GroupingOptimizationOnAssociativeOnlyMiddleOperands) { + BSONArray spec = BSON_ARRAY("$path1" << 55 << 66 << "$path"); + addOperandArrayToExpr(_associativeOnly, spec); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + assertContents(_associativeOnly, BSON_ARRAY("$path1" << BSON_ARRAY(55 << 66) << "$path")); +} + +TEST_F(ExpressionNaryTest, GroupingOptimizationOnAssociativeOnlyBackOperands) { + BSONArray spec = BSON_ARRAY("$path" << 55 << 66); + addOperandArrayToExpr(_associativeOnly, spec); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + assertContents(_associativeOnly, BSON_ARRAY("$path" << BSON_ARRAY(55 << 66))); +} + +TEST_F(ExpressionNaryTest, GroupingOptimizationOnAssociativeOnlyNotExecuteOnSingleConstantsFront) { + BSONArray spec = BSON_ARRAY(55 << "$path"); + addOperandArrayToExpr(_associativeOnly, spec); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + assertContents(_associativeOnly, BSON_ARRAY(55 << "$path")); +} + +TEST_F(ExpressionNaryTest, GroupingOptimizationOnAssociativeOnlyNotExecuteOnSingleConstantsMiddle) { + BSONArray spec = BSON_ARRAY("$path1" << 55 << "$path2"); + addOperandArrayToExpr(_associativeOnly, spec); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + assertContents(_associativeOnly, BSON_ARRAY("$path1" << 55 << "$path2")); +} + +TEST_F(ExpressionNaryTest, GroupingOptimizationOnAssociativeOnlyNotExecuteOnSingleConstantsBack) { + BSONArray spec = BSON_ARRAY("$path" << 55); + addOperandArrayToExpr(_associativeOnly, spec); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + assertContents(_associativeOnly, BSON_ARRAY("$path" << 55)); +} + +TEST_F(ExpressionNaryTest, GroupingOptimizationOnCommutativeAndAssociative) { + BSONArray spec = BSON_ARRAY(55 << 66 << "$path"); + addOperandArrayToExpr(_associativeAndCommutative, spec); + assertContents(_associativeAndCommutative, spec); + intrusive_ptr<Expression> optimized = _associativeAndCommutative->optimize(); + ASSERT(_associativeAndCommutative == optimized); + assertContents(_associativeAndCommutative, BSON_ARRAY("$path" << BSON_ARRAY(55 << 66))); +} + +TEST_F(ExpressionNaryTest, FlattenOptimizationNotDoneOnOtherExpressionsForAssociativeExpressions) { + BSONArray spec = BSON_ARRAY(66 << "$path" << BSON("$sum" << BSON_ARRAY("$path" << 2))); + addOperandArrayToExpr(_associativeOnly, spec); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + assertContents(_associativeOnly, spec); +} + +TEST_F(ExpressionNaryTest, FlattenOptimizationNotDoneOnSameButNotAssociativeExpression) { + BSONArrayBuilder specBuilder; + + intrusive_ptr<Testable> innerOperand = Testable::create(false, false); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1" << 101)); + specBuilder.append(expressionToBson(innerOperand)); + _associativeOnly->addOperand(innerOperand); + + addOperandArrayToExpr(_associativeOnly, BSON_ARRAY(99 << "$path2")); + specBuilder << 99 << "$path2"; + + BSONArray spec = specBuilder.arr(); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + + assertContents(_associativeOnly, spec); +} + +// Test that if there is an expression of the same type in a non-commutative nor associative +// expression, the inner expression is not expanded. +// {"$testable" : [ { "$testable" : [ 100, "$path1"] }, 99, "$path2"] } is optimized to: +// {"$testable" : [ { "$testable" : [ 100, "$path1"] }, 99, "$path2"] } +TEST_F(ExpressionNaryTest, FlattenInnerOperandsOptimizationOnNotCommutativeNorAssociative) { + BSONArrayBuilder specBuilder; + + intrusive_ptr<Testable> innerOperand = Testable::create(false, false); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1")); + specBuilder.append(expressionToBson(innerOperand)); + _notAssociativeNorCommutative->addOperand(innerOperand); + + addOperandArrayToExpr(_notAssociativeNorCommutative, BSON_ARRAY(99 << "$path2")); + specBuilder << 99 << "$path2"; + + BSONArray spec = specBuilder.arr(); + assertContents(_notAssociativeNorCommutative, spec); + intrusive_ptr<Expression> optimized = _notAssociativeNorCommutative->optimize(); + ASSERT(_notAssociativeNorCommutative == optimized); + + assertContents(_notAssociativeNorCommutative, spec); +} + +// Test that if there is an expression of the same type as the first operand +// in a non-commutative but associative expression, the inner expression is expanded. +// Also, there shouldn't be any grouping of the operands. +// {"$testable" : [ { "$testable" : [ 100, "$path1"] }, 99, "$path2"] } is optimized to: +// {"$testable" : [ 100, "$path1", 99, "$path2"] } +TEST_F(ExpressionNaryTest, FlattenInnerOperandsOptimizationOnAssociativeOnlyFrontOperandNoGroup) { + BSONArrayBuilder specBuilder; + + intrusive_ptr<Testable> innerOperand = Testable::create(true, false); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1")); + specBuilder.append(expressionToBson(innerOperand)); + _associativeOnly->addOperand(innerOperand); + + addOperandArrayToExpr(_associativeOnly, BSON_ARRAY(99 << "$path2")); + specBuilder << 99 << "$path2"; + + BSONArray spec = specBuilder.arr(); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + + BSONArray expectedContent = BSON_ARRAY(100 << "$path1" << 99 << "$path2"); + assertContents(_associativeOnly, expectedContent); +} + +// Test that if there is an expression of the same type as the first operand +// in a non-commutative but associative expression, the inner expression is expanded. +// Partial collapsing optimization should be applied to the operands. +// {"$testable" : [ { "$testable" : [ 100, "$path1", 101] }, 99, "$path2"] } is optimized to: +// {"$testable" : [ 100, "$path1", [101, 99], "$path2"] } +TEST_F(ExpressionNaryTest, FlattenInnerOperandsOptimizationOnAssociativeOnlyFrontOperandAndGroup) { + BSONArrayBuilder specBuilder; + + intrusive_ptr<Testable> innerOperand = Testable::create(true, false); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1" << 101)); + specBuilder.append(expressionToBson(innerOperand)); + _associativeOnly->addOperand(innerOperand); + + addOperandArrayToExpr(_associativeOnly, BSON_ARRAY(99 << "$path2")); + specBuilder << 99 << "$path2"; + + BSONArray spec = specBuilder.arr(); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + + BSONArray expectedContent = BSON_ARRAY(100 << "$path1" << BSON_ARRAY(101 << 99) << "$path2"); + assertContents(_associativeOnly, expectedContent); +} + +// Test that if there is an expression of the same type in the middle of the operands +// in a non-commutative but associative expression, the inner expression is expanded. +// Partial collapsing optimization should not be applied to the operands. +// {"$testable" : [ 200, "$path3", { "$testable" : [ 100, "$path1"] }, 99, "$path2"] } is +// optimized to: {"$testable" : [ 200, "$path3", 100, "$path1", 99, "$path2"] } +TEST_F(ExpressionNaryTest, FlattenInnerOperandsOptimizationOnAssociativeOnlyMiddleOperandNoGroup) { + BSONArrayBuilder specBuilder; + + addOperandArrayToExpr(_associativeOnly, BSON_ARRAY(200 << "$path3")); + specBuilder << 200 << "$path3"; + + intrusive_ptr<Testable> innerOperand = Testable::create(true, false); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1")); + specBuilder.append(expressionToBson(innerOperand)); + _associativeOnly->addOperand(innerOperand); + + addOperandArrayToExpr(_associativeOnly, BSON_ARRAY(99 << "$path2")); + specBuilder << 99 << "$path2"; + + BSONArray spec = specBuilder.arr(); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + + BSONArray expectedContent = BSON_ARRAY(200 << "$path3" << 100 << "$path1" << 99 << "$path2"); + assertContents(_associativeOnly, expectedContent); +} + +// Test that if there is an expression of the same type in the middle of the operands +// in a non-commutative but associative expression, the inner expression is expanded. +// Partial collapsing optimization should be applied to the operands. +// {"$testable" : [ 200, "$path3", 201 { "$testable" : [ 100, "$path1", 101] }, 99, "$path2"] } is +// optimized to: {"$testable" : [ 200, "$path3", [201, 100], "$path1", [101, 99], "$path2"] } +TEST_F(ExpressionNaryTest, FlattenInnerOperandsOptimizationOnAssociativeOnlyMiddleOperandAndGroup) { + BSONArrayBuilder specBuilder; + + addOperandArrayToExpr(_associativeOnly, BSON_ARRAY(200 << "$path3" << 201)); + specBuilder << 200 << "$path3" << 201; + + intrusive_ptr<Testable> innerOperand = Testable::create(true, false); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1" << 101)); + specBuilder.append(expressionToBson(innerOperand)); + _associativeOnly->addOperand(innerOperand); + + addOperandArrayToExpr(_associativeOnly, BSON_ARRAY(99 << "$path2")); + specBuilder << 99 << "$path2"; + + BSONArray spec = specBuilder.arr(); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + + BSONArray expectedContent = BSON_ARRAY(200 << "$path3" << BSON_ARRAY(201 << 100) << "$path1" + << BSON_ARRAY(101 << 99) << "$path2"); + assertContents(_associativeOnly, expectedContent); +} + +// Test that if there is an expression of the same type in the back of the operands in a +// non-commutative but associative expression, the inner expression is expanded. +// Partial collapsing optimization should not be applied to the operands. +// {"$testable" : [ 200, "$path3", { "$testable" : [ 100, "$path1"] }] } is +// optimized to: {"$testable" : [ 200, "$path3", 100, "$path1"] } +TEST_F(ExpressionNaryTest, FlattenInnerOperandsOptimizationOnAssociativeOnlyBackOperandNoGroup) { + BSONArrayBuilder specBuilder; + + addOperandArrayToExpr(_associativeOnly, BSON_ARRAY(200 << "$path3")); + specBuilder << 200 << "$path3"; + + intrusive_ptr<Testable> innerOperand = Testable::create(true, false); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1")); + specBuilder.append(expressionToBson(innerOperand)); + _associativeOnly->addOperand(innerOperand); + + BSONArray spec = specBuilder.arr(); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + + BSONArray expectedContent = BSON_ARRAY(200 << "$path3" << 100 << "$path1"); + assertContents(_associativeOnly, expectedContent); +} + +// Test that if there is an expression of the same type in the back of the operands in a +// non-commutative but associative expression, the inner expression is expanded. +// Partial collapsing optimization should be applied to the operands. +// {"$testable" : [ 200, "$path3", 201, { "$testable" : [ 100, "$path1", 101] }] } is +// optimized to: {"$testable" : [ 200, "$path3", [201, 100], "$path1", 101] } +TEST_F(ExpressionNaryTest, FlattenInnerOperandsOptimizationOnAssociativeOnlyBackOperandAndGroup) { + BSONArrayBuilder specBuilder; + + addOperandArrayToExpr(_associativeOnly, BSON_ARRAY(200 << "$path3" << 201)); + specBuilder << 200 << "$path3" << 201; + + intrusive_ptr<Testable> innerOperand = Testable::create(true, false); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1" << 101)); + specBuilder.append(expressionToBson(innerOperand)); + _associativeOnly->addOperand(innerOperand); + + BSONArray spec = specBuilder.arr(); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + + BSONArray expectedContent = + BSON_ARRAY(200 << "$path3" << BSON_ARRAY(201 << 100) << "$path1" << 101); + assertContents(_associativeOnly, expectedContent); +} + +// Test that if there are two consecutive inner expressions of the same type in a non-commutative +// but associative expression, both expressions are correctly flattened. +// Partial collapsing optimization should not be applied to the operands. +// {"$testable" : [ { "$testable" : [ 100, "$path1"] }, { "$testable" : [ 200, "$path2"] }] } is +// optimized to: {"$testable" : [ 100, "$path1", 200, "$path2"] } +TEST_F(ExpressionNaryTest, FlattenConsecutiveInnerOperandsOptimizationOnAssociativeOnlyNoGroup) { + BSONArrayBuilder specBuilder; + + intrusive_ptr<Testable> innerOperand = Testable::create(true, false); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1")); + specBuilder.append(expressionToBson(innerOperand)); + _associativeOnly->addOperand(innerOperand); + + intrusive_ptr<Testable> innerOperand2 = Testable::create(true, false); + addOperandArrayToExpr(innerOperand2, BSON_ARRAY(200 << "$path2")); + specBuilder.append(expressionToBson(innerOperand2)); + _associativeOnly->addOperand(innerOperand2); + + BSONArray spec = specBuilder.arr(); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + + BSONArray expectedContent = BSON_ARRAY(100 << "$path1" << 200 << "$path2"); + assertContents(_associativeOnly, expectedContent); +} + +// Test that if there are two consecutive inner expressions of the same type in a non-commutative +// but associative expression, both expressions are correctly flattened. +// Partial collapsing optimization should be applied to the operands. +// {"$testable" : [ { "$testable" : [ 100, "$path1", 101] }, { "$testable" : [ 200, "$path2"] }] } +// is optimized to: {"$testable" : [ 100, "$path1", [ 101, 200], "$path2"] } +TEST_F(ExpressionNaryTest, FlattenConsecutiveInnerOperandsOptimizationOnAssociativeAndGroup) { + BSONArrayBuilder specBuilder; + + intrusive_ptr<Testable> innerOperand = Testable::create(true, false); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1" << 101)); + specBuilder.append(expressionToBson(innerOperand)); + _associativeOnly->addOperand(innerOperand); + + intrusive_ptr<Testable> innerOperand2 = Testable::create(true, false); + addOperandArrayToExpr(innerOperand2, BSON_ARRAY(200 << "$path2")); + specBuilder.append(expressionToBson(innerOperand2)); + _associativeOnly->addOperand(innerOperand2); + + BSONArray spec = specBuilder.arr(); + assertContents(_associativeOnly, spec); + intrusive_ptr<Expression> optimized = _associativeOnly->optimize(); + ASSERT(_associativeOnly == optimized); + + BSONArray expectedContent = BSON_ARRAY(100 << "$path1" << BSON_ARRAY(101 << 200) << "$path2"); + assertContents(_associativeOnly, expectedContent); +} + +// Test that inner expressions are correctly flattened and constant operands re-arranged and +// collapsed when using a commutative and associative expression. +// {"$testable" : [ 200, "$path3", 201, { "$testable" : [ 100, "$path1", 101] }, 99, "$path2"] } is +// optimized to: {"$testable" : [ "$path3", "$path1", "$path2", [200, 201, [ 100, 101], 99] ] } +TEST_F(ExpressionNaryTest, FlattenInnerOperandsOptimizationOnCommutativeAndAssociative) { + BSONArrayBuilder specBuilder; + + addOperandArrayToExpr(_associativeAndCommutative, BSON_ARRAY(200 << "$path3" << 201)); + specBuilder << 200 << "$path3" << 201; + + intrusive_ptr<Testable> innerOperand = Testable::create(true, true); + addOperandArrayToExpr(innerOperand, BSON_ARRAY(100 << "$path1" << 101)); + specBuilder.append(expressionToBson(innerOperand)); + _associativeAndCommutative->addOperand(innerOperand); + + addOperandArrayToExpr(_associativeAndCommutative, BSON_ARRAY(99 << "$path2")); + specBuilder << 99 << "$path2"; + + BSONArray spec = specBuilder.arr(); + assertContents(_associativeAndCommutative, spec); + intrusive_ptr<Expression> optimized = _associativeAndCommutative->optimize(); + ASSERT(_associativeAndCommutative == optimized); + + BSONArray expectedContent = BSON_ARRAY("$path3" + << "$path1" + << "$path2" + << BSON_ARRAY(200 << 201 << BSON_ARRAY(100 << 101) + << 99)); + assertContents(_associativeAndCommutative, expectedContent); +} + +/* ------------------------- ExpressionTrunc -------------------------- */ + +class ExpressionTruncOneArgTest : public ExpressionNaryTestOneArg { +public: + void assertEval(ImplicitValue input, ImplicitValue output) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _expr = new ExpressionTrunc(expCtx); + ExpressionNaryTestOneArg::assertEvaluates(input, output); + } +}; + +class ExpressionTruncTwoArgTest : public ExpressionNaryTestTwoArg { +public: + void assertEval(ImplicitValue input1, ImplicitValue input2, ImplicitValue output) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _expr = new ExpressionTrunc(expCtx); + ExpressionNaryTestTwoArg::assertEvaluates(input1, input2, output); + } +}; + +TEST_F(ExpressionTruncOneArgTest, IntArg1) { + assertEval(0, 0); + assertEval(0, 0); + assertEval(numeric_limits<int>::min(), numeric_limits<int>::min()); + assertEval(numeric_limits<int>::max(), numeric_limits<int>::max()); +} + +TEST_F(ExpressionTruncTwoArgTest, IntArg2) { + assertEval(0, 0, 0); + assertEval(2, -1, 0); + assertEval(29, -1, 20); + assertEval(numeric_limits<int>::min(), 10, numeric_limits<int>::min()); + assertEval(numeric_limits<int>::max(), 42, numeric_limits<int>::max()); +} + +TEST_F(ExpressionTruncOneArgTest, LongArg1) { + assertEval(0LL, 0LL); + assertEval(numeric_limits<long long>::min(), numeric_limits<long long>::min()); + assertEval(numeric_limits<long long>::max(), numeric_limits<long long>::max()); +} + +TEST_F(ExpressionTruncTwoArgTest, LongArg2) { + assertEval(0LL, 0LL, 0LL); + assertEval(2LL, -1LL, 0LL); + assertEval(29LL, -1LL, 20LL); + assertEval(numeric_limits<long long>::min(), 10LL, numeric_limits<long long>::min()); + assertEval(numeric_limits<long long>::max(), 42LL, numeric_limits<long long>::max()); +} + +TEST_F(ExpressionTruncOneArgTest, DoubleArg1) { + assertEval(2.0, 2.0); + assertEval(-2.0, -2.0); + assertEval(0.9, 0.0); + assertEval(0.1, 0.0); + assertEval(0.5, 0.0); + assertEval(1.5, 1.0); + assertEval(2.5, 2.0); + assertEval(-1.2, -1.0); + assertEval(-1.7, -1.0); + assertEval(-0.5, -0.0); + assertEval(-1.5, -1.0); + assertEval(-2.5, -2.0); + + // Outside the range of long longs (there isn't enough precision for decimals in this range, so + // should just preserve the number). + double largerThanLong = numeric_limits<long long>::max() * 2.0; + assertEval(largerThanLong, largerThanLong); + double smallerThanLong = numeric_limits<long long>::min() * 2.0; + assertEval(smallerThanLong, smallerThanLong); +} + +TEST_F(ExpressionTruncTwoArgTest, DoubleArg2) { + assertEval(2.0, 1.0, 2.0); + assertEval(-2.0, 2.0, -2.0); + assertEval(0.9, 0, 0.0); + assertEval(0.1, 0, 0.0); + assertEval(0.5, 0, 0.0); + assertEval(1.5, 0, 1.0); + assertEval(2.5, 0, 2.0); + assertEval(-1.2, 0, -1.0); + assertEval(-1.7, 0, -1.0); + assertEval(-0.5, 0, -0.0); + assertEval(-1.5, 0, -1.0); + assertEval(-2.5, 0, -2.0); + + assertEval(-3.14159265, 0, -3.0); + assertEval(-3.14159265, 1, -3.1); + assertEval(-3.14159265, 2, -3.14); + assertEval(-3.14159265, 3, -3.141); + assertEval(-3.14159265, 4, -3.1415); + assertEval(-3.14159265, 5, -3.14159); + assertEval(-3.14159265, 6, -3.141592); + assertEval(-3.14159265, 7, -3.1415926); + assertEval(-3.14159265, 100, -3.14159265); + assertEval(3.14159265, 0, 3.0); + assertEval(3.14159265, 1, 3.1); + assertEval(3.14159265, 2, 3.14); + assertEval(3.14159265, 3, 3.141); + assertEval(3.14159265, 4, 3.1415); + assertEval(3.14159265, 5, 3.14159); + assertEval(3.14159265, 6, 3.141592); + assertEval(3.14159265, 7, 3.1415926); + assertEval(3.14159265, 100, 3.14159265); + assertEval(3.14159265, -1, 0.0); + assertEval(335.14159265, -1, 330.0); + assertEval(333.14159265, -2, 300.0); +} + +TEST_F(ExpressionTruncOneArgTest, DecimalArg1) { + assertEval(Decimal128("2"), Decimal128("2.0")); + assertEval(Decimal128("-2"), Decimal128("-2.0")); + assertEval(Decimal128("0.9"), Decimal128("0.0")); + assertEval(Decimal128("0.1"), Decimal128("0.0")); + assertEval(Decimal128("-1.2"), Decimal128("-1.0")); + assertEval(Decimal128("-1.7"), Decimal128("-1.0")); + assertEval(Decimal128("123456789.9999999999999999999999999"), Decimal128("123456789")); + assertEval(Decimal128("-99999999999999999999999999999.99"), + Decimal128("-99999999999999999999999999999.00")); + assertEval(Decimal128("3.4E-6000"), Decimal128("0")); +} + +TEST_F(ExpressionTruncTwoArgTest, DecimalArg2) { + assertEval(Decimal128("2"), 0, Decimal128("2.0")); + assertEval(Decimal128("-2"), 0, Decimal128("-2.0")); + assertEval(Decimal128("0.9"), 0, Decimal128("0.0")); + assertEval(Decimal128("0.1"), 0, Decimal128("0.0")); + assertEval(Decimal128("-1.2"), 0, Decimal128("-1.0")); + assertEval(Decimal128("-1.7"), 0, Decimal128("-1.0")); + assertEval(Decimal128("123456789.9999999999999999999999999"), 0, Decimal128("123456789")); + assertEval(Decimal128("-99999999999999999999999999999.99"), + 0, + Decimal128("-99999999999999999999999999999.00")); + assertEval(Decimal128("3.4E-6000"), 0, Decimal128("0")); + + assertEval(Decimal128("-3.14159265"), 0, Decimal128("-3.0")); + assertEval(Decimal128("-3.14159265"), 1, Decimal128("-3.1")); + assertEval(Decimal128("-3.14159265"), 2, Decimal128("-3.14")); + assertEval(Decimal128("-3.14159265"), 3, Decimal128("-3.141")); + assertEval(Decimal128("-3.14159265"), 4, Decimal128("-3.1415")); + assertEval(Decimal128("-3.14159265"), 5, Decimal128("-3.14159")); + assertEval(Decimal128("-3.14159265"), 6, Decimal128("-3.141592")); + assertEval(Decimal128("-3.14159265"), 7, Decimal128("-3.1415926")); + assertEval(Decimal128("-3.14159265"), 100, Decimal128("-3.14159265")); + assertEval(Decimal128("3.14159265"), 0, Decimal128("3.0")); + assertEval(Decimal128("3.14159265"), 1, Decimal128("3.1")); + assertEval(Decimal128("3.14159265"), 2, Decimal128("3.14")); + assertEval(Decimal128("3.14159265"), Decimal128("3"), Decimal128("3.141")); + assertEval(Decimal128("3.14159265"), 4, Decimal128("3.1415")); + assertEval(Decimal128("3.14159265"), Decimal128("5"), Decimal128("3.14159")); + assertEval(Decimal128("3.14159265"), 6, Decimal128("3.141592")); + assertEval(Decimal128("3.14159265"), 7, Decimal128("3.1415926")); + assertEval(Decimal128("3.14159265"), 100, Decimal128("3.14159265")); + assertEval(Decimal128("3.14159265"), Decimal128("-1"), Decimal128("0")); + assertEval(Decimal128("335.14159265"), -1, Decimal128("330")); + assertEval(Decimal128("333.14159265"), -2, Decimal128("300")); +} + +TEST_F(ExpressionTruncOneArgTest, NullArg1) { + assertEval((BSONNULL), (BSONNULL)); +} + +TEST_F(ExpressionTruncTwoArgTest, NullArg2) { + assertEval((BSONNULL), (BSONNULL), (BSONNULL)); + assertEval((1), (BSONNULL), (BSONNULL)); + assertEval((BSONNULL), (1), (BSONNULL)); +} + +/* ------------------------- ExpressionSqrt -------------------------- */ + +class ExpressionSqrtTest : public ExpressionNaryTestOneArg { +public: + virtual void assertEvaluates(Value input, Value output) override { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _expr = new ExpressionSqrt(expCtx); + ExpressionNaryTestOneArg::assertEvaluates(input, output); + } +}; + +TEST_F(ExpressionSqrtTest, SqrtIntArg) { + assertEvaluates(Value(0), Value(0.0)); + assertEvaluates(Value(1), Value(1.0)); + assertEvaluates(Value(25), Value(5.0)); +} + +TEST_F(ExpressionSqrtTest, SqrtLongArg) { + assertEvaluates(Value(0LL), Value(0.0)); + assertEvaluates(Value(1LL), Value(1.0)); + assertEvaluates(Value(25LL), Value(5.0)); + assertEvaluates(Value(40000000000LL), Value(200000.0)); +} + +TEST_F(ExpressionSqrtTest, SqrtDoubleArg) { + assertEvaluates(Value(0.0), Value(0.0)); + assertEvaluates(Value(1.0), Value(1.0)); + assertEvaluates(Value(25.0), Value(5.0)); +} + +TEST_F(ExpressionSqrtTest, SqrtDecimalArg) { + assertEvaluates(Value(Decimal128("0")), Value(Decimal128("0"))); + assertEvaluates(Value(Decimal128("1")), Value(Decimal128("1"))); + assertEvaluates(Value(Decimal128("25")), Value(Decimal128("5"))); + assertEvaluates(Value(Decimal128("30.25")), Value(Decimal128("5.5"))); +} + +TEST_F(ExpressionSqrtTest, SqrtNullArg) { + assertEvaluates(Value(BSONNULL), Value(BSONNULL)); +} + +TEST_F(ExpressionSqrtTest, SqrtNaNArg) { + assertEvaluates(Value(std::numeric_limits<double>::quiet_NaN()), + Value(std::numeric_limits<double>::quiet_NaN())); +} + +/* ------------------------- ExpressionExp -------------------------- */ + +class ExpressionExpTest : public ExpressionNaryTestOneArg { +public: + virtual void assertEvaluates(Value input, Value output) override { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _expr = new ExpressionExp(expCtx); + ExpressionNaryTestOneArg::assertEvaluates(input, output); + } + + const Decimal128 decimalE = Decimal128("2.718281828459045235360287471352662"); +}; + +TEST_F(ExpressionExpTest, ExpIntArg) { + assertEvaluates(Value(0), Value(1.0)); + assertEvaluates(Value(1), Value(exp(1.0))); +} + +TEST_F(ExpressionExpTest, ExpLongArg) { + assertEvaluates(Value(0LL), Value(1.0)); + assertEvaluates(Value(1LL), Value(exp(1.0))); +} + +TEST_F(ExpressionExpTest, ExpDoubleArg) { + assertEvaluates(Value(0.0), Value(1.0)); + assertEvaluates(Value(1.0), Value(exp(1.0))); +} + +TEST_F(ExpressionExpTest, ExpDecimalArg) { + assertEvaluates(Value(Decimal128("0")), Value(Decimal128("1"))); + assertEvaluates(Value(Decimal128("1")), Value(decimalE)); +} + +TEST_F(ExpressionExpTest, ExpNullArg) { + assertEvaluates(Value(BSONNULL), Value(BSONNULL)); +} + +TEST_F(ExpressionExpTest, ExpNaNArg) { + assertEvaluates(Value(std::numeric_limits<double>::quiet_NaN()), + Value(std::numeric_limits<double>::quiet_NaN())); +} + +/* ------------------------- ExpressionCeil -------------------------- */ + +class ExpressionCeilTest : public ExpressionNaryTestOneArg { +public: + virtual void assertEvaluates(Value input, Value output) override { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _expr = new ExpressionCeil(expCtx); + ExpressionNaryTestOneArg::assertEvaluates(input, output); + } +}; + +TEST_F(ExpressionCeilTest, IntArg) { + assertEvaluates(Value(0), Value(0)); + assertEvaluates(Value(numeric_limits<int>::min()), Value(numeric_limits<int>::min())); + assertEvaluates(Value(numeric_limits<int>::max()), Value(numeric_limits<int>::max())); +} + +TEST_F(ExpressionCeilTest, LongArg) { + assertEvaluates(Value(0LL), Value(0LL)); + assertEvaluates(Value(numeric_limits<long long>::min()), + Value(numeric_limits<long long>::min())); + assertEvaluates(Value(numeric_limits<long long>::max()), + Value(numeric_limits<long long>::max())); +} + +TEST_F(ExpressionCeilTest, DoubleArg) { + assertEvaluates(Value(2.0), Value(2.0)); + assertEvaluates(Value(-2.0), Value(-2.0)); + assertEvaluates(Value(0.9), Value(1.0)); + assertEvaluates(Value(0.1), Value(1.0)); + assertEvaluates(Value(-1.2), Value(-1.0)); + assertEvaluates(Value(-1.7), Value(-1.0)); + + // Outside the range of long longs (there isn't enough precision for decimals in this range, so + // ceil should just preserve the number). + double largerThanLong = numeric_limits<long long>::max() * 2.0; + assertEvaluates(Value(largerThanLong), Value(largerThanLong)); + double smallerThanLong = numeric_limits<long long>::min() * 2.0; + assertEvaluates(Value(smallerThanLong), Value(smallerThanLong)); +} + +TEST_F(ExpressionCeilTest, DecimalArg) { + assertEvaluates(Value(Decimal128("2")), Value(Decimal128("2.0"))); + assertEvaluates(Value(Decimal128("-2")), Value(Decimal128("-2.0"))); + assertEvaluates(Value(Decimal128("0.9")), Value(Decimal128("1.0"))); + assertEvaluates(Value(Decimal128("0.1")), Value(Decimal128("1.0"))); + assertEvaluates(Value(Decimal128("-1.2")), Value(Decimal128("-1.0"))); + assertEvaluates(Value(Decimal128("-1.7")), Value(Decimal128("-1.0"))); + assertEvaluates(Value(Decimal128("1234567889.000000000000000000000001")), + Value(Decimal128("1234567890"))); + assertEvaluates(Value(Decimal128("-99999999999999999999999999999.99")), + Value(Decimal128("-99999999999999999999999999999.00"))); + assertEvaluates(Value(Decimal128("3.4E-6000")), Value(Decimal128("1"))); +} + +TEST_F(ExpressionCeilTest, NullArg) { + assertEvaluates(Value(BSONNULL), Value(BSONNULL)); +} + +/* ------------------------- ExpressionFloor -------------------------- */ + +class ExpressionFloorTest : public ExpressionNaryTestOneArg { +public: + virtual void assertEvaluates(Value input, Value output) override { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _expr = new ExpressionFloor(expCtx); + ExpressionNaryTestOneArg::assertEvaluates(input, output); + } +}; + +TEST_F(ExpressionFloorTest, IntArg) { + assertEvaluates(Value(0), Value(0)); + assertEvaluates(Value(numeric_limits<int>::min()), Value(numeric_limits<int>::min())); + assertEvaluates(Value(numeric_limits<int>::max()), Value(numeric_limits<int>::max())); +} + +TEST_F(ExpressionFloorTest, LongArg) { + assertEvaluates(Value(0LL), Value(0LL)); + assertEvaluates(Value(numeric_limits<long long>::min()), + Value(numeric_limits<long long>::min())); + assertEvaluates(Value(numeric_limits<long long>::max()), + Value(numeric_limits<long long>::max())); +} + +TEST_F(ExpressionFloorTest, DoubleArg) { + assertEvaluates(Value(2.0), Value(2.0)); + assertEvaluates(Value(-2.0), Value(-2.0)); + assertEvaluates(Value(0.9), Value(0.0)); + assertEvaluates(Value(0.1), Value(0.0)); + assertEvaluates(Value(-1.2), Value(-2.0)); + assertEvaluates(Value(-1.7), Value(-2.0)); + + // Outside the range of long longs (there isn't enough precision for decimals in this range, so + // floor should just preserve the number). + double largerThanLong = numeric_limits<long long>::max() * 2.0; + assertEvaluates(Value(largerThanLong), Value(largerThanLong)); + double smallerThanLong = numeric_limits<long long>::min() * 2.0; + assertEvaluates(Value(smallerThanLong), Value(smallerThanLong)); +} + +TEST_F(ExpressionFloorTest, DecimalArg) { + assertEvaluates(Value(Decimal128("2")), Value(Decimal128("2.0"))); + assertEvaluates(Value(Decimal128("-2")), Value(Decimal128("-2.0"))); + assertEvaluates(Value(Decimal128("0.9")), Value(Decimal128("0.0"))); + assertEvaluates(Value(Decimal128("0.1")), Value(Decimal128("0.0"))); + assertEvaluates(Value(Decimal128("-1.2")), Value(Decimal128("-2.0"))); + assertEvaluates(Value(Decimal128("-1.7")), Value(Decimal128("-2.0"))); + assertEvaluates(Value(Decimal128("1234567890.000000000000000000000001")), + Value(Decimal128("1234567890"))); + assertEvaluates(Value(Decimal128("-99999999999999999999999999999.99")), + Value(Decimal128("-100000000000000000000000000000"))); + assertEvaluates(Value(Decimal128("3.4E-6000")), Value(Decimal128("0"))); +} + +TEST_F(ExpressionFloorTest, NullArg) { + assertEvaluates(Value(BSONNULL), Value(BSONNULL)); +} + +/* ------------------------- ExpressionRound -------------------------- */ + +class ExpressionRoundOneArgTest : public ExpressionNaryTestOneArg { +public: + void assertEval(ImplicitValue input, ImplicitValue output) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _expr = new ExpressionRound(expCtx); + ExpressionNaryTestOneArg::assertEvaluates(input, output); + } +}; + +class ExpressionRoundTwoArgTest : public ExpressionNaryTestTwoArg { +public: + void assertEval(ImplicitValue input1, ImplicitValue input2, ImplicitValue output) { + intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + _expr = new ExpressionRound(expCtx); + ExpressionNaryTestTwoArg::assertEvaluates(input1, input2, output); + } +}; + +TEST_F(ExpressionRoundOneArgTest, IntArg1) { + assertEval(0, 0); + assertEval(numeric_limits<int>::min(), numeric_limits<int>::min()); + assertEval(numeric_limits<int>::max(), numeric_limits<int>::max()); +} + +TEST_F(ExpressionRoundTwoArgTest, IntArg2) { + assertEval(0, 0, 0); + assertEval(2, -1, 0); + assertEval(29, -1, 30); + assertEval(numeric_limits<int>::min(), 10, numeric_limits<int>::min()); + assertEval(numeric_limits<int>::max(), 42, numeric_limits<int>::max()); +} + +TEST_F(ExpressionRoundOneArgTest, LongArg1) { + assertEval(0LL, 0LL); + assertEval(numeric_limits<long long>::min(), numeric_limits<long long>::min()); + assertEval(numeric_limits<long long>::max(), numeric_limits<long long>::max()); +} + +TEST_F(ExpressionRoundTwoArgTest, LongArg2) { + assertEval(0LL, 0LL, 0LL); + assertEval(2LL, -1LL, 0LL); + assertEval(29LL, -1LL, 30LL); + assertEval(numeric_limits<long long>::min(), 10LL, numeric_limits<long long>::min()); + assertEval(numeric_limits<long long>::max(), 42LL, numeric_limits<long long>::max()); +} + +TEST_F(ExpressionRoundOneArgTest, DoubleArg1) { + assertEval(2.0, 2.0); + assertEval(-2.0, -2.0); + assertEval(0.9, 1.0); + assertEval(0.1, 0.0); + assertEval(1.5, 2.0); + assertEval(2.5, 2.0); + assertEval(3.5, 4.0); + assertEval(-1.2, -1.0); + assertEval(-1.7, -2.0); + assertEval(-1.5, -2.0); + assertEval(-2.5, -2.0); + + // Outside the range of long longs (there isn't enough precision for decimals in this range, so + // should just preserve the number). + double largerThanLong = numeric_limits<long long>::max() * 2.0; + assertEval(largerThanLong, largerThanLong); + double smallerThanLong = numeric_limits<long long>::min() * 2.0; + assertEval(smallerThanLong, smallerThanLong); +} + +TEST_F(ExpressionRoundTwoArgTest, DoubleArg2) { + assertEval(2.0, 1.0, 2.0); + assertEval(-2.0, 2.0, -2.0); + assertEval(0.9, 0, 1.0); + assertEval(0.1, 0, 0.0); + assertEval(1.5, 0, 2.0); + assertEval(2.5, 0, 2.0); + assertEval(3.5, 0, 4.0); + assertEval(-1.2, 0, -1.0); + assertEval(-1.7, 0, -2.0); + assertEval(-1.5, 0, -2.0); + assertEval(-2.5, 0, -2.0); + + assertEval(-3.14159265, 0, -3.0); + assertEval(-3.14159265, 1, -3.1); + assertEval(-3.14159265, 2, -3.14); + assertEval(-3.14159265, 3, -3.142); + assertEval(-3.14159265, 4, -3.1416); + assertEval(-3.14159265, 5, -3.14159); + assertEval(-3.14159265, 6, -3.141593); + assertEval(-3.14159265, 7, -3.1415927); + assertEval(-3.14159265, 100, -3.14159265); + assertEval(3.14159265, 0, 3.0); + assertEval(3.14159265, 1, 3.1); + assertEval(3.14159265, 2, 3.14); + assertEval(3.14159265, 3, 3.142); + assertEval(3.14159265, 4, 3.1416); + assertEval(3.14159265, 5, 3.14159); + assertEval(3.14159265, 6, 3.141593); + assertEval(3.14159265, 7, 3.1415927); + assertEval(3.14159265, 100, 3.14159265); + assertEval(3.14159265, -1, 0.0); + assertEval(335.14159265, -1, 340.0); + assertEval(333.14159265, -2, 300.0); +} + +TEST_F(ExpressionRoundOneArgTest, DecimalArg1) { + assertEval(Decimal128("2"), Decimal128("2.0")); + assertEval(Decimal128("-2"), Decimal128("-2.0")); + assertEval(Decimal128("0.9"), Decimal128("1.0")); + assertEval(Decimal128("0.1"), Decimal128("0.0")); + assertEval(Decimal128("0.5"), Decimal128("0.0")); + assertEval(Decimal128("1.5"), Decimal128("2.0")); + assertEval(Decimal128("2.5"), Decimal128("2.0")); + assertEval(Decimal128("-1.2"), Decimal128("-1.0")); + assertEval(Decimal128("-1.7"), Decimal128("-2.0")); + assertEval(Decimal128("123456789.9999999999999999999999999"), Decimal128("123456790")); + assertEval(Decimal128("-99999999999999999999999999999.99"), + Decimal128("-100000000000000000000000000000")); + assertEval(Decimal128("3.4E-6000"), Decimal128("0")); +} + +TEST_F(ExpressionRoundTwoArgTest, DecimalArg2) { + assertEval(Decimal128("2"), 0, Decimal128("2.0")); + assertEval(Decimal128("-2"), 0, Decimal128("-2.0")); + assertEval(Decimal128("0.9"), 0, Decimal128("1.0")); + assertEval(Decimal128("0.1"), 0, Decimal128("0.0")); + assertEval(Decimal128("0.5"), 0, Decimal128("0.0")); + assertEval(Decimal128("1.5"), 0, Decimal128("2.0")); + assertEval(Decimal128("2.5"), 0, Decimal128("2.0")); + assertEval(Decimal128("-1.2"), 0, Decimal128("-1.0")); + assertEval(Decimal128("-1.7"), 0, Decimal128("-2.0")); + assertEval(Decimal128("123456789.9999999999999999999999999"), 0, Decimal128("123456790")); + assertEval(Decimal128("-99999999999999999999999999999.99"), + 0, + Decimal128("-100000000000000000000000000000")); + assertEval(Decimal128("3.4E-6000"), 0, Decimal128("0")); + + assertEval(Decimal128("-3.14159265"), 0, Decimal128("-3.0")); + assertEval(Decimal128("-3.14159265"), 1, Decimal128("-3.1")); + assertEval(Decimal128("-3.14159265"), 2, Decimal128("-3.14")); + assertEval(Decimal128("-3.14159265"), 3, Decimal128("-3.142")); + assertEval(Decimal128("-3.14159265"), 4, Decimal128("-3.1416")); + assertEval(Decimal128("-3.14159265"), 5, Decimal128("-3.14159")); + assertEval(Decimal128("-3.14159265"), 6, Decimal128("-3.141593")); + assertEval(Decimal128("-3.14159265"), 7, Decimal128("-3.1415926")); + assertEval(Decimal128("-3.14159265"), 100, Decimal128("-3.14159265")); + assertEval(Decimal128("3.14159265"), 0, Decimal128("3.0")); + assertEval(Decimal128("3.14159265"), 1, Decimal128("3.1")); + assertEval(Decimal128("3.14159265"), 2, Decimal128("3.14")); + assertEval(Decimal128("3.14159265"), Decimal128("3"), Decimal128("3.142")); + assertEval(Decimal128("3.14159265"), 4, Decimal128("3.1416")); + assertEval(Decimal128("3.14159265"), Decimal128("5"), Decimal128("3.14159")); + assertEval(Decimal128("3.14159265"), 6, Decimal128("3.141593")); + assertEval(Decimal128("3.14159265"), 7, Decimal128("3.1415926")); + assertEval(Decimal128("3.14159265"), 100, Decimal128("3.14159265")); + assertEval(Decimal128("3.14159265"), Decimal128("-1"), Decimal128("0")); + assertEval(Decimal128("335.14159265"), -1, Decimal128("340")); + assertEval(Decimal128("333.14159265"), -2, Decimal128("300")); +} + +TEST_F(ExpressionRoundOneArgTest, NullArg1) { + assertEval(BSONNULL, BSONNULL); +} + +TEST_F(ExpressionRoundTwoArgTest, NullArg2) { + assertEval(BSONNULL, BSONNULL, BSONNULL); + assertEval(1, BSONNULL, BSONNULL); + assertEval(BSONNULL, 1, BSONNULL); +} + +} // anonymous namespace +} // namespace ExpressionTests |