summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/bson/bsontypes.h2
-rw-r--r--src/mongo/db/pipeline/SConscript3
-rw-r--r--src/mongo/db/pipeline/expression.cpp67
-rw-r--r--src/mongo/db/pipeline/expression.h22
-rw-r--r--src/mongo/db/pipeline/expression_convert_test.cpp1447
-rw-r--r--src/mongo/db/pipeline/expression_date_test.cpp3
-rw-r--r--src/mongo/db/pipeline/expression_test.cpp1257
-rw-r--r--src/mongo/db/pipeline/value.h2
8 files changed, 1497 insertions, 1306 deletions
diff --git a/src/mongo/bson/bsontypes.h b/src/mongo/bson/bsontypes.h
index 5ec96545772..70c3fe9504d 100644
--- a/src/mongo/bson/bsontypes.h
+++ b/src/mongo/bson/bsontypes.h
@@ -109,7 +109,7 @@ enum BSONType {
MaxKey = 127
};
-/*
+/**
* Maps from the set of type aliases accepted by the $type query operator to the corresponding BSON
* types. Excludes "number", since this alias maps to a set of BSON types.
*/
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
index 5e06cd54358..d91f36f7a1c 100644
--- a/src/mongo/db/pipeline/SConscript
+++ b/src/mongo/db/pipeline/SConscript
@@ -392,8 +392,9 @@ env.CppUnitTest(
env.CppUnitTest(
target='agg_expression_test',
source=[
- 'expression_test.cpp',
+ 'expression_convert_test.cpp',
'expression_date_test.cpp',
+ 'expression_test.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/db/query/query_test_service_context',
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index d7f73cc3ac7..3db514b5222 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -4785,11 +4785,12 @@ private:
static void validateDoubleValueIsFinite(double inputDouble) {
uassert(ErrorCodes::ConversionFailure,
- "Attempt to cast NaN value to integer type in $convert with no onError value",
+ "Attempt to convert NaN value to integer type in $convert with no onError value",
!std::isnan(inputDouble));
- uassert(ErrorCodes::ConversionFailure,
- "Attempt to cast infinity value to integer type in $convert with no onError value",
- std::isfinite(inputDouble));
+ uassert(
+ ErrorCodes::ConversionFailure,
+ "Attempt to convert infinity value to integer type in $convert with no onError value",
+ std::isfinite(inputDouble));
}
static Value performCastDoubleToInt(Value inputValue) {
@@ -4827,11 +4828,12 @@ private:
// Performing these checks up front allows us to provide more specific error messages than
// if we just gave the same error for any 'kInvalid' conversion.
uassert(ErrorCodes::ConversionFailure,
- "Attempt to cast NaN value to integer type in $convert with no onError value",
+ "Attempt to convert NaN value to integer type in $convert with no onError value",
!inputDecimal.isNaN());
- uassert(ErrorCodes::ConversionFailure,
- "Attempt to cast infinity value to integer type in $convert with no onError value",
- !inputDecimal.isInfinite());
+ uassert(
+ ErrorCodes::ConversionFailure,
+ "Attempt to convert infinity value to integer type in $convert with no onError value",
+ !inputDecimal.isInfinite());
std::uint32_t signalingFlags = Decimal128::SignalingFlag::kNoFlag;
Value result;
@@ -4871,7 +4873,8 @@ private:
str::stream()
<< "Conversion would overflow target type in $convert with no onError value: "
<< inputDecimal.toString(),
- signalingFlags == Decimal128::SignalingFlag::kNoFlag);
+ signalingFlags == Decimal128::SignalingFlag::kNoFlag ||
+ signalingFlags == Decimal128::SignalingFlag::kInexact);
return Value(result);
}
@@ -4934,18 +4937,12 @@ intrusive_ptr<Expression> ExpressionConvert::parse(
}
Value ExpressionConvert::evaluate(const Document& root) const {
- boost::optional<BSONType> targetType = _constTargetType;
- if (!targetType) {
- // Note: the "to" field is not lazily evaluated. We should not short circuit its execution
- // when inputValue evaluates to nullish.
- auto toValue = _to->evaluate(root);
- if (!toValue.nullish()) {
- targetType = computeTargetType(toValue);
- } else {
- // targetType stays as boost::none.
- }
- }
+ auto toValue = _to->evaluate(root);
Value inputValue = _input->evaluate(root);
+ boost::optional<BSONType> targetType;
+ if (!toValue.nullish()) {
+ targetType = computeTargetType(toValue);
+ }
if (inputValue.nullish()) {
return _onNull ? _onNull->evaluate(root) : Value(BSONNULL);
@@ -4975,13 +4972,6 @@ boost::intrusive_ptr<Expression> ExpressionConvert::optimize() {
_onNull = _onNull->optimize();
}
- // The "to" value will almost always be a constant that we can parse in advance.
- auto constTo = dynamic_cast<ExpressionConstant*>(_to.get());
- if (constTo && !constTo->getValue().nullish()) {
- // Note: this may throw a user error.
- _constTargetType = computeTargetType(constTo->getValue());
- }
-
// Perform constant folding if possible. This does not support folding for $convert operations
// that have constant _to and _input values but non-constant _onError and _onNull values.
// Because _onError and _onNull are evaluated lazily, conversions that do not used the _onError
@@ -5013,6 +5003,24 @@ void ExpressionConvert::_doAddDependencies(DepsTracker* deps) const {
}
}
+namespace {
+bool isTargetTypeSupported(BSONType targetType) {
+ switch (targetType) {
+ case BSONType::NumberDouble:
+ case BSONType::String:
+ case BSONType::jstOID:
+ case BSONType::Bool:
+ case BSONType::Date:
+ case BSONType::NumberInt:
+ case BSONType::NumberLong:
+ case BSONType::NumberDecimal:
+ return true;
+ default:
+ return false;
+ }
+}
+}
+
BSONType ExpressionConvert::computeTargetType(Value targetTypeName) const {
BSONType targetType;
if (targetTypeName.getType() == BSONType::String) {
@@ -5039,10 +5047,7 @@ BSONType ExpressionConvert::computeTargetType(Value targetTypeName) const {
// Make sure the type is one of the supported "to" types for $convert.
uassert(ErrorCodes::FailedToParse,
str::stream() << "$convert with unsupported 'to' type: " << typeName(targetType),
- targetType == BSONType::NumberDouble || targetType == BSONType::String ||
- targetType == BSONType::jstOID || targetType == BSONType::Bool ||
- targetType == BSONType::Date || targetType == BSONType::NumberInt ||
- targetType == BSONType::NumberLong || targetType == BSONType::NumberDecimal);
+ isTargetTypeSupported(targetType));
return targetType;
}
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h
index 00b2268d830..7f843caffcc 100644
--- a/src/mongo/db/pipeline/expression.h
+++ b/src/mongo/db/pipeline/expression.h
@@ -1948,21 +1948,21 @@ private:
class ExpressionConvert final : public Expression {
public:
- explicit ExpressionConvert(const boost::intrusive_ptr<ExpressionContext>& expCtx)
- : Expression(expCtx) {}
+ /**
+ * Constant double representation of 2^63.
+ */
+ static const double kLongLongMaxPlusOneAsDouble;
- Value evaluate(const Document& root) const final;
- boost::intrusive_ptr<Expression> optimize() final;
static boost::intrusive_ptr<Expression> parse(
const boost::intrusive_ptr<ExpressionContext>& expCtx,
BSONElement expr,
const VariablesParseState& vpsIn);
- Value serialize(bool explain) const final;
- /**
- * Constant double representation of 2^63.
- */
- static const double kLongLongMaxPlusOneAsDouble;
+ explicit ExpressionConvert(const boost::intrusive_ptr<ExpressionContext>& expCtx)
+ : Expression(expCtx) {}
+ Value evaluate(const Document& root) const final;
+ boost::intrusive_ptr<Expression> optimize() final;
+ Value serialize(bool explain) const final;
protected:
void _doAddDependencies(DepsTracker* deps) const final;
@@ -1975,9 +1975,5 @@ private:
boost::intrusive_ptr<Expression> _to;
boost::intrusive_ptr<Expression> _onError;
boost::intrusive_ptr<Expression> _onNull;
-
- // If the 'to' field is a constant, we evaluate it once during optimization and store the result
- // here.
- boost::optional<BSONType> _constTargetType;
};
}
diff --git a/src/mongo/db/pipeline/expression_convert_test.cpp b/src/mongo/db/pipeline/expression_convert_test.cpp
new file mode 100644
index 00000000000..3f8609b1289
--- /dev/null
+++ b/src/mongo/db/pipeline/expression_convert_test.cpp
@@ -0,0 +1,1447 @@
+/**
+ * Copyright (C) 2017 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * 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 GNU Affero General 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/db/pipeline/aggregation_context_fixture.h"
+#include "mongo/db/pipeline/document_value_test_util.h"
+#include "mongo/db/pipeline/value_comparator.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+
+namespace ExpressionConvertTest {
+
+using ExpressionConvertTest = AggregationContextFixture;
+
+TEST_F(ExpressionConvertTest, ParseAndSerializeWithoutOptionalArguments) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_VALUE_EQ(Value(fromjson("{$convert: {input: '$path1', to: {$const: 'int'}}}")),
+ convertExp->serialize(false));
+
+ ASSERT_VALUE_EQ(Value(fromjson("{$convert: {input: '$path1', to: {$const: 'int'}}}")),
+ convertExp->serialize(true));
+}
+
+TEST_F(ExpressionConvertTest, ParseAndSerializeWithOnError) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"
+ << "onError"
+ << 0));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_VALUE_EQ(
+ Value(fromjson("{$convert: {input: '$path1', to: {$const: 'int'}, onError: {$const: 0}}}")),
+ convertExp->serialize(false));
+
+ ASSERT_VALUE_EQ(
+ Value(fromjson("{$convert: {input: '$path1', to: {$const: 'int'}, onError: {$const: 0}}}")),
+ convertExp->serialize(true));
+}
+
+TEST_F(ExpressionConvertTest, ParseAndSerializeWithOnNull) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"
+ << "onNull"
+ << 0));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_VALUE_EQ(
+ Value(fromjson("{$convert: {input: '$path1', to: {$const: 'int'}, onNull: {$const: 0}}}")),
+ convertExp->serialize(false));
+
+ ASSERT_VALUE_EQ(
+ Value(fromjson("{$convert: {input: '$path1', to: {$const: 'int'}, onNull: {$const: 0}}}")),
+ convertExp->serialize(true));
+}
+
+TEST_F(ExpressionConvertTest, ConvertWithoutInputFailsToParse) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("to"
+ << "int"
+ << "onError"
+ << 0));
+ ASSERT_THROWS_WITH_CHECK(Expression::parseExpression(expCtx, spec, expCtx->variablesParseState),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Missing 'input' parameter to $convert");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertWithoutToFailsToParse) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "onError"
+ << 0));
+ ASSERT_THROWS_WITH_CHECK(Expression::parseExpression(expCtx, spec, expCtx->variablesParseState),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Missing 'to' parameter to $convert");
+ });
+}
+
+TEST_F(ExpressionConvertTest, InvalidTypeNameFails) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "dinosaur"
+ << "onError"
+ << 0));
+
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(Document()),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::BadValue);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Unknown type name");
+ });
+}
+
+TEST_F(ExpressionConvertTest, NonIntegralTypeFails) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << 3.6
+ << "onError"
+ << 0));
+
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(Document()),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "In $convert, numeric 'to' argument is not an integer");
+ });
+}
+
+TEST_F(ExpressionConvertTest, NonStringNonNumericalTypeFails) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << BSON("dinosaur"
+ << "Tyrannosaurus rex")
+ << "onError"
+ << 0));
+
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(Document()),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "$convert's 'to' argument must be a string or number");
+ });
+}
+
+TEST_F(ExpressionConvertTest, IllegalTargetTypeFails) {
+ auto expCtx = getExpCtx();
+
+ std::vector<std::string> illegalTargetTypes{"minKey",
+ "object",
+ "array",
+ "binData",
+ "undefined",
+ "null",
+ "regex",
+ "dbPointer",
+ "javascript",
+ "symbol",
+ "javascriptWithScope",
+ "timestamp",
+ "maxKey"};
+
+ // Attempt a conversion with each illegal type.
+ for (auto&& typeName : illegalTargetTypes) {
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << Value(typeName)
+ << "onError"
+ << 0));
+
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(Document()),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "$convert with unsupported 'to' type");
+ });
+ }
+}
+
+TEST_F(ExpressionConvertTest, InvalidNumericTargetTypeFails) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << 100
+ << "onError"
+ << 0));
+
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate(Document()),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "In $convert, numeric value for 'to' does not correspond to a BSON type");
+ });
+}
+
+TEST_F(ExpressionConvertTest, NegativeNumericTargetTypeFails) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << -2
+ << "onError"
+ << 0));
+
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate(Document()),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "In $convert, numeric value for 'to' does not correspond to a BSON type");
+ });
+}
+
+TEST_F(ExpressionConvertTest, UnsupportedConversionFails) {
+ auto expCtx = getExpCtx();
+
+ std::vector<std::pair<Value, std::string>> unsupportedConversions{
+ {Value(OID()), "double"},
+ {Value(OID()), "int"},
+ {Value(OID()), "long"},
+ {Value(OID()), "decimal"},
+ {Value(Date_t::fromMillisSinceEpoch(0)), "objectId"},
+ {Value(0.0), "date"},
+ {Value(int{1}), "date"},
+ {Value(true), "date"},
+ {Value(0LL), "date"},
+ {Value(Decimal128("0")), "date"},
+ };
+
+ // Attempt every possible unsupported conversion.
+ for (auto conversion : unsupportedConversions) {
+ auto inputValue = conversion.first;
+ auto targetTypeName = conversion.second;
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << Value(targetTypeName)));
+
+ Document intInput{{"path1", inputValue}};
+
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(intInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Unsupported conversion");
+ });
+ }
+}
+
+TEST_F(ExpressionConvertTest, ConvertNullishInput) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document nullInput{{"path1", Value(BSONNULL)}};
+ Document undefinedInput{{"path1", Value(BSONUndefined)}};
+ Document missingInput{{"path1", Value()}};
+
+ ASSERT_VALUE_EQ(convertExp->evaluate(nullInput), Value(BSONNULL));
+ ASSERT_VALUE_EQ(convertExp->evaluate(undefinedInput), Value(BSONNULL));
+ ASSERT_VALUE_EQ(convertExp->evaluate(missingInput), Value(BSONNULL));
+}
+
+TEST_F(ExpressionConvertTest, ConvertNullishInputWithOnNull) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"
+ << "onNull"
+ << "B)"
+ << "onError"
+ << "Should not be used here"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document nullInput{{"path1", Value(BSONNULL)}};
+ Document undefinedInput{{"path1", Value(BSONUndefined)}};
+ Document missingInput{{"path1", Value()}};
+
+ ASSERT_VALUE_EQ(convertExp->evaluate(nullInput), Value("B)"_sd));
+ ASSERT_VALUE_EQ(convertExp->evaluate(undefinedInput), Value("B)"_sd));
+ ASSERT_VALUE_EQ(convertExp->evaluate(missingInput), Value("B)"_sd));
+}
+
+TEST_F(ExpressionConvertTest, NullishToReturnsNull) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "inputString"
+ << "to"
+ << "$path1"
+ << "onNull"
+ << "Should not be used here"
+ << "onError"
+ << "Also should not be used"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document nullInput{{"path1", Value(BSONNULL)}};
+ Document undefinedInput{{"path1", Value(BSONUndefined)}};
+ Document missingInput{{"path1", Value()}};
+
+ ASSERT_VALUE_EQ(convertExp->evaluate(nullInput), Value(BSONNULL));
+ ASSERT_VALUE_EQ(convertExp->evaluate(undefinedInput), Value(BSONNULL));
+ ASSERT_VALUE_EQ(convertExp->evaluate(missingInput), Value(BSONNULL));
+}
+
+#define ASSERT_VALUE_CONTENTS_AND_TYPE(v, contents, type) \
+ do { \
+ Value evaluatedResult = v; \
+ ASSERT_VALUE_EQ(evaluatedResult, Value(contents)); \
+ ASSERT_EQ(evaluatedResult.getType(), type); \
+ } while (false);
+
+TEST_F(ExpressionConvertTest, NullInputOverridesNullTo) {
+ auto expCtx = getExpCtx();
+
+ auto spec =
+ BSON("$convert" << BSON("input" << Value(BSONNULL) << "to" << Value(BSONNULL) << "onNull"
+ << "X"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(Document{}), "X"_sd, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertOptimizesToExpressionConstant) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input" << 0 << "to"
+ << "double"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ convertExp = convertExp->optimize();
+
+ auto constResult = dynamic_cast<ExpressionConstant*>(convertExp.get());
+ ASSERT(constResult);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(constResult->getValue(), 0.0, BSONType::NumberDouble);
+}
+
+TEST_F(ExpressionConvertTest, ConvertWithOnErrorOptimizesToExpressionConstant) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input" << 0 << "to"
+ << "objectId"
+ << "onError"
+ << "X"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ convertExp = convertExp->optimize();
+
+ auto constResult = dynamic_cast<ExpressionConstant*>(convertExp.get());
+ ASSERT(constResult);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(constResult->getValue(), "X"_sd, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, DoubleIdentityConversion) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "double"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document doubleInput{{"path1", Value(2.4)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleInput), 2.4, BSONType::NumberDouble);
+
+ Document doubleNaN{{"path1", std::numeric_limits<double>::quiet_NaN()}};
+ auto result = convertExp->evaluate(doubleNaN);
+ ASSERT(std::isnan(result.getDouble()));
+
+ Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
+ result = convertExp->evaluate(doubleInfinity);
+ ASSERT_EQ(result.getType(), BSONType::NumberDouble);
+ ASSERT_GT(result.getDouble(), 0.0);
+ ASSERT(std::isinf(result.getDouble()));
+
+ Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
+ result = convertExp->evaluate(doubleNegativeInfinity);
+ ASSERT_EQ(result.getType(), BSONType::NumberDouble);
+ ASSERT_LT(result.getDouble(), 0.0);
+ ASSERT(std::isinf(result.getDouble()));
+}
+
+TEST_F(ExpressionConvertTest, BoolIdentityConversion) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "bool"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document trueBoolInput{{"path1", Value(true)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(trueBoolInput), true, BSONType::Bool);
+
+ Document falseBoolInput{{"path1", Value(false)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(falseBoolInput), false, BSONType::Bool);
+}
+
+TEST_F(ExpressionConvertTest, IntIdentityConversion) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document intInput{{"path1", Value(int{123})}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(intInput), int{123}, BSONType::NumberInt);
+}
+
+TEST_F(ExpressionConvertTest, LongIdentityConversion) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "long"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document longInput{{"path1", Value(123LL)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(longInput), 123LL, BSONType::NumberLong);
+}
+
+TEST_F(ExpressionConvertTest, DecimalIdentityConversion) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "decimal"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document decimalInput{{"path1", Value(Decimal128("2.4"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(decimalInput), Decimal128("2.4"), BSONType::NumberDecimal);
+
+ Document decimalNaN{{"path1", Decimal128::kPositiveNaN}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(decimalNaN), Decimal128::kPositiveNaN, BSONType::NumberDecimal);
+
+ Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalInfinity),
+ Decimal128::kPositiveInfinity,
+ BSONType::NumberDecimal);
+
+ Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalNegativeInfinity),
+ Decimal128::kNegativeInfinity,
+ BSONType::NumberDecimal);
+}
+
+TEST_F(ExpressionConvertTest, ConvertIntToBool) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "bool"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document trueIntInput{{"path1", Value(int{1})}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(trueIntInput), true, BSONType::Bool);
+
+ Document falseIntInput{{"path1", Value(int{0})}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(falseIntInput), false, BSONType::Bool);
+}
+
+TEST_F(ExpressionConvertTest, ConvertLongToBool) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "bool"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document trueLongInput{{"path1", Value(-1ll)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(trueLongInput), true, BSONType::Bool);
+
+ Document falseLongInput{{"path1", Value(0ll)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(falseLongInput), false, BSONType::Bool);
+}
+
+TEST_F(ExpressionConvertTest, ConvertDoubleToBool) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "bool"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document trueDoubleInput{{"path1", Value(2.4)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(trueDoubleInput), true, BSONType::Bool);
+
+ Document falseDoubleInput{{"path1", Value(-0.0)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(falseDoubleInput), false, BSONType::Bool);
+
+ Document doubleNaN{{"path1", std::numeric_limits<double>::quiet_NaN()}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleNaN), true, BSONType::Bool);
+
+ Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleInfinity), true, BSONType::Bool);
+
+ Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(doubleNegativeInfinity), true, BSONType::Bool);
+}
+
+TEST_F(ExpressionConvertTest, ConvertDecimalToBool) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "bool"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document trueDecimalInput{{"path1", Value(Decimal128(5))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(trueDecimalInput), true, BSONType::Bool);
+
+ Document falseDecimalInput{{"path1", Value(Decimal128(0))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(falseDecimalInput), false, BSONType::Bool);
+
+ Document preciseZero{{"path1", Value(Decimal128("0.00"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(preciseZero), false, BSONType::Bool);
+
+ Document negativeZero{{"path1", Value(Decimal128("-0.00"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(negativeZero), false, BSONType::Bool);
+
+ Document decimalNaN{{"path1", Decimal128::kPositiveNaN}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalNaN), true, BSONType::Bool);
+
+ Document decimalNegativeNaN{{"path1", Decimal128::kNegativeNaN}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalNegativeNaN), true, BSONType::Bool);
+
+ Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalInfinity), true, BSONType::Bool);
+
+ Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(decimalNegativeInfinity), true, BSONType::Bool);
+}
+
+TEST_F(ExpressionConvertTest, ConvertNumericToDouble) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "double"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document intInput{{"path1", Value(int{1})}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(intInput), 1.0, BSONType::NumberDouble);
+
+ Document longInput{{"path1", Value(0xf00000000ll)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(longInput), 64424509440.0, BSONType::NumberDouble);
+
+ Document decimalInput{{"path1", Value(Decimal128("5.5"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalInput), 5.5, BSONType::NumberDouble);
+
+ Document boolFalse{{"path1", Value(false)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolFalse), 0.0, BSONType::NumberDouble);
+
+ Document boolTrue{{"path1", Value(true)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolTrue), 1.0, BSONType::NumberDouble);
+
+ Document decimalNaN{{"path1", Decimal128::kPositiveNaN}};
+ auto result = convertExp->evaluate(decimalNaN);
+ ASSERT_EQ(result.getType(), BSONType::NumberDouble);
+ ASSERT(std::isnan(result.getDouble()));
+
+ Document decimalNegativeNaN{{"path1", Decimal128::kNegativeNaN}};
+ result = convertExp->evaluate(decimalNegativeNaN);
+ ASSERT_EQ(result.getType(), BSONType::NumberDouble);
+ ASSERT(std::isnan(result.getDouble()));
+
+ Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
+ result = convertExp->evaluate(decimalInfinity);
+ ASSERT_EQ(result.getType(), BSONType::NumberDouble);
+ ASSERT_GT(result.getDouble(), 0.0);
+ ASSERT(std::isinf(result.getDouble()));
+
+ Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
+ result = convertExp->evaluate(decimalNegativeInfinity);
+ ASSERT_EQ(result.getType(), BSONType::NumberDouble);
+ ASSERT_LT(result.getDouble(), 0.0);
+ ASSERT(std::isinf(result.getDouble()));
+
+ // Note that the least significant bits get lost, because the significand of a double is not
+ // wide enough for the original long long value in its entirety.
+ Document largeLongInput{{"path1", Value(0xf0000000000000fLL)}};
+ result = convertExp->evaluate(largeLongInput);
+ ASSERT_EQ(static_cast<long long>(result.getDouble()), 0xf00000000000000ll);
+
+ // Again, some precision is lost in the conversion from Decimal128 to double.
+ Document preciseDecimalInput{{"path1", Value(Decimal128("1.125000000000000000005"))}};
+ result = convertExp->evaluate(preciseDecimalInput);
+ ASSERT_EQ(result.getDouble(), 1.125);
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToDouble) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "double"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document overflowInput{{"path1", Decimal128("1e309")}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+
+ Document negativeOverflowInput{{"path1", Decimal128("-1e309")}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToDoubleWithOnError) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "double"
+ << "onError"
+ << "X"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document overflowInput{{"path1", Decimal128("1e309")}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), "X"_sd, BSONType::String);
+
+ Document negativeOverflowInput{{"path1", Decimal128("-1e309")}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(negativeOverflowInput), "X"_sd, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertNumericToDecimal) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "decimal"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document intInput{{"path1", Value(int{1})}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(intInput), Decimal128(1), BSONType::NumberDecimal);
+
+ Document longInput{{"path1", Value(0xf00000000ll)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(longInput),
+ Decimal128(std::int64_t{0xf00000000LL}),
+ BSONType::NumberDecimal);
+
+ Document doubleInput{{"path1", Value(0.1)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(doubleInput), Decimal128("0.1"), BSONType::NumberDecimal);
+
+ Document boolFalse{{"path1", Value(false)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(boolFalse), Decimal128(0), BSONType::NumberDecimal);
+
+ Document boolTrue{{"path1", Value(true)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(boolTrue), Decimal128(1), BSONType::NumberDecimal);
+
+ Document doubleNaN{{"path1", std::numeric_limits<double>::quiet_NaN()}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(doubleNaN), Decimal128::kPositiveNaN, BSONType::NumberDecimal);
+
+ Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleInfinity),
+ Decimal128::kPositiveInfinity,
+ BSONType::NumberDecimal);
+
+ Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleNegativeInfinity),
+ Decimal128::kNegativeInfinity,
+ BSONType::NumberDecimal);
+
+ // Unlike the similar conversion in ConvertNumericToDouble, there is more than enough precision
+ // to store the exact orignal value in a Decimal128.
+ Document largeLongInput{{"path1", Value(0xf0000000000000fLL)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(largeLongInput), Value(0xf0000000000000fLL), BSONType::NumberDecimal);
+}
+
+TEST_F(ExpressionConvertTest, ConvertDoubleToInt) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document simpleInput{{"path1", Value(1.0)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1, BSONType::NumberInt);
+
+ // Conversions to int should always truncate the fraction (i.e., round towards 0).
+ Document nonIntegerInput1{{"path1", Value(2.1)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput1), 2, BSONType::NumberInt);
+
+ Document nonIntegerInput2{{"path1", Value(2.9)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput2), 2, BSONType::NumberInt);
+
+ Document nonIntegerInput3{{"path1", Value(-2.1)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput3), -2, BSONType::NumberInt);
+
+ Document nonIntegerInput4{{"path1", Value(-2.9)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput4), -2, BSONType::NumberInt);
+
+ int maxInt = std::numeric_limits<int>::max();
+ Document maxInput{{"path1", Value(static_cast<double>(maxInt))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(maxInput), maxInt, BSONType::NumberInt);
+
+ int minInt = std::numeric_limits<int>::lowest();
+ Document minInput{{"path1", Value(static_cast<double>(minInt))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(minInput), minInt, BSONType::NumberInt);
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDoubleToInt) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ int maxInt = std::numeric_limits<int>::max();
+ double overflowInt =
+ std::nextafter(static_cast<double>(maxInt), std::numeric_limits<double>::max());
+ Document overflowInput{{"path1", Value(overflowInt)}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+
+ int minInt = std::numeric_limits<int>::lowest();
+ double negativeOverflowInt =
+ std::nextafter(static_cast<double>(minInt), std::numeric_limits<double>::lowest());
+ Document negativeOverflowInput{{"path1", Value(negativeOverflowInt)}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+
+ Document nanInput{{"path1", Value(std::numeric_limits<double>::quiet_NaN())}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(nanInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Attempt to convert NaN value to integer");
+ });
+
+ Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(doubleInfinity),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "Attempt to convert infinity value to integer");
+ });
+
+ Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(doubleNegativeInfinity),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "Attempt to convert infinity value to integer");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDoubleToIntWithOnError) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"
+ << "onError"
+ << "X"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ int maxInt = std::numeric_limits<int>::max();
+ double overflowInt =
+ std::nextafter(static_cast<double>(maxInt), std::numeric_limits<double>::max());
+ Document overflowInput{{"path1", Value(overflowInt)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), "X"_sd, BSONType::String);
+
+ int minInt = std::numeric_limits<int>::lowest();
+ double negativeOverflowInt =
+ std::nextafter(static_cast<double>(minInt), std::numeric_limits<double>::lowest());
+ Document negativeOverflowInput{{"path1", Value(negativeOverflowInt)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(negativeOverflowInput), "X"_sd, BSONType::String);
+
+ Document nanInput{{"path1", Value(std::numeric_limits<double>::quiet_NaN())}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nanInput), "X"_sd, BSONType::String);
+
+ Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleInfinity), "X"_sd, BSONType::String);
+
+ Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(doubleNegativeInfinity), "X"_sd, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertDoubleToLong) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "long"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document simpleInput{{"path1", Value(1.0)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1, BSONType::NumberLong);
+
+ // Conversions to int should always truncate the fraction (i.e., round towards 0).
+ Document nonIntegerInput1{{"path1", Value(2.1)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput1), 2, BSONType::NumberLong);
+
+ Document nonIntegerInput2{{"path1", Value(2.9)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput2), 2, BSONType::NumberLong);
+
+ Document nonIntegerInput3{{"path1", Value(-2.1)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(nonIntegerInput3), -2, BSONType::NumberLong);
+
+ Document nonIntegerInput4{{"path1", Value(-2.9)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(nonIntegerInput4), -2, BSONType::NumberLong);
+
+ // maxVal is the highest double value that will not overflow long long.
+ double maxVal = std::nextafter(ExpressionConvert::kLongLongMaxPlusOneAsDouble, 0.0);
+ Document maxInput{{"path1", Value(maxVal)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(maxInput), static_cast<long long>(maxVal), BSONType::NumberLong);
+
+ // minVal is the lowest double value that will not overflow long long.
+ double minVal = static_cast<double>(std::numeric_limits<long long>::lowest());
+ Document minInput{{"path1", Value(minVal)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(minInput), static_cast<long long>(minVal), BSONType::NumberLong);
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDoubleToLong) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "long"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ double overflowLong = ExpressionConvert::kLongLongMaxPlusOneAsDouble;
+ Document overflowInput{{"path1", Value(overflowLong)}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+
+ double minLong = static_cast<double>(std::numeric_limits<long long>::lowest());
+ double negativeOverflowLong =
+ std::nextafter(static_cast<double>(minLong), std::numeric_limits<double>::lowest());
+ Document negativeOverflowInput{{"path1", Value(negativeOverflowLong)}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+
+ Document nanInput{{"path1", Value(std::numeric_limits<double>::quiet_NaN())}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(nanInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Attempt to convert NaN value to integer");
+ });
+
+ Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(doubleInfinity),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "Attempt to convert infinity value to integer");
+ });
+
+ Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(doubleNegativeInfinity),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "Attempt to convert infinity value to integer");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDoubleToLongWithOnError) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "long"
+ << "onError"
+ << "X"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ double overflowLong = ExpressionConvert::kLongLongMaxPlusOneAsDouble;
+ Document overflowInput{{"path1", Value(overflowLong)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), "X"_sd, BSONType::String);
+
+ double minLong = static_cast<double>(std::numeric_limits<long long>::lowest());
+ double negativeOverflowLong =
+ std::nextafter(static_cast<double>(minLong), std::numeric_limits<double>::lowest());
+ Document negativeOverflowInput{{"path1", Value(negativeOverflowLong)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(negativeOverflowInput), "X"_sd, BSONType::String);
+
+ Document nanInput{{"path1", Value(std::numeric_limits<double>::quiet_NaN())}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nanInput), "X"_sd, BSONType::String);
+
+ Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleInfinity), "X"_sd, BSONType::String);
+
+ Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(doubleNegativeInfinity), "X"_sd, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertDecimalToInt) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document simpleInput{{"path1", Value(Decimal128("1.0"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1, BSONType::NumberInt);
+
+ // Conversions to int should always truncate the fraction (i.e., round towards 0).
+ Document nonIntegerInput1{{"path1", Value(Decimal128("2.1"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput1), 2, BSONType::NumberInt);
+
+ Document nonIntegerInput2{{"path1", Value(Decimal128("2.9"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput2), 2, BSONType::NumberInt);
+
+ Document nonIntegerInput3{{"path1", Value(Decimal128("-2.1"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput3), -2, BSONType::NumberInt);
+
+ Document nonIntegerInput4{{"path1", Value(Decimal128("-2.9"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput3), -2, BSONType::NumberInt);
+
+ int maxInt = std::numeric_limits<int>::max();
+ Document maxInput{{"path1", Value(Decimal128(maxInt))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(maxInput), maxInt, BSONType::NumberInt);
+
+ int minInt = std::numeric_limits<int>::min();
+ Document minInput{{"path1", Value(Decimal128(minInt))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(minInput), minInt, BSONType::NumberInt);
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToInt) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ int maxInt = std::numeric_limits<int>::max();
+ Document overflowInput{{"path1", Decimal128(maxInt).add(Decimal128(1))}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+
+ int minInt = std::numeric_limits<int>::lowest();
+ Document negativeOverflowInput{{"path1", Decimal128(minInt).subtract(Decimal128(1))}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+
+ Document nanInput{{"path1", Decimal128::kPositiveNaN}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(nanInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Attempt to convert NaN value to integer");
+ });
+
+ Document negativeNaNInput{{"path1", Decimal128::kNegativeNaN}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeNaNInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Attempt to convert NaN value to integer");
+ });
+
+ Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(decimalInfinity),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "Attempt to convert infinity value to integer");
+ });
+
+ Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(decimalNegativeInfinity),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "Attempt to convert infinity value to integer");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToIntWithOnError) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"
+ << "onError"
+ << "X"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ int maxInt = std::numeric_limits<int>::max();
+ Document overflowInput{{"path1", Decimal128(maxInt).add(Decimal128(1))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), "X"_sd, BSONType::String);
+
+ int minInt = std::numeric_limits<int>::lowest();
+ Document negativeOverflowInput{{"path1", Decimal128(minInt).subtract(Decimal128(1))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(negativeOverflowInput), "X"_sd, BSONType::String);
+
+ Document nanInput{{"path1", Decimal128::kPositiveNaN}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nanInput), "X"_sd, BSONType::String);
+
+ Document negativeNaNInput{{"path1", Decimal128::kNegativeNaN}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(negativeNaNInput), "X"_sd, BSONType::String);
+
+ Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalInfinity), "X"_sd, BSONType::String);
+
+ Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(decimalNegativeInfinity), "X"_sd, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertDecimalToLong) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "long"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document simpleInput{{"path1", Value(Decimal128("1.0"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1, BSONType::NumberLong);
+
+ // Conversions to long should always truncate the fraction (i.e., round towards 0).
+ Document nonIntegerInput1{{"path1", Value(Decimal128("2.1"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput1), 2, BSONType::NumberLong);
+
+ Document nonIntegerInput2{{"path1", Value(Decimal128("2.9"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput2), 2, BSONType::NumberLong);
+
+ Document nonIntegerInput3{{"path1", Value(Decimal128("-2.1"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(nonIntegerInput3), -2, BSONType::NumberLong);
+
+ Document nonIntegerInput4{{"path1", Value(Decimal128("-2.9"))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(nonIntegerInput4), -2, BSONType::NumberLong);
+
+ long long maxVal = std::numeric_limits<long long>::max();
+ Document maxInput{{"path1", Value(Decimal128(std::int64_t{maxVal}))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(maxInput), maxVal, BSONType::NumberLong);
+
+ long long minVal = std::numeric_limits<long long>::min();
+ Document minInput{{"path1", Value(Decimal128(std::int64_t{minVal}))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(minInput), minVal, BSONType::NumberLong);
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToLong) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "long"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ long long maxVal = std::numeric_limits<long long>::max();
+ Document overflowInput{{"path1", Decimal128(std::int64_t{maxVal}).add(Decimal128(1))}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+
+ long long minVal = std::numeric_limits<long long>::lowest();
+ Document negativeOverflowInput{
+ {"path1", Decimal128(std::int64_t{minVal}).subtract(Decimal128(1))}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+
+ Document nanInput{{"path1", Decimal128::kPositiveNaN}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(nanInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Attempt to convert NaN value to integer");
+ });
+
+ Document negativeNaNInput{{"path1", Decimal128::kNegativeNaN}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeNaNInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Attempt to convert NaN value to integer");
+ });
+
+ Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(decimalInfinity),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "Attempt to convert infinity value to integer");
+ });
+
+ Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(decimalNegativeInfinity),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "Attempt to convert infinity value to integer");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToLongWithOnError) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "long"
+ << "onError"
+ << "X"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ long long maxVal = std::numeric_limits<long long>::max();
+ Document overflowInput{{"path1", Decimal128(std::int64_t{maxVal}).add(Decimal128(1))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), "X"_sd, BSONType::String);
+
+ long long minVal = std::numeric_limits<long long>::lowest();
+ Document negativeOverflowInput{
+ {"path1", Decimal128(std::int64_t{minVal}).subtract(Decimal128(1))}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(negativeOverflowInput), "X"_sd, BSONType::String);
+
+ Document nanInput{{"path1", Decimal128::kPositiveNaN}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nanInput), "X"_sd, BSONType::String);
+
+ Document negativeNaNInput{{"path1", Decimal128::kNegativeNaN}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(negativeNaNInput), "X"_sd, BSONType::String);
+
+ Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalInfinity), "X"_sd, BSONType::String);
+
+ Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(decimalNegativeInfinity), "X"_sd, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertIntToLong) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "long"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document simpleInput{{"path1", Value(1)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1LL, BSONType::NumberLong);
+
+ int maxInt = std::numeric_limits<int>::max();
+ Document maxInput{{"path1", Value(maxInt)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(maxInput), maxInt, BSONType::NumberLong);
+
+ int minInt = std::numeric_limits<int>::min();
+ Document minInput{{"path1", Value(minInt)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(minInput), minInt, BSONType::NumberLong);
+}
+
+TEST_F(ExpressionConvertTest, ConvertLongToInt) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document simpleInput{{"path1", Value(1)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1, BSONType::NumberInt);
+
+ long long maxInt = std::numeric_limits<int>::max();
+ Document maxInput{{"path1", Value(maxInt)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(maxInput), maxInt, BSONType::NumberInt);
+
+ long long minInt = std::numeric_limits<int>::min();
+ Document minInput{{"path1", Value(minInt)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(minInput), minInt, BSONType::NumberInt);
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsLongToInt) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ long long maxInt = std::numeric_limits<int>::max();
+ Document overflowInput{{"path1", Value(maxInt + 1)}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+
+ long long minInt = std::numeric_limits<int>::min();
+ Document negativeOverflowInput{{"path1", Value(minInt - 1)}};
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
+ AssertionException,
+ [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion would overflow target type");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertOutOfBoundsLongToIntWithOnError) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"
+ << "onError"
+ << "X"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ long long maxInt = std::numeric_limits<int>::max();
+ Document overflowInput{{"path1", Value(maxInt + 1)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), "X"_sd, BSONType::String);
+
+ long long minInt = std::numeric_limits<int>::min();
+ Document negativeOverflowInput{{"path1", Value(minInt - 1)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate(negativeOverflowInput), "X"_sd, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertBoolToInt) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "int"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document boolFalse{{"path1", Value(false)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolFalse), 0, BSONType::NumberInt);
+
+ Document boolTrue{{"path1", Value(true)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolTrue), 1, BSONType::NumberInt);
+}
+
+TEST_F(ExpressionConvertTest, ConvertBoolToLong) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$convert" << BSON("input"
+ << "$path1"
+ << "to"
+ << "long"));
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+
+ Document boolFalse{{"path1", Value(false)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolFalse), 0ll, BSONType::NumberLong);
+
+ Document boolTrue{{"path1", Value(true)}};
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolTrue), 1ll, BSONType::NumberLong);
+}
+
+} // namespace ExpressionConvertTest
+
+} // namespace mongo
diff --git a/src/mongo/db/pipeline/expression_date_test.cpp b/src/mongo/db/pipeline/expression_date_test.cpp
index f41f2b4e857..bfe72aefa3b 100644
--- a/src/mongo/db/pipeline/expression_date_test.cpp
+++ b/src/mongo/db/pipeline/expression_date_test.cpp
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2017 MongoDB Inc.
+ * Copyright (C) 2018 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
@@ -30,7 +30,6 @@
#include "mongo/db/pipeline/aggregation_context_fixture.h"
#include "mongo/db/pipeline/document_value_test_util.h"
-#include "mongo/db/pipeline/value_comparator.h"
#include "mongo/unittest/unittest.h"
namespace mongo {
diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp
index e38bc4ed346..a32473505cf 100644
--- a/src/mongo/db/pipeline/expression_test.cpp
+++ b/src/mongo/db/pipeline/expression_test.cpp
@@ -33,7 +33,6 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/json.h"
#include "mongo/db/pipeline/accumulator.h"
-#include "mongo/db/pipeline/aggregation_context_fixture.h"
#include "mongo/db/pipeline/document.h"
#include "mongo/db/pipeline/document_value_test_util.h"
#include "mongo/db/pipeline/expression.h"
@@ -5286,1262 +5285,6 @@ TEST(GetComputedPathsTest, ExpressionMapNotConsideredRenameWithDottedInputPath)
} // namespace GetComputedPathsTest
-namespace ExpressionConvertTest {
-
-// This provides access to an ExpressionContext that has a valid ServiceContext with a
-// TimeZoneDatabase via getExpCtx(), but we'll use a different name for this test suite.
-using ExpressionConvertTest = AggregationContextFixture;
-
-TEST_F(ExpressionConvertTest, ParseAndSerializeWithoutOptionalArguments) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- ASSERT_VALUE_EQ(Value(fromjson("{$convert: {input: '$path1', to: {$const: 'int'}}}")),
- convertExp->serialize(false));
-}
-
-TEST_F(ExpressionConvertTest, ParseAndSerializeWithOnError) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"
- << "onError"
- << 0));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- ASSERT_VALUE_EQ(
- Value(fromjson("{$convert: {input: '$path1', to: {$const: 'int'}, onError: {$const: 0}}}")),
- convertExp->serialize(false));
-}
-
-TEST_F(ExpressionConvertTest, ParseAndSerializeWithOnNull) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"
- << "onNull"
- << 0));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- ASSERT_VALUE_EQ(
- Value(fromjson("{$convert: {input: '$path1', to: {$const: 'int'}, onNull: {$const: 0}}}")),
- convertExp->serialize(false));
-}
-
-TEST_F(ExpressionConvertTest, ConvertWithoutInputFailsToParse) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("to"
- << "int"
- << "onError"
- << 0));
- ASSERT_THROWS_WITH_CHECK(Expression::parseExpression(expCtx, spec, expCtx->variablesParseState),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Missing 'input' parameter to $convert");
- });
-}
-
-TEST_F(ExpressionConvertTest, ConvertWithoutToFailsToParse) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "onError"
- << 0));
- ASSERT_THROWS_WITH_CHECK(Expression::parseExpression(expCtx, spec, expCtx->variablesParseState),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Missing 'to' parameter to $convert");
- });
-}
-
-TEST_F(ExpressionConvertTest, InvalidTypeNameFails) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "dinosaur"
- << "onError"
- << 0));
-
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->optimize(), AssertionException, [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::BadValue);
- ASSERT_STRING_CONTAINS(exception.reason(), "Unknown type name");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(Document()),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::BadValue);
- ASSERT_STRING_CONTAINS(exception.reason(), "Unknown type name");
- });
-}
-
-TEST_F(ExpressionConvertTest, NonIntegralTypeFails) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << 3.6
- << "onError"
- << 0));
-
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->optimize(), AssertionException, [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "In $convert, numeric 'to' argument is not an integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(Document()),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
- ASSERT_STRING_CONTAINS(
- exception.reason(),
- "In $convert, numeric 'to' argument is not an integer");
- });
-}
-
-TEST_F(ExpressionConvertTest, NonStringNonNumericalTypeFails) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << BSON("dinosaur"
- << "Tyrannosaurus rex")
- << "onError"
- << 0));
-
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(Document()),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
- ASSERT_STRING_CONTAINS(
- exception.reason(),
- "$convert's 'to' argument must be a string or number");
- });
-}
-
-TEST_F(ExpressionConvertTest, IllegalTargetTypeFails) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "minKey"
- << "onError"
- << 0));
-
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->optimize(), AssertionException, [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
- ASSERT_STRING_CONTAINS(exception.reason(), "$convert with unsupported 'to' type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(Document()),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "$convert with unsupported 'to' type");
- });
-}
-
-TEST_F(ExpressionConvertTest, InvalidNumericTargetTypeFails) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << 100
- << "onError"
- << 0));
-
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->optimize(), AssertionException, [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
- ASSERT_STRING_CONTAINS(
- exception.reason(),
- "In $convert, numeric value for 'to' does not correspond to a BSON type");
- });
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->evaluate(Document()),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::FailedToParse);
- ASSERT_STRING_CONTAINS(
- exception.reason(),
- "In $convert, numeric value for 'to' does not correspond to a BSON type");
- });
-}
-
-TEST_F(ExpressionConvertTest, UnsupportedConversionFails) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "date"));
-
- Document intInput{{"path1", Value(int{1})}};
-
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(intInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Unsupported conversion");
- });
-}
-
-TEST_F(ExpressionConvertTest, ConvertNullishInput) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document nullInput{{"path1", Value(BSONNULL)}};
- Document undefinedInput{{"path1", Value(BSONUndefined)}};
- Document missingInput{{"path1", Value()}};
-
- ASSERT_VALUE_EQ(convertExp->evaluate(nullInput), Value(BSONNULL));
- ASSERT_VALUE_EQ(convertExp->evaluate(undefinedInput), Value(BSONNULL));
- ASSERT_VALUE_EQ(convertExp->evaluate(missingInput), Value(BSONNULL));
-}
-
-TEST_F(ExpressionConvertTest, ConvertNullishInputWithOnNull) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"
- << "onNull"
- << "B)"
- << "onError"
- << "Should not be used here"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document nullInput{{"path1", Value(BSONNULL)}};
- Document undefinedInput{{"path1", Value(BSONUndefined)}};
- Document missingInput{{"path1", Value()}};
-
- ASSERT_VALUE_EQ(convertExp->evaluate(nullInput), Value("B)"_sd));
- ASSERT_VALUE_EQ(convertExp->evaluate(undefinedInput), Value("B)"_sd));
- ASSERT_VALUE_EQ(convertExp->evaluate(missingInput), Value("B)"_sd));
-}
-
-TEST_F(ExpressionConvertTest, NullishToReturnsNull) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "inputString"
- << "to"
- << "$path1"
- << "onNull"
- << "Should not be used here"
- << "onError"
- << "Also should not be used"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document nullInput{{"path1", Value(BSONNULL)}};
- Document undefinedInput{{"path1", Value(BSONUndefined)}};
- Document missingInput{{"path1", Value()}};
-
- ASSERT_VALUE_EQ(convertExp->evaluate(nullInput), Value(BSONNULL));
- ASSERT_VALUE_EQ(convertExp->evaluate(undefinedInput), Value(BSONNULL));
- ASSERT_VALUE_EQ(convertExp->evaluate(missingInput), Value(BSONNULL));
-}
-
-#define ASSERT_VALUE_CONTENTS_AND_TYPE(v, contents, type) \
- do { \
- Value evaluatedResult = v; \
- ASSERT_VALUE_EQ(evaluatedResult, Value(contents)); \
- ASSERT_EQ(evaluatedResult.getType(), type); \
- } while (false);
-
-TEST_F(ExpressionConvertTest, NullInputOverridesNullTo) {
- auto expCtx = getExpCtx();
-
- auto spec =
- BSON("$convert" << BSON("input" << Value(BSONNULL) << "to" << Value(BSONNULL) << "onNull"
- << ":x"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(Document{}), ":x"_sd, BSONType::String);
-}
-
-TEST_F(ExpressionConvertTest, FoldableConversion) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input" << 0 << "to"
- << "double"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- auto constResult = dynamic_cast<ExpressionConstant*>(convertExp.get());
- ASSERT(constResult);
- ASSERT_VALUE_CONTENTS_AND_TYPE(constResult->getValue(), 0.0, BSONType::NumberDouble);
-}
-
-TEST_F(ExpressionConvertTest, FoldableConversionWithOnError) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input" << 0 << "to"
- << "date"
- << "onError"
- << ":]"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- auto constResult = dynamic_cast<ExpressionConstant*>(convertExp.get());
- ASSERT(constResult);
- ASSERT_VALUE_CONTENTS_AND_TYPE(constResult->getValue(), ":]"_sd, BSONType::String);
-}
-
-TEST_F(ExpressionConvertTest, ConvertNumericToBool) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "bool"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document trueIntInput{{"path1", Value(int{1})}};
- Document trueLongInput{{"path1", Value(-1ll)}};
- Document trueDoubleInput{{"path1", Value(2.4)}};
- Document trueDecimalInput{{"path1", Value(Decimal128(5))}};
-
- Document falseIntInput{{"path1", Value(int{0})}};
- Document falseLongInput{{"path1", Value(0ll)}};
- Document falseDoubleInput{{"path1", Value(-0.0)}};
- Document falseDecimalInput{{"path1", Value(Decimal128(0))}};
-
- Document doubleNaN{{"path1", std::numeric_limits<double>::quiet_NaN()}};
- Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
- Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
-
- Document decimalNaN{{"path1", Decimal128::kPositiveNaN}};
- Document decimalNegativeNaN{{"path1", Decimal128::kNegativeNaN}};
- Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
- Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(trueIntInput), true, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(trueLongInput), true, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(trueDoubleInput), true, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(trueDecimalInput), true, BSONType::Bool);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(falseIntInput), false, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(falseLongInput), false, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(falseDoubleInput), false, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(falseDecimalInput), false, BSONType::Bool);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleNaN), true, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleInfinity), true, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(doubleNegativeInfinity), true, BSONType::Bool);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalNaN), true, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalNegativeNaN), true, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalInfinity), true, BSONType::Bool);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(decimalNegativeInfinity), true, BSONType::Bool);
-}
-
-TEST_F(ExpressionConvertTest, ConvertNumericToDouble) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "double"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document intInput{{"path1", Value(int{1})}};
- Document longInput{{"path1", Value(0xf00000000ll)}};
- Document decimalInput{{"path1", Value(Decimal128("5.5"))}};
-
- Document boolFalse{{"path1", Value(false)}};
- Document boolTrue{{"path1", Value(true)}};
-
- Document decimalNaN{{"path1", Decimal128::kPositiveNaN}};
- Document decimalNegativeNaN{{"path1", Decimal128::kNegativeNaN}};
- Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
- Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
-
- Document largeLongInput{{"path1", Value(0xf0000000000000fll)}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(intInput), 1.0, BSONType::NumberDouble);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(longInput), 64424509440.0, BSONType::NumberDouble);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(decimalInput), 5.5, BSONType::NumberDouble);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolFalse), 0.0, BSONType::NumberDouble);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolTrue), 1.0, BSONType::NumberDouble);
-
- Value result;
- result = convertExp->evaluate(decimalNaN);
- ASSERT_EQ(result.getType(), BSONType::NumberDouble);
- ASSERT(std::isnan(result.getDouble()));
-
- result = convertExp->evaluate(decimalNegativeNaN);
- ASSERT_EQ(result.getType(), BSONType::NumberDouble);
- ASSERT(std::isnan(result.getDouble()));
-
- result = convertExp->evaluate(decimalInfinity);
- ASSERT_EQ(result.getType(), BSONType::NumberDouble);
- ASSERT_GT(result.getDouble(), 0.0);
- ASSERT(std::isinf(result.getDouble()));
-
- result = convertExp->evaluate(decimalNegativeInfinity);
- ASSERT_EQ(result.getType(), BSONType::NumberDouble);
- ASSERT_LT(result.getDouble(), 0.0);
- ASSERT(std::isinf(result.getDouble()));
-
- // Note that the least significant bits get lost, because the significand of a double is not
- // wide enough for the original long long value in its entirety.
- result = convertExp->evaluate(largeLongInput);
- ASSERT_EQ(static_cast<long long>(result.getDouble()), 0xf00000000000000ll);
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToDouble) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "double"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document overflowInput{{"path1", Decimal128("1e309")}};
- Document negativeOverflowInput{{"path1", Decimal128("-1e309")}};
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToDoubleWithOnError) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "double"
- << "onError"
- << ":/"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document overflowInput{{"path1", Decimal128("1e309")}};
- Document negativeOverflowInput{{"path1", Decimal128("-1e309")}};
-
- std::string contents = ":/";
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(negativeOverflowInput), contents, BSONType::String);
-}
-
-TEST_F(ExpressionConvertTest, ConvertNumericToDecimal) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "decimal"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document intInput{{"path1", Value(int{1})}};
- Document longInput{{"path1", Value(0xf00000000ll)}};
- Document doubleInput{{"path1", Value(Decimal128("0.1"))}};
-
- Document boolFalse{{"path1", Value(false)}};
- Document boolTrue{{"path1", Value(true)}};
-
- Document doubleNaN{{"path1", std::numeric_limits<double>::quiet_NaN()}};
- Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
- Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
-
- Document largeLongInput{{"path1", Value(0xf0000000000000fll)}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(intInput), Decimal128(1), BSONType::NumberDecimal);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(longInput),
- Decimal128(int64_t{0xf00000000LL}),
- BSONType::NumberDecimal);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(doubleInput), Decimal128("0.1"), BSONType::NumberDecimal);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(boolFalse), Decimal128(0), BSONType::NumberDecimal);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(boolTrue), Decimal128(1), BSONType::NumberDecimal);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(doubleNaN), Decimal128::kPositiveNaN, BSONType::NumberDecimal);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleInfinity),
- Decimal128::kPositiveInfinity,
- BSONType::NumberDecimal);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(doubleNegativeInfinity),
- Decimal128::kNegativeInfinity,
- BSONType::NumberDecimal);
-
- // Unlike the similar conversion in ConvertNumericToDouble, there is more than enough precision
- // to store the exact orignal value in a Decimal128.
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(largeLongInput), Value(0xf0000000000000fll), BSONType::NumberDecimal);
-}
-
-TEST_F(ExpressionConvertTest, ConvertDoubleToInt) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document simpleInput{{"path1", Value(1.0)}};
-
- // Conversions to int should always truncate the fraction (i.e., round towards 0).
- Document nonIntegerInput1{{"path1", Value(2.1)}};
- Document nonIntegerInput2{{"path1", Value(2.9)}};
- Document nonIntegerInput3{{"path1", Value(-2.1)}};
- Document nonIntegerInput4{{"path1", Value(-2.9)}};
-
- int maxInt = std::numeric_limits<int>::max();
- int minInt = std::numeric_limits<int>::lowest();
- Document maxInput{{"path1", Value(static_cast<double>(maxInt))}};
- Document minInput{{"path1", Value(static_cast<double>(minInt))}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1, BSONType::NumberInt);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput1), 2, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput2), 2, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput3), -2, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput4), -2, BSONType::NumberInt);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(maxInput), maxInt, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(minInput), minInt, BSONType::NumberInt);
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDoubleToInt) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- int maxInt = std::numeric_limits<int>::max();
- int minInt = std::numeric_limits<int>::lowest();
- double overflowInt =
- std::nextafter(static_cast<double>(maxInt), std::numeric_limits<double>::max());
- double negativeOverflowInt =
- std::nextafter(static_cast<double>(minInt), std::numeric_limits<double>::lowest());
- Document overflowInput{{"path1", Value(overflowInt)}};
- Document negativeOverflowInput{{"path1", Value(negativeOverflowInt)}};
-
- Document nanInput{{"path1", Value(std::numeric_limits<double>::quiet_NaN())}};
- Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
- Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(nanInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Attempt to cast NaN value to integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->evaluate(doubleInfinity),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(), "Attempt to cast infinity value to integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->evaluate(doubleNegativeInfinity),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(), "Attempt to cast infinity value to integer");
- });
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDoubleToIntWithOnError) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"
- << "onError"
- << ":)"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- int maxInt = std::numeric_limits<int>::max();
- int minInt = std::numeric_limits<int>::lowest();
- double overflowInt =
- std::nextafter(static_cast<double>(maxInt), std::numeric_limits<double>::max());
- double negativeOverflowInt =
- std::nextafter(static_cast<double>(minInt), std::numeric_limits<double>::lowest());
- Document overflowInput{{"path1", Value(overflowInt)}};
- Document negativeOverflowInput{{"path1", Value(negativeOverflowInt)}};
-
- Document nanInput{{"path1", Value(std::numeric_limits<double>::quiet_NaN())}};
- Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
- Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
-
- std::string contents = ":)";
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(negativeOverflowInput), contents, BSONType::String);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nanInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(doubleInfinity), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(doubleNegativeInfinity), contents, BSONType::String);
-}
-
-TEST_F(ExpressionConvertTest, ConvertDoubleToLong) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "long"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document simpleInput{{"path1", Value(1.0)}};
-
- // Conversions to int should always truncate the fraction (i.e., round towards 0).
- Document nonIntegerInput1{{"path1", Value(2.1)}};
- Document nonIntegerInput2{{"path1", Value(2.9)}};
- Document nonIntegerInput3{{"path1", Value(-2.1)}};
- Document nonIntegerInput4{{"path1", Value(-2.9)}};
-
- // maxVal is the highest double value that will not overflow long long, and minVal is the lowest
- // double value that will not overflow long long.
- double maxVal = std::nextafter(ExpressionConvert::kLongLongMaxPlusOneAsDouble, 0.0);
- double minVal = static_cast<double>(std::numeric_limits<long long>::lowest());
- Document maxInput{{"path1", Value(maxVal)}};
- Document minInput{{"path1", Value(minVal)}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1, BSONType::NumberLong);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput1), 2, BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput2), 2, BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(nonIntegerInput3), -2, BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(nonIntegerInput4), -2, BSONType::NumberLong);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(maxInput), static_cast<long long>(maxVal), BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(minInput), static_cast<long long>(minVal), BSONType::NumberLong);
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDoubleToLong) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "long"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- double minLong = static_cast<double>(std::numeric_limits<long long>::lowest());
- double overflowLong = ExpressionConvert::kLongLongMaxPlusOneAsDouble;
- double negativeOverflowLong =
- std::nextafter(static_cast<double>(minLong), std::numeric_limits<double>::lowest());
- Document overflowInput{{"path1", Value(overflowLong)}};
- Document negativeOverflowInput{{"path1", Value(negativeOverflowLong)}};
-
- Document nanInput{{"path1", Value(std::numeric_limits<double>::quiet_NaN())}};
- Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
- Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(nanInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Attempt to cast NaN value to integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->evaluate(doubleInfinity),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(), "Attempt to cast infinity value to integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->evaluate(doubleNegativeInfinity),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(), "Attempt to cast infinity value to integer");
- });
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDoubleToLongWithOnError) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "long"
- << "onError"
- << ":D"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- double minLong = static_cast<double>(std::numeric_limits<long long>::lowest());
- double overflowLong = ExpressionConvert::kLongLongMaxPlusOneAsDouble;
- double negativeOverflowLong =
- std::nextafter(static_cast<double>(minLong), std::numeric_limits<double>::lowest());
- Document overflowInput{{"path1", Value(overflowLong)}};
- Document negativeOverflowInput{{"path1", Value(negativeOverflowLong)}};
-
- Document nanInput{{"path1", Value(std::numeric_limits<double>::quiet_NaN())}};
- Document doubleInfinity{{"path1", std::numeric_limits<double>::infinity()}};
- Document doubleNegativeInfinity{{"path1", -std::numeric_limits<double>::infinity()}};
-
- std::string contents = ":D";
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(negativeOverflowInput), contents, BSONType::String);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nanInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(doubleInfinity), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(doubleNegativeInfinity), contents, BSONType::String);
-}
-
-TEST_F(ExpressionConvertTest, ConvertDecimalToInt) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document simpleInput{{"path1", Value(Decimal128("1.0"))}};
-
- // Conversions to int should always truncate the fraction (i.e., round towards 0).
- Document nonIntegerInput1{{"path1", Value(Decimal128("2.1"))}};
- Document nonIntegerInput2{{"path1", Value(Decimal128("2.9"))}};
- Document nonIntegerInput3{{"path1", Value(Decimal128("-2.1"))}};
- Document nonIntegerInput4{{"path1", Value(Decimal128("-2.9"))}};
-
- int maxInt = std::numeric_limits<int>::max();
- int minInt = std::numeric_limits<int>::min();
- Document maxInput{{"path1", Value(Decimal128(maxInt))}};
- Document minInput{{"path1", Value(Decimal128(minInt))}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1, BSONType::NumberInt);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput1), 2, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput2), 2, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput3), -2, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput4), -2, BSONType::NumberInt);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(maxInput), maxInt, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(minInput), minInt, BSONType::NumberInt);
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToInt) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- int maxInt = std::numeric_limits<int>::max();
- int minInt = std::numeric_limits<int>::lowest();
- Document overflowInput{{"path1", Decimal128(maxInt).add(Decimal128(1))}};
- Document negativeOverflowInput{{"path1", Decimal128(minInt).subtract(Decimal128(1))}};
-
- Document nanInput{{"path1", Decimal128::kPositiveNaN}};
- Document negativeNaNInput{{"path1", Decimal128::kNegativeNaN}};
- Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
- Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(nanInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Attempt to cast NaN value to integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeNaNInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Attempt to cast NaN value to integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->evaluate(decimalInfinity),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(), "Attempt to cast infinity value to integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->evaluate(decimalNegativeInfinity),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(), "Attempt to cast infinity value to integer");
- });
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToIntWithOnError) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"
- << "onError"
- << ":o"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- int maxInt = std::numeric_limits<int>::max();
- int minInt = std::numeric_limits<int>::lowest();
- Document overflowInput{{"path1", Decimal128(maxInt).add(Decimal128(1))}};
- Document negativeOverflowInput{{"path1", Decimal128(minInt).subtract(Decimal128(1))}};
-
- Document nanInput{{"path1", Decimal128::kPositiveNaN}};
- Document negativeNaNInput{{"path1", Decimal128::kNegativeNaN}};
- Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
- Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
-
- std::string contents = ":o";
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(negativeOverflowInput), contents, BSONType::String);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nanInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(negativeNaNInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(decimalInfinity), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(decimalNegativeInfinity), contents, BSONType::String);
-}
-
-TEST_F(ExpressionConvertTest, ConvertDecimalToLong) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "long"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document simpleInput{{"path1", Value(Decimal128("1.0"))}};
-
- // Conversions to long should always truncate the fraction (i.e., round towards 0).
- Document nonIntegerInput1{{"path1", Value(Decimal128("2.1"))}};
- Document nonIntegerInput2{{"path1", Value(Decimal128("2.9"))}};
- Document nonIntegerInput3{{"path1", Value(Decimal128("-2.1"))}};
- Document nonIntegerInput4{{"path1", Value(Decimal128("-2.9"))}};
-
- long long maxVal = std::numeric_limits<long long>::max();
- long long minVal = std::numeric_limits<long long>::min();
- Document maxInput{{"path1", Value(Decimal128(std::int64_t{maxVal}))}};
- Document minInput{{"path1", Value(Decimal128(std::int64_t{minVal}))}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1, BSONType::NumberLong);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput1), 2, BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nonIntegerInput2), 2, BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(nonIntegerInput3), -2, BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(nonIntegerInput4), -2, BSONType::NumberLong);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(maxInput), maxVal, BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(minInput), minVal, BSONType::NumberLong);
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToLong) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "long"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- long long maxVal = std::numeric_limits<long long>::max();
- long long minVal = std::numeric_limits<long long>::lowest();
- Document overflowInput{{"path1", Decimal128(std::int64_t{maxVal}).add(Decimal128(1))}};
- Document negativeOverflowInput{
- {"path1", Decimal128(std::int64_t{minVal}).subtract(Decimal128(1))}};
-
- Document nanInput{{"path1", Decimal128::kPositiveNaN}};
- Document negativeNaNInput{{"path1", Decimal128::kNegativeNaN}};
- Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
- Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(nanInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Attempt to cast NaN value to integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeNaNInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Attempt to cast NaN value to integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->evaluate(decimalInfinity),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(), "Attempt to cast infinity value to integer");
- });
-
- ASSERT_THROWS_WITH_CHECK(
- convertExp->evaluate(decimalNegativeInfinity),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(), "Attempt to cast infinity value to integer");
- });
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsDecimalToLongWithOnError) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "long"
- << "onError"
- << ":p"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- long long maxVal = std::numeric_limits<long long>::max();
- long long minVal = std::numeric_limits<long long>::lowest();
- Document overflowInput{{"path1", Decimal128(std::int64_t{maxVal}).add(Decimal128(1))}};
- Document negativeOverflowInput{
- {"path1", Decimal128(std::int64_t{minVal}).subtract(Decimal128(1))}};
-
- Document nanInput{{"path1", Decimal128::kPositiveNaN}};
- Document negativeNaNInput{{"path1", Decimal128::kNegativeNaN}};
- Document decimalInfinity{{"path1", Decimal128::kPositiveInfinity}};
- Document decimalNegativeInfinity{{"path1", Decimal128::kNegativeInfinity}};
-
- std::string contents = ":p";
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(negativeOverflowInput), contents, BSONType::String);
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(nanInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(negativeNaNInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(decimalInfinity), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(decimalNegativeInfinity), contents, BSONType::String);
-}
-
-TEST_F(ExpressionConvertTest, ConvertIntToLong) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "long"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document simpleInput{{"path1", Value(1)}};
-
- int maxInt = std::numeric_limits<int>::max();
- int minInt = std::numeric_limits<int>::min();
- Document maxInput{{"path1", Value(maxInt)}};
- Document minInput{{"path1", Value(minInt)}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1ll, BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(maxInput), maxInt, BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(minInput), minInt, BSONType::NumberLong);
-}
-
-TEST_F(ExpressionConvertTest, ConvertLongToInt) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document simpleInput{{"path1", Value(1)}};
-
- long long maxInt = std::numeric_limits<int>::max();
- long long minInt = std::numeric_limits<int>::min();
- Document maxInput{{"path1", Value(maxInt)}};
- Document minInput{{"path1", Value(minInt)}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(simpleInput), 1, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(maxInput), maxInt, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(minInput), minInt, BSONType::NumberInt);
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsLongToInt) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- long long maxInt = std::numeric_limits<int>::max();
- long long minInt = std::numeric_limits<int>::min();
- Document overflowInput{{"path1", Value(maxInt + 1)}};
- Document negativeOverflowInput{{"path1", Value(minInt - 1)}};
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(overflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-
- ASSERT_THROWS_WITH_CHECK(convertExp->evaluate(negativeOverflowInput),
- AssertionException,
- [](const AssertionException& exception) {
- ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
- ASSERT_STRING_CONTAINS(exception.reason(),
- "Conversion would overflow target type");
- });
-}
-
-TEST_F(ExpressionConvertTest, ConvertOutOfBoundsLongToIntWithOnError) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"
- << "onError"
- << ":|"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- long long maxInt = std::numeric_limits<int>::max();
- long long minInt = std::numeric_limits<int>::min();
- Document overflowInput{{"path1", Value(maxInt + 1)}};
- Document negativeOverflowInput{{"path1", Value(minInt - 1)}};
-
- std::string contents = ":|";
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(overflowInput), contents, BSONType::String);
- ASSERT_VALUE_CONTENTS_AND_TYPE(
- convertExp->evaluate(negativeOverflowInput), contents, BSONType::String);
-}
-
-TEST_F(ExpressionConvertTest, ConvertBoolToInt) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "int"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document boolFalse{{"path1", Value(false)}};
- Document boolTrue{{"path1", Value(true)}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolFalse), 0, BSONType::NumberInt);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolTrue), 1, BSONType::NumberInt);
-}
-
-TEST_F(ExpressionConvertTest, ConvertBoolToLong) {
- auto expCtx = getExpCtx();
-
- auto spec = BSON("$convert" << BSON("input"
- << "$path1"
- << "to"
- << "long"));
- auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- convertExp = convertExp->optimize();
-
- Document boolFalse{{"path1", Value(false)}};
- Document boolTrue{{"path1", Value(true)}};
-
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolFalse), 0ll, BSONType::NumberLong);
- ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate(boolTrue), 1ll, BSONType::NumberLong);
-}
-
-} // namespace ExpressionConvertTest
-
class All : public Suite {
public:
All() : Suite("expression") {}
diff --git a/src/mongo/db/pipeline/value.h b/src/mongo/db/pipeline/value.h
index b3f54f4191f..90ecd5c3ffa 100644
--- a/src/mongo/db/pipeline/value.h
+++ b/src/mongo/db/pipeline/value.h
@@ -354,7 +354,7 @@ public:
* It is not safe to obtain this value by casting std::numeric_limits<long long>::max() to
* double, because the conversion loses precision, and the C++ standard leaves it up to the
* implentation to decide whether to round up to 2^63 or round down to the next representable
- * value (2^63 - 2^11).
+ * value (2^63 - 2^10).
*/
static const double kLongLongMaxPlusOneAsDouble;