summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Zolnierz <nicholas.zolnierz@mongodb.com>2018-02-26 16:33:48 -0500
committerNick Zolnierz <nicholas.zolnierz@mongodb.com>2018-02-27 17:12:05 -0500
commitad0e38e91dea6b1e49807c6dc0e50197917d2804 (patch)
tree9a36b826dbb7f45c7706861206ac5f524078594d
parent9be3128cbba97456dd70647174a011c0a85ed88b (diff)
downloadmongo-3.6.3.tar.gz
SERVER-33169: Add string to date conversions to $convert3.6.3
-rw-r--r--src/mongo/db/pipeline/expression.cpp186
-rw-r--r--src/mongo/db/pipeline/expression_convert_test.cpp114
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