diff options
Diffstat (limited to 'src/mongo/db/pipeline')
-rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 186 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_convert_test.cpp | 114 |
2 files changed, 221 insertions, 79 deletions
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 318933b3c20..a8d002e0c35 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -4849,22 +4849,24 @@ namespace { */ class ConversionTable { public: - using ConversionFunc = std::function<Value(Value)>; + using ConversionFunc = + std::function<Value(const boost::intrusive_ptr<ExpressionContext>&, Value)>; ConversionTable() { // // Conversions from NumberDouble // table[BSONType::NumberDouble][BSONType::NumberDouble] = &performIdentityConversion; - table[BSONType::NumberDouble][BSONType::Bool] = [](Value inputValue) { - return Value(inputValue.coerceToBool()); - }; + table[BSONType::NumberDouble] + [BSONType::Bool] = [](const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { return Value(inputValue.coerceToBool()); }; table[BSONType::NumberDouble][BSONType::Date] = &performCastNumberToDate; table[BSONType::NumberDouble][BSONType::NumberInt] = &performCastDoubleToInt; table[BSONType::NumberDouble][BSONType::NumberLong] = &performCastDoubleToLong; - table[BSONType::NumberDouble][BSONType::NumberDecimal] = [](Value inputValue) { - return Value(inputValue.coerceToDecimal()); - }; + table[BSONType::NumberDouble][BSONType::NumberDecimal] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value(inputValue.coerceToDecimal()); + }; // // Conversions from String @@ -4872,7 +4874,14 @@ public: 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::Bool] = [](const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { return Value(true); }; + table[BSONType::String][BSONType::Date] = []( + const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value(expCtx->timeZoneDatabase->fromString(inputValue.getStringData(), + mongo::TimeZoneDatabase::utcZone())); + }; 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>; @@ -4880,92 +4889,107 @@ public: // // Conversions from jstOID // - table[BSONType::jstOID][BSONType::Date] = [](Value inputValue) { - return Value(inputValue.getOid().asDateT()); - }; + table[BSONType::jstOID][BSONType::Date] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value(inputValue.getOid().asDateT()); + }; // // Conversions from Bool // - table[BSONType::Bool][BSONType::NumberDouble] = [](Value inputValue) { - return inputValue.getBool() ? Value(1.0) : Value(0.0); - }; + table[BSONType::Bool][BSONType::NumberDouble] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return inputValue.getBool() ? Value(1.0) : Value(0.0); + }; table[BSONType::Bool][BSONType::Bool] = &performIdentityConversion; - table[BSONType::Bool][BSONType::NumberInt] = [](Value inputValue) { - return inputValue.getBool() ? Value(int{1}) : Value(int{0}); - }; - table[BSONType::Bool][BSONType::NumberLong] = [](Value inputValue) { - return inputValue.getBool() ? Value(1LL) : Value(0LL); - }; - table[BSONType::Bool][BSONType::NumberDecimal] = [](Value inputValue) { - return inputValue.getBool() ? Value(Decimal128(1)) : Value(Decimal128(0)); - }; + table[BSONType::Bool][BSONType::NumberInt] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return inputValue.getBool() ? Value(int{1}) : Value(int{0}); + }; + table[BSONType::Bool][BSONType::NumberLong] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return inputValue.getBool() ? Value(1LL) : Value(0LL); + }; + table[BSONType::Bool][BSONType::NumberDecimal] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return inputValue.getBool() ? Value(Decimal128(1)) : Value(Decimal128(0)); + }; // // Conversions from Date // - table[BSONType::Date][BSONType::NumberDouble] = [](Value inputValue) { - return Value(static_cast<double>(inputValue.getDate().toMillisSinceEpoch())); - }; - table[BSONType::Date][BSONType::Bool] = [](Value inputValue) { - return Value(inputValue.coerceToBool()); - }; + table[BSONType::Date][BSONType::NumberDouble] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value(static_cast<double>(inputValue.getDate().toMillisSinceEpoch())); + }; + table[BSONType::Date] + [BSONType::Bool] = [](const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { return Value(inputValue.coerceToBool()); }; table[BSONType::Date][BSONType::Date] = &performIdentityConversion; - table[BSONType::Date][BSONType::NumberLong] = [](Value inputValue) { - return Value(inputValue.getDate().toMillisSinceEpoch()); - }; - table[BSONType::Date][BSONType::NumberDecimal] = [](Value inputValue) { - return Value( - Decimal128(static_cast<int64_t>(inputValue.getDate().toMillisSinceEpoch()))); - }; + table[BSONType::Date][BSONType::NumberLong] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value(inputValue.getDate().toMillisSinceEpoch()); + }; + table[BSONType::Date][BSONType::NumberDecimal] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value( + Decimal128(static_cast<int64_t>(inputValue.getDate().toMillisSinceEpoch()))); + }; // // Conversions from NumberInt // - table[BSONType::NumberInt][BSONType::NumberDouble] = [](Value inputValue) { - return Value(inputValue.coerceToDouble()); - }; - table[BSONType::NumberInt][BSONType::Bool] = [](Value inputValue) { - return Value(inputValue.coerceToBool()); - }; + table[BSONType::NumberInt][BSONType::NumberDouble] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value(inputValue.coerceToDouble()); + }; + table[BSONType::NumberInt] + [BSONType::Bool] = [](const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { return Value(inputValue.coerceToBool()); }; table[BSONType::NumberInt][BSONType::NumberInt] = &performIdentityConversion; - table[BSONType::NumberInt][BSONType::NumberLong] = [](Value inputValue) { - return Value(static_cast<long long>(inputValue.getInt())); - }; - table[BSONType::NumberInt][BSONType::NumberDecimal] = [](Value inputValue) { - return Value(inputValue.coerceToDecimal()); - }; + table[BSONType::NumberInt][BSONType::NumberLong] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value(static_cast<long long>(inputValue.getInt())); + }; + table[BSONType::NumberInt][BSONType::NumberDecimal] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value(inputValue.coerceToDecimal()); + }; // // Conversions from NumberLong // - table[BSONType::NumberLong][BSONType::NumberDouble] = [](Value inputValue) { - return Value(inputValue.coerceToDouble()); - }; - table[BSONType::NumberLong][BSONType::Bool] = [](Value inputValue) { - return Value(inputValue.coerceToBool()); - }; + table[BSONType::NumberLong][BSONType::NumberDouble] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value(inputValue.coerceToDouble()); + }; + table[BSONType::NumberLong] + [BSONType::Bool] = [](const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { return Value(inputValue.coerceToBool()); }; table[BSONType::NumberLong][BSONType::Date] = &performCastNumberToDate; table[BSONType::NumberLong][BSONType::NumberInt] = &performCastLongToInt; table[BSONType::NumberLong][BSONType::NumberLong] = &performIdentityConversion; - table[BSONType::NumberLong][BSONType::NumberDecimal] = [](Value inputValue) { - return Value(inputValue.coerceToDecimal()); - }; + table[BSONType::NumberLong][BSONType::NumberDecimal] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return Value(inputValue.coerceToDecimal()); + }; // // Conversions from NumberDecimal // table[BSONType::NumberDecimal][BSONType::NumberDouble] = &performCastDecimalToDouble; - table[BSONType::NumberDecimal][BSONType::Bool] = [](Value inputValue) { - return Value(inputValue.coerceToBool()); - }; + table[BSONType::NumberDecimal] + [BSONType::Bool] = [](const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { return Value(inputValue.coerceToBool()); }; table[BSONType::NumberDecimal][BSONType::Date] = &performCastNumberToDate; - table[BSONType::NumberDecimal][BSONType::NumberInt] = [](Value inputValue) { - return performCastDecimalToInt(BSONType::NumberInt, inputValue); - }; - table[BSONType::NumberDecimal][BSONType::NumberLong] = [](Value inputValue) { - return performCastDecimalToInt(BSONType::NumberLong, inputValue); - }; + table[BSONType::NumberDecimal][BSONType::NumberInt] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return performCastDecimalToInt(BSONType::NumberInt, inputValue); + }; + table[BSONType::NumberDecimal][BSONType::NumberLong] = + [](const boost::intrusive_ptr<ExpressionContext>& expCtx, Value inputValue) { + return performCastDecimalToInt(BSONType::NumberLong, inputValue); + }; table[BSONType::NumberDecimal][BSONType::NumberDecimal] = &performIdentityConversion; } @@ -4995,7 +5019,8 @@ private: std::isfinite(inputDouble)); } - static Value performCastDoubleToInt(Value inputValue) { + static Value performCastDoubleToInt(const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { double inputDouble = inputValue.getDouble(); validateDoubleValueIsFinite(inputDouble); @@ -5009,7 +5034,8 @@ private: return Value(static_cast<int>(inputDouble)); } - static Value performCastDoubleToLong(Value inputValue) { + static Value performCastDoubleToLong(const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { double inputDouble = inputValue.getDouble(); validateDoubleValueIsFinite(inputDouble); @@ -5064,7 +5090,8 @@ private: return result; } - static Value performCastDecimalToDouble(Value inputValue) { + static Value performCastDecimalToDouble(const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { Decimal128 inputDecimal = inputValue.getDecimal(); std::uint32_t signalingFlags = Decimal128::SignalingFlag::kNoFlag; @@ -5081,7 +5108,8 @@ private: return Value(result); } - static Value performCastLongToInt(Value inputValue) { + static Value performCastLongToInt(const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { long long longValue = inputValue.getLong(); uassert(ErrorCodes::ConversionFailure, @@ -5093,7 +5121,8 @@ private: return Value(static_cast<int>(longValue)); } - static Value performCastNumberToDate(Value inputValue) { + static Value performCastNumberToDate(const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { long long millisSinceEpoch; switch (inputValue.getType()) { @@ -5101,7 +5130,7 @@ private: millisSinceEpoch = inputValue.getLong(); break; case BSONType::NumberDouble: - millisSinceEpoch = performCastDoubleToLong(inputValue).getLong(); + millisSinceEpoch = performCastDoubleToLong(expCtx, inputValue).getLong(); break; case BSONType::NumberDecimal: millisSinceEpoch = @@ -5115,7 +5144,8 @@ private: } template <class targetType, int base> - static Value parseStringToNumber(Value inputValue) { + static Value parseStringToNumber(const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { auto stringValue = inputValue.getStringData(); targetType result; @@ -5137,7 +5167,8 @@ private: return Value(result); } - static Value parseStringToOID(Value inputValue) { + static Value parseStringToOID(const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { try { return Value(OID::createFromString(inputValue.getStringData())); } catch (const DBException& ex) { @@ -5150,7 +5181,8 @@ private: } } - static Value performIdentityConversion(Value inputValue) { + static Value performIdentityConversion(const boost::intrusive_ptr<ExpressionContext>& expCtx, + Value inputValue) { return inputValue; } }; @@ -5318,9 +5350,9 @@ BSONType ExpressionConvert::computeTargetType(Value targetTypeName) const { Value ExpressionConvert::performConversion(BSONType targetType, Value inputValue) const { invariant(!inputValue.nullish()); - static ConversionTable table; + static const ConversionTable table; BSONType inputType = inputValue.getType(); - return table.findConversionFunc(inputType, targetType)(inputValue); + return table.findConversionFunc(inputType, targetType)(getExpressionContext(), inputValue); } } // namespace mongo diff --git a/src/mongo/db/pipeline/expression_convert_test.cpp b/src/mongo/db/pipeline/expression_convert_test.cpp index 51f579ceede..b91c4a37d9a 100644 --- a/src/mongo/db/pipeline/expression_convert_test.cpp +++ b/src/mongo/db/pipeline/expression_convert_test.cpp @@ -2380,7 +2380,7 @@ TEST_F(ExpressionConvertTest, ConvertStringToOID) { convertExp->evaluate({}), OID("123456789abcdef123456789"), BSONType::jstOID); } -TEST_F(ExpressionConvertTest, ConvertToOIDFailsForInvalidHexStrings) { +TEST_F(ExpressionConvertTest, ConvertStringToOIDFailsForInvalidHexStrings) { auto expCtx = getExpCtx(); auto spec = fromjson("{$convert: {input: 'InvalidHexButSizeCorrect', to: 'objectId'}}"); @@ -2408,7 +2408,7 @@ TEST_F(ExpressionConvertTest, ConvertToOIDFailsForInvalidHexStrings) { }); } -TEST_F(ExpressionConvertTest, ConvertToOIDWithOnError) { +TEST_F(ExpressionConvertTest, ConvertStringToOIDWithOnError) { auto expCtx = getExpCtx(); const auto onErrorValue = "><(((((>"_sd; @@ -2429,6 +2429,116 @@ TEST_F(ExpressionConvertTest, ConvertToOIDWithOnError) { ASSERT_VALUE_CONTENTS_AND_TYPE(convertExp->evaluate({}), onErrorValue, BSONType::String); } +TEST_F(ExpressionConvertTest, ConvertStringToDateRejectsUnparsableString) { + auto expCtx = getExpCtx(); + + auto spec = fromjson("{$convert: {input: '60.Monday1770/06:59', to: 'date'}}"); + auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT_THROWS_CODE(convertExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure); + + spec = fromjson("{$convert: {input: 'Definitely not a date', to: 'date'}}"); + convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT_THROWS_CODE(convertExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure); +} + +TEST_F(ExpressionConvertTest, ConvertStringToDateRejectsTimezoneNameInString) { + auto expCtx = getExpCtx(); + + auto spec = fromjson("{$convert: {input: '2017-07-13T10:02:57 Europe/London', to: 'date'}}"); + auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT_THROWS_CODE(convertExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure); + + spec = fromjson("{$convert: {input: 'July 4, 2017 Europe/London', to: 'date'}}"); + convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT_THROWS_CODE(convertExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure); +} + +TEST_F(ExpressionConvertTest, ConvertStringToDate) { + auto expCtx = getExpCtx(); + + auto spec = fromjson("{$convert: {input: '$path1', to: 'date'}}"); + auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + + auto result = convertExp->evaluate({{"path1", Value("2017-07-06T12:35:37Z"_sd)}}); + ASSERT_EQ(result.getType(), BSONType::Date); + ASSERT_EQ("2017-07-06T12:35:37.000Z", result.toString()); + + result = convertExp->evaluate({{"path1", Value("2017-07-06T12:35:37.513Z"_sd)}}); + ASSERT_EQ(result.getType(), BSONType::Date); + ASSERT_EQ("2017-07-06T12:35:37.513Z", result.toString()); + + result = convertExp->evaluate({{"path1", Value("2017-07-06"_sd)}}); + ASSERT_EQ(result.getType(), BSONType::Date); + ASSERT_EQ("2017-07-06T00:00:00.000Z", result.toString()); +} + +TEST_F(ExpressionConvertTest, ConvertStringWithTimezoneToDate) { + auto expCtx = getExpCtx(); + + auto spec = fromjson("{$convert: {input: '$path1', to: 'date'}}"); + auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + + auto result = convertExp->evaluate({{"path1", Value("2017-07-14T12:02:44.771 GMT+02:00"_sd)}}); + ASSERT_EQ(result.getType(), BSONType::Date); + ASSERT_EQ("2017-07-14T10:02:44.771Z", result.toString()); + + result = convertExp->evaluate({{"path1", Value("2017-07-14T12:02:44.771 A"_sd)}}); + ASSERT_EQ(result.getType(), BSONType::Date); + ASSERT_EQ("2017-07-14T11:02:44.771Z", result.toString()); +} + +TEST_F(ExpressionConvertTest, ConvertVerbalStringToDate) { + auto expCtx = getExpCtx(); + + auto spec = fromjson("{$convert: {input: '$path1', to: 'date'}}"); + auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + + auto result = convertExp->evaluate({{"path1", Value("July 4th, 2017"_sd)}}); + ASSERT_EQ(result.getType(), BSONType::Date); + ASSERT_EQ("2017-07-04T00:00:00.000Z", result.toString()); + + result = convertExp->evaluate({{"path1", Value("July 4th, 2017 12pm"_sd)}}); + ASSERT_EQ(result.getType(), BSONType::Date); + ASSERT_EQ("2017-07-04T12:00:00.000Z", result.toString()); + + result = convertExp->evaluate({{"path1", Value("2017-Jul-04 noon"_sd)}}); + ASSERT_EQ(result.getType(), BSONType::Date); + ASSERT_EQ("2017-07-04T12:00:00.000Z", result.toString()); +} + +TEST_F(ExpressionConvertTest, ConvertStringToDateWithOnError) { + auto expCtx = getExpCtx(); + const auto onErrorValue = "(-_-)"_sd; + + auto spec = + fromjson("{$convert: {input: '$path1', to: 'date', onError: '" + onErrorValue + "'}}"); + auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + + auto result = convertExp->evaluate({{"path1", Value("Not a date"_sd)}}); + ASSERT_VALUE_CONTENTS_AND_TYPE(result, onErrorValue, BSONType::String); + + result = convertExp->evaluate({{"path1", Value("60.Monday1770/06:59"_sd)}}); + ASSERT_VALUE_CONTENTS_AND_TYPE(result, onErrorValue, BSONType::String); + + result = convertExp->evaluate({{"path1", Value("2017-07-13T10:02:57 Europe/London"_sd)}}); + ASSERT_VALUE_CONTENTS_AND_TYPE(result, onErrorValue, BSONType::String); +} + +TEST_F(ExpressionConvertTest, ConvertStringToDateWithOnNull) { + auto expCtx = getExpCtx(); + const auto onNullValue = "(-_-)"_sd; + + auto spec = + fromjson("{$convert: {input: '$path1', to: 'date', onNull: '" + onNullValue + "'}}"); + auto convertExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + + auto result = convertExp->evaluate({}); + ASSERT_VALUE_CONTENTS_AND_TYPE(result, onNullValue, BSONType::String); + + result = convertExp->evaluate({{"path1", Value(BSONNULL)}}); + ASSERT_VALUE_CONTENTS_AND_TYPE(result, onNullValue, BSONType::String); +} + } // namespace ExpressionConvertTest } // namespace mongo |