summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Zolnierz <nicholas.zolnierz@mongodb.com>2018-02-16 11:21:58 -0500
committerNick Zolnierz <nicholas.zolnierz@mongodb.com>2018-02-26 10:18:07 -0500
commit35212ca1250977a64f190a8de3572a35a88343c6 (patch)
tree0a4fcd84069bd288b6e006a6b9c1a2096b08c013
parentbebdcf721eaa24fa589977bf48459a8cdd0fb2a2 (diff)
downloadmongo-35212ca1250977a64f190a8de3572a35a88343c6.tar.gz
SERVER-33171: Add number and objectID parsing conversions to $convert
-rw-r--r--src/mongo/base/parse_number.cpp32
-rw-r--r--src/mongo/bson/oid.h21
-rw-r--r--src/mongo/db/pipeline/expression.cpp47
-rw-r--r--src/mongo/db/pipeline/expression_convert_test.cpp694
4 files changed, 794 insertions, 0 deletions
diff --git a/src/mongo/base/parse_number.cpp b/src/mongo/base/parse_number.cpp
index c7f7258ac5c..372ebb4aaf9 100644
--- a/src/mongo/base/parse_number.cpp
+++ b/src/mongo/base/parse_number.cpp
@@ -37,6 +37,7 @@
#include <string>
#include "mongo/base/status_with.h"
+#include "mongo/platform/decimal128.h"
#include "mongo/platform/overflow_arithmetic.h"
namespace mongo {
@@ -260,4 +261,35 @@ Status parseNumberFromStringWithBase<double>(StringData stringValue, int base, d
return Status::OK();
}
+template <>
+Status parseNumberFromStringWithBase<Decimal128>(StringData stringValue,
+ int base,
+ Decimal128* result) {
+ if (base != 0) {
+ return Status(ErrorCodes::BadValue,
+ "Must pass 0 as base to parseNumberFromStringWithBase<Decimal128>.");
+ }
+
+ if (stringValue.empty()) {
+ return Status(ErrorCodes::FailedToParse, "Empty string");
+ }
+
+ std::uint32_t signalingFlags = 0;
+ auto parsedDecimal = Decimal128(
+ stringValue.toString(), &signalingFlags, Decimal128::RoundingMode::kRoundTowardZero);
+
+ if (Decimal128::hasFlag(signalingFlags, Decimal128::SignalingFlag::kOverflow)) {
+ return Status(ErrorCodes::FailedToParse,
+ "Conversion from string to decimal would overflow");
+ } else if (Decimal128::hasFlag(signalingFlags, Decimal128::SignalingFlag::kUnderflow)) {
+ return Status(ErrorCodes::FailedToParse,
+ "Conversion from string to decimal would underflow");
+ } else if (signalingFlags != Decimal128::SignalingFlag::kNoFlag &&
+ signalingFlags != Decimal128::SignalingFlag::kInexact) { // Ignore precision loss.
+ return Status(ErrorCodes::FailedToParse, "Failed to parse string to decimal");
+ }
+
+ *result = parsedDecimal;
+ return Status::OK();
+}
} // namespace mongo
diff --git a/src/mongo/bson/oid.h b/src/mongo/bson/oid.h
index 4b070e50589..4474a12dbe2 100644
--- a/src/mongo/bson/oid.h
+++ b/src/mongo/bson/oid.h
@@ -140,6 +140,27 @@ public:
return o;
}
+ /**
+ * This method creates and initializes an OID from a string, throwing a BadValue exception if
+ * the string is not a valid OID.
+ */
+ static OID createFromString(StringData input) {
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "Invalid string length for parsing to OID, expected 24 but found "
+ << input.size(),
+ input.size() == 24);
+ for (auto digit : input) {
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "Invalid character found in hex string: " << digit,
+ ('0' <= digit && digit <= '9') || ('a' <= digit && digit <= 'f') ||
+ ('A' <= digit && digit <= 'F'));
+ }
+
+ OID result;
+ result.init(input.toString());
+ return result;
+ }
+
/** sets the contents to a new oid / randomized value */
void init();
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index 33d3c03c94e..016749ca29b 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -4781,6 +4781,17 @@ public:
};
//
+ // Conversions from String
+ //
+ table[BSONType::String][BSONType::NumberDouble] = &parseStringToNumber<double, 0>;
+ table[BSONType::String][BSONType::String] = &performIdentityConversion;
+ table[BSONType::String][BSONType::jstOID] = &parseStringToOID;
+ table[BSONType::String][BSONType::Bool] = [](Value inputValue) { return Value(true); };
+ table[BSONType::String][BSONType::NumberInt] = &parseStringToNumber<int, 10>;
+ table[BSONType::String][BSONType::NumberLong] = &parseStringToNumber<long long, 10>;
+ table[BSONType::String][BSONType::NumberDecimal] = &parseStringToNumber<Decimal128, 0>;
+
+ //
// Conversions from jstOID
//
table[BSONType::jstOID][BSONType::Date] = [](Value inputValue) {
@@ -5017,6 +5028,42 @@ private:
return Value(Date_t::fromMillisSinceEpoch(millisSinceEpoch));
}
+ template <class targetType, int base>
+ static Value parseStringToNumber(Value inputValue) {
+ auto stringValue = inputValue.getStringData();
+ targetType result;
+
+ // Reject any strings in hex format. This check is needed because the
+ // parseNumberFromStringWithBase call below allows an input hex string prefixed by '0x' when
+ // parsing to a double.
+ uassert(ErrorCodes::ConversionFailure,
+ str::stream() << "Illegal hexadecimal input in $convert with no onError value: "
+ << stringValue,
+ !stringValue.startsWith("0x"));
+
+ Status parseStatus = parseNumberFromStringWithBase(stringValue, base, &result);
+ uassert(ErrorCodes::ConversionFailure,
+ str::stream() << "Failed to parse number '" << stringValue
+ << "' in $convert with no onError value: "
+ << parseStatus.reason(),
+ parseStatus.isOK());
+
+ return Value(result);
+ }
+
+ static Value parseStringToOID(Value inputValue) {
+ try {
+ return Value(OID::createFromString(inputValue.getStringData()));
+ } catch (const DBException& ex) {
+ // Rethrow any caught exception as a conversion failure such that 'onError' is evaluated
+ // and returned.
+ uasserted(ErrorCodes::ConversionFailure,
+ str::stream() << "Failed to parse objectId '" << inputValue.getString()
+ << "' in $convert with no onError value: "
+ << ex.reason());
+ }
+ }
+
static Value performIdentityConversion(Value inputValue) {
return inputValue;
}
diff --git a/src/mongo/db/pipeline/expression_convert_test.cpp b/src/mongo/db/pipeline/expression_convert_test.cpp
index 23aa30319de..51f579ceede 100644
--- a/src/mongo/db/pipeline/expression_convert_test.cpp
+++ b/src/mongo/db/pipeline/expression_convert_test.cpp
@@ -28,6 +28,7 @@
#include "mongo/platform/basic.h"
+#include "mongo/bson/oid.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"
@@ -37,6 +38,15 @@ namespace mongo {
namespace ExpressionConvertTest {
+static const long long kIntMax = std::numeric_limits<int>::max();
+static const long long kIntMin = std::numeric_limits<int>::lowest();
+static const long long kLongMax = std::numeric_limits<long long>::max();
+static const double kLongMin = static_cast<double>(std::numeric_limits<long long>::lowest());
+static const double kLongNegativeOverflow =
+ std::nextafter(static_cast<double>(kLongMin), std::numeric_limits<double>::lowest());
+static const Decimal128 kDoubleOverflow = Decimal128("1e309");
+static const Decimal128 kDoubleNegativeOverflow = Decimal128("-1e309");
+
using ExpressionConvertTest = AggregationContextFixture;
TEST_F(ExpressionConvertTest, ParseAndSerializeWithoutOptionalArguments) {
@@ -1735,6 +1745,690 @@ TEST_F(ExpressionConvertTest, ConvertObjectIdToDate) {
"2017-10-19T13:30:00.000Z");
}
+TEST_F(ExpressionConvertTest, ConvertStringToInt) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '5', to: 'int'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), 5, BSONType::NumberInt);
+
+ spec = fromjson("{$convert: {input: '" + std::to_string(kIntMax) + "', to: 'int'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), kIntMax, BSONType::NumberInt);
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToIntOverflow) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '" + std::to_string(kIntMax + 1) + "', to: 'int'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Overflow");
+ });
+
+ spec = fromjson("{$convert: {input: '" + std::to_string(kIntMin - 1) + "', to: 'int'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Overflow");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToIntOverflowWithOnError) {
+ auto expCtx = getExpCtx();
+ const auto onErrorValue = "><(((((>"_sd;
+
+ auto spec = fromjson("{$convert: {input: '" + std::to_string(kIntMax + 1) +
+ "', to: 'int', onError: '" + onErrorValue + "'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+
+ spec = fromjson("{$convert: {input: '" + std::to_string(kIntMin - 1) +
+ "', to: 'int', onError: '" + onErrorValue + "'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToLong) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '5', to: 'long'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), 5LL, BSONType::NumberLong);
+
+ spec = fromjson("{$convert: {input: '" + std::to_string(kLongMax) + "', to: 'long'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), kLongMax, BSONType::NumberLong);
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToLongOverflow) {
+ auto expCtx = getExpCtx();
+ auto longMaxPlusOneAsString = std::to_string(ExpressionConvert::kLongLongMaxPlusOneAsDouble);
+ // Remove digits after the decimal to avoid parse failure.
+ longMaxPlusOneAsString = longMaxPlusOneAsString.substr(0, longMaxPlusOneAsString.find('.'));
+
+ auto spec = fromjson("{$convert: {input: '" + longMaxPlusOneAsString + "', to: 'long'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Overflow");
+ });
+
+ auto longMinMinusOneAsString = std::to_string(kLongNegativeOverflow);
+ longMinMinusOneAsString = longMinMinusOneAsString.substr(0, longMinMinusOneAsString.find('.'));
+
+ spec = fromjson("{$convert: {input: '" + longMinMinusOneAsString + "', to: 'long'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Overflow");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToLongFailsForFloats) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '5.5', to: 'long'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Bad digit \".\"");
+ });
+
+ spec = fromjson("{$convert: {input: '5.0', to: 'long'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Bad digit \".\"");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToLongWithOnError) {
+ auto expCtx = getExpCtx();
+ const auto onErrorValue = "><(((((>"_sd;
+ auto longMaxPlusOneAsString = std::to_string(ExpressionConvert::kLongLongMaxPlusOneAsDouble);
+ // Remove digits after the decimal to avoid parse failure.
+ longMaxPlusOneAsString = longMaxPlusOneAsString.substr(0, longMaxPlusOneAsString.find('.'));
+
+ auto spec = fromjson("{$convert: {input: '" + longMaxPlusOneAsString +
+ "', to: 'long', onError: '" + onErrorValue + "'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+
+ auto longMinMinusOneAsString = std::to_string(kLongNegativeOverflow);
+ longMinMinusOneAsString = longMinMinusOneAsString.substr(0, longMinMinusOneAsString.find('.'));
+
+ spec = fromjson("{$convert: {input: '" + longMinMinusOneAsString + "', to: 'long', onError: '" +
+ onErrorValue + "'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+
+ spec = fromjson("{$convert: {input: '5.5', to: 'long', onError: '" + onErrorValue + "'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+
+ spec = fromjson("{$convert: {input: '5.0', to: 'long', onError: '" + onErrorValue + "'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDouble) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '5', to: 'double'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), 5.0, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: '5.5', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), 5.5, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: '.5', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), 0.5, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: '+5', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), 5.0, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: '+5.0e42', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), 5.0e42, BSONType::NumberDouble);
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDoubleWithPrecisionLoss) {
+ auto expCtx = getExpCtx();
+
+ // Note that the least significant bits get lost, because the significand of a double is not
+ // wide enough for the given input string in its entirety.
+ auto spec = fromjson("{$convert: {input: '10000000000000000001', to: 'double'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), 1e19, BSONType::NumberDouble);
+
+ // Again, some precision is lost in the conversion to double.
+ spec = fromjson("{$convert: {input: '1.125000000000000000005', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), 1.125, BSONType::NumberDouble);
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDoubleFailsForInvalidFloats) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '.5.', to: 'double'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Did not consume whole number");
+ });
+
+ spec = fromjson("{$convert: {input: '5.5f', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Did not consume whole number");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertInfinityStringsToDouble) {
+ auto expCtx = getExpCtx();
+ auto infValue = std::numeric_limits<double>::infinity();
+
+ auto spec = fromjson("{$convert: {input: 'Infinity', to: 'double'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), infValue, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: 'INF', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), infValue, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: 'infinity', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), infValue, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: '+InFiNiTy', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), infValue, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: '-Infinity', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), -infValue, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: '-INF', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), -infValue, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: '-InFiNiTy', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), -infValue, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: '-inf', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), -infValue, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: '-infinity', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), -infValue, BSONType::NumberDouble);
+}
+
+TEST_F(ExpressionConvertTest, ConvertZeroStringsToDouble) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '-0', to: 'double'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto result = convertExp->evaluate({});
+ ASSERT_VALUE_CONTENTS_AND_TYPE(result, 0, BSONType::NumberDouble);
+ ASSERT_TRUE(std::signbit(result.getDouble()));
+
+ spec = fromjson("{$convert: {input: '-0.0', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ result = convertExp->evaluate({});
+ ASSERT_VALUE_CONTENTS_AND_TYPE(result, 0, BSONType::NumberDouble);
+ ASSERT_TRUE(std::signbit(result.getDouble()));
+
+ spec = fromjson("{$convert: {input: '+0', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ result = convertExp->evaluate({});
+ ASSERT_VALUE_CONTENTS_AND_TYPE(result, 0, BSONType::NumberDouble);
+ ASSERT_FALSE(std::signbit(result.getDouble()));
+
+ spec = fromjson("{$convert: {input: '+0.0', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ result = convertExp->evaluate({});
+ ASSERT_VALUE_CONTENTS_AND_TYPE(result, 0, BSONType::NumberDouble);
+ ASSERT_FALSE(std::signbit(result.getDouble()));
+}
+
+TEST_F(ExpressionConvertTest, ConvertNanStringsToDouble) {
+ auto expCtx = getExpCtx();
+ auto nanValue = std::numeric_limits<double>::quiet_NaN();
+
+ auto spec = fromjson("{$convert: {input: 'nan', to: 'double'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto result = convertExp->evaluate({});
+ ASSERT_TRUE(std::isnan(result.getDouble()));
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), nanValue, BSONType::NumberDouble);
+
+ spec = fromjson("{$convert: {input: 'Nan', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ result = convertExp->evaluate({});
+ ASSERT_TRUE(std::isnan(result.getDouble()));
+
+ spec = fromjson("{$convert: {input: 'NaN', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ result = convertExp->evaluate({});
+ ASSERT_TRUE(std::isnan(result.getDouble()));
+
+ spec = fromjson("{$convert: {input: '-NAN', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ result = convertExp->evaluate({});
+ ASSERT_TRUE(std::isnan(result.getDouble()));
+
+ spec = fromjson("{$convert: {input: '+NaN', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ result = convertExp->evaluate({});
+ ASSERT_TRUE(std::isnan(result.getDouble()));
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDoubleOverflow) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '" + kDoubleOverflow.toString() + "', to: 'double'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Out of range");
+ });
+
+ spec =
+ fromjson("{$convert: {input: '" + kDoubleNegativeOverflow.toString() + "', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Out of range");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDoubleUnderflow) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '1E-1000', to: 'double'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(
+ exception.reason(),
+ "Failed to parse number '1E-1000' in $convert with no onError value: Out of range");
+ });
+
+ spec = fromjson("{$convert: {input: '-1E-1000', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Failed to parse number '-1E-1000' in $convert with no onError "
+ "value: Out of range");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDoubleWithOnError) {
+ auto expCtx = getExpCtx();
+ const auto onErrorValue = "><(((((>"_sd;
+
+ auto spec = fromjson("{$convert: {input: '" + kDoubleOverflow.toString() +
+ "', to: 'double', onError: '" + onErrorValue + "'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+
+ spec = fromjson("{$convert: {input: '" + kDoubleNegativeOverflow.toString() +
+ "', to: 'double', onError: '" + onErrorValue + "'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+
+ spec = fromjson("{$convert: {input: '.5.', to: 'double', onError: '" + onErrorValue + "'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+
+ spec = fromjson("{$convert: {input: '5.5f', to: 'double', onError: '" + onErrorValue + "'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDecimal) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '5', to: 'decimal'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), 5, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '2.02', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate({}), Decimal128("2.02"), BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '2.02E200', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate({}), Decimal128("2.02E200"), BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '" + Decimal128::kLargestPositive.toString() +
+ "', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate({}), Decimal128::kLargestPositive, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '" + Decimal128::kLargestNegative.toString() +
+ "', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate({}), Decimal128::kLargestNegative, BSONType::NumberDecimal);
+}
+
+TEST_F(ExpressionConvertTest, ConvertInfinityStringsToDecimal) {
+ auto expCtx = getExpCtx();
+ auto infValue = Decimal128::kPositiveInfinity;
+ auto negInfValue = Decimal128::kNegativeInfinity;
+
+ auto spec = fromjson("{$convert: {input: 'Infinity', to: 'decimal'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), infValue, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: 'INF', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), infValue, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: 'infinity', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), infValue, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '+InFiNiTy', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), infValue, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '-Infinity', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), negInfValue, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '-INF', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), negInfValue, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '-InFiNiTy', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), negInfValue, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '-inf', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), negInfValue, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '-infinity', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), negInfValue, BSONType::NumberDecimal);
+}
+
+TEST_F(ExpressionConvertTest, ConvertNanStringsToDecimal) {
+ auto expCtx = getExpCtx();
+ auto positiveNan = Decimal128::kPositiveNaN;
+ auto negativeNan = Decimal128::kNegativeNaN;
+
+ auto spec = fromjson("{$convert: {input: 'nan', to: 'decimal'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), positiveNan, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: 'Nan', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), positiveNan, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: 'NaN', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), positiveNan, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '+NaN', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), positiveNan, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '-NAN', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), negativeNan, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '-nan', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), negativeNan, BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '-NaN', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), negativeNan, BSONType::NumberDecimal);
+}
+
+TEST_F(ExpressionConvertTest, ConvertZeroStringsToDecimal) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '-0', to: 'decimal'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ auto result = convertExp->evaluate({});
+ ASSERT_VALUE_CONTENTS_AND_TYPE(result, 0, BSONType::NumberDecimal);
+ ASSERT_TRUE(result.getDecimal().isZero());
+ ASSERT_TRUE(result.getDecimal().isNegative());
+
+ spec = fromjson("{$convert: {input: '-0.0', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ result = convertExp->evaluate({});
+ ASSERT_VALUE_CONTENTS_AND_TYPE(result, 0, BSONType::NumberDecimal);
+ ASSERT_TRUE(result.getDecimal().isZero());
+ ASSERT_TRUE(result.getDecimal().isNegative());
+
+ spec = fromjson("{$convert: {input: '+0', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ result = convertExp->evaluate({});
+ ASSERT_VALUE_CONTENTS_AND_TYPE(result, 0, BSONType::NumberDecimal);
+ ASSERT_TRUE(result.getDecimal().isZero());
+ ASSERT_FALSE(result.getDecimal().isNegative());
+
+ spec = fromjson("{$convert: {input: '+0.0', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ result = convertExp->evaluate({});
+ ASSERT_VALUE_CONTENTS_AND_TYPE(result, 0, BSONType::NumberDecimal);
+ ASSERT_TRUE(result.getDecimal().isZero());
+ ASSERT_FALSE(result.getDecimal().isNegative());
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDecimalOverflow) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '1E6145', to: 'decimal'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion from string to decimal would overflow");
+ });
+
+ spec = fromjson("{$convert: {input: '-1E6145', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion from string to decimal would overflow");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDecimalUnderflow) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: '1E-6178', to: 'decimal'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion from string to decimal would underflow");
+ });
+
+ spec = fromjson("{$convert: {input: '-1E-6177', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(),
+ "Conversion from string to decimal would underflow");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDecimalWithPrecisionLoss) {
+ auto expCtx = getExpCtx();
+
+ auto spec =
+ fromjson("{$convert: {input: '10000000000000000000000000000000001', to: 'decimal'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate({}), Decimal128("1e34"), BSONType::NumberDecimal);
+
+ spec = fromjson("{$convert: {input: '1.1250000000000000000000000000000001', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate({}), Decimal128("1.125"), BSONType::NumberDecimal);
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToDecimalWithOnError) {
+ auto expCtx = getExpCtx();
+ const auto onErrorValue = "><(((((>"_sd;
+
+ auto spec =
+ fromjson("{$convert: {input: '1E6145', to: 'decimal', onError: '" + onErrorValue + "'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+
+ spec =
+ fromjson("{$convert: {input: '-1E-6177', to: 'decimal', onError: '" + onErrorValue + "'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToNumberFailsForHexStrings) {
+ auto expCtx = getExpCtx();
+ auto invalidHexFailure = [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Illegal hexadecimal input in $convert");
+ };
+
+ auto spec = fromjson("{$convert: {input: '0xFF', to: 'int'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate({}), AssertionException, invalidHexFailure);
+
+ spec = fromjson("{$convert: {input: '0xFF', to: 'long'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate({}), AssertionException, invalidHexFailure);
+
+ spec = fromjson("{$convert: {input: '0xFF', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate({}), AssertionException, invalidHexFailure);
+
+ spec = fromjson("{$convert: {input: '0xFF', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate({}), AssertionException, invalidHexFailure);
+
+ spec = fromjson("{$convert: {input: '0x00', to: 'int'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate({}), AssertionException, invalidHexFailure);
+
+ spec = fromjson("{$convert: {input: '0x00', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(convertExp->evaluate({}), AssertionException, invalidHexFailure);
+
+ spec = fromjson("{$convert: {input: 'FF', to: 'double'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Did not consume whole number");
+ });
+
+ spec = fromjson("{$convert: {input: 'FF', to: 'decimal'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Failed to parse string to decimal");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertStringToOID) {
+ auto expCtx = getExpCtx();
+ auto oid = OID::gen();
+
+ auto spec = fromjson("{$convert: {input: '" + oid.toString() + "', to: 'objectId'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), oid, BSONType::jstOID);
+
+ spec = fromjson("{$convert: {input: '123456789abcdef123456789', to: 'objectId'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(
+ convertExp->evaluate({}), OID("123456789abcdef123456789"), BSONType::jstOID);
+}
+
+TEST_F(ExpressionConvertTest, ConvertToOIDFailsForInvalidHexStrings) {
+ auto expCtx = getExpCtx();
+
+ auto spec = fromjson("{$convert: {input: 'InvalidHexButSizeCorrect', to: 'objectId'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Invalid character found in hex string");
+ });
+
+ spec = fromjson("{$convert: {input: 'InvalidSize', to: 'objectId'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Invalid string length for parsing to OID");
+ });
+
+ spec = fromjson("{$convert: {input: '0x123456789abcdef123456789', to: 'objectId'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_WITH_CHECK(
+ convertExp->evaluate({}), AssertionException, [](const AssertionException& exception) {
+ ASSERT_EQ(exception.code(), ErrorCodes::ConversionFailure);
+ ASSERT_STRING_CONTAINS(exception.reason(), "Invalid string length for parsing to OID");
+ });
+}
+
+TEST_F(ExpressionConvertTest, ConvertToOIDWithOnError) {
+ auto expCtx = getExpCtx();
+ const auto onErrorValue = "><(((((>"_sd;
+
+ auto spec =
+ fromjson("{$convert: {input: 'InvalidHexButSizeCorrect', to: 'objectId', onError: '" +
+ onErrorValue + "'}}");
+ auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+
+ spec = fromjson("{$convert: {input: 'InvalidSize', to: 'objectId', onError: '" + onErrorValue +
+ "'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+
+ spec = fromjson("{$convert: {input: '0x123456789abcdef123456789', to: 'objectId', onError: '" +
+ onErrorValue + "'}}");
+ convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String);
+}
+
} // namespace ExpressionConvertTest
} // namespace mongo