summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorNick Zolnierz <nicholas.zolnierz@mongodb.com>2018-01-30 13:10:47 -0500
committerNick Zolnierz <nicholas.zolnierz@mongodb.com>2018-02-21 13:33:36 -0500
commit1e9e5f85d3d9ae42ea80a48b593be45306724831 (patch)
tree6ffaca9b84c1117fe6cf4b9e33e720463898b9db /src/mongo
parentf15200621c45cf27bc348eaa1e0573372fc6ff93 (diff)
downloadmongo-1e9e5f85d3d9ae42ea80a48b593be45306724831.tar.gz
SERVER-32736: Add "onError" and "onNull" options to $dateFromString
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/pipeline/expression.cpp114
-rw-r--r--src/mongo/db/pipeline/expression.h6
-rw-r--r--src/mongo/db/pipeline/expression_date_test.cpp280
-rw-r--r--src/mongo/db/query/datetime/date_time_support.cpp10
-rw-r--r--src/mongo/db/query/datetime/date_time_support_test.cpp40
5 files changed, 380 insertions, 70 deletions
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index 3db514b5222..f679616e720 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -1279,9 +1279,7 @@ intrusive_ptr<Expression> ExpressionDateFromString::parse(
<< typeName(expr.type()),
expr.type() == BSONType::Object);
- BSONElement dateStringElem;
- BSONElement timeZoneElem;
- BSONElement formatElem;
+ BSONElement dateStringElem, timeZoneElem, formatElem, onNullElem, onErrorElem;
const BSONObj args = expr.embeddedObject();
for (auto&& arg : args) {
@@ -1293,6 +1291,10 @@ intrusive_ptr<Expression> ExpressionDateFromString::parse(
dateStringElem = arg;
} else if (field == "timezone"_sd) {
timeZoneElem = arg;
+ } else if (field == "onNull"_sd) {
+ onNullElem = arg;
+ } else if (field == "onError"_sd) {
+ onErrorElem = arg;
} else {
uasserted(40541,
str::stream() << "Unrecognized argument to $dateFromString: "
@@ -1306,29 +1308,45 @@ intrusive_ptr<Expression> ExpressionDateFromString::parse(
expCtx,
parseOperand(expCtx, dateStringElem, vps),
timeZoneElem ? parseOperand(expCtx, timeZoneElem, vps) : nullptr,
- formatElem ? parseOperand(expCtx, formatElem, vps) : nullptr);
+ formatElem ? parseOperand(expCtx, formatElem, vps) : nullptr,
+ onNullElem ? parseOperand(expCtx, onNullElem, vps) : nullptr,
+ onErrorElem ? parseOperand(expCtx, onErrorElem, vps) : nullptr);
}
ExpressionDateFromString::ExpressionDateFromString(
const boost::intrusive_ptr<ExpressionContext>& expCtx,
intrusive_ptr<Expression> dateString,
intrusive_ptr<Expression> timeZone,
- intrusive_ptr<Expression> format)
+ intrusive_ptr<Expression> format,
+ intrusive_ptr<Expression> onNull,
+ intrusive_ptr<Expression> onError)
: Expression(expCtx),
_dateString(std::move(dateString)),
_timeZone(std::move(timeZone)),
- _format(std::move(format)) {}
+ _format(std::move(format)),
+ _onNull(std::move(onNull)),
+ _onError(std::move(onError)) {}
intrusive_ptr<Expression> ExpressionDateFromString::optimize() {
_dateString = _dateString->optimize();
if (_timeZone) {
_timeZone = _timeZone->optimize();
}
+
if (_format) {
_format = _format->optimize();
}
- if (ExpressionConstant::allNullOrConstant({_dateString, _timeZone, _format})) {
+ if (_onNull) {
+ _onNull = _onNull->optimize();
+ }
+
+ if (_onError) {
+ _onError = _onError->optimize();
+ }
+
+ if (ExpressionConstant::allNullOrConstant(
+ {_dateString, _timeZone, _format, _onNull, _onError})) {
// Everything is a constant, so we can turn into a constant.
return ExpressionConstant::create(getExpressionContext(), evaluate(Document{}));
}
@@ -1340,48 +1358,71 @@ Value ExpressionDateFromString::serialize(bool explain) const {
Document{{"$dateFromString",
Document{{"dateString", _dateString->serialize(explain)},
{"timezone", _timeZone ? _timeZone->serialize(explain) : Value()},
- {"format", _format ? _format->serialize(explain) : Value()}}}});
+ {"format", _format ? _format->serialize(explain) : Value()},
+ {"onNull", _onNull ? _onNull->serialize(explain) : Value()},
+ {"onError", _onError ? _onError->serialize(explain) : Value()}}}});
}
Value ExpressionDateFromString::evaluate(const Document& root) const {
const Value dateString = _dateString->evaluate(root);
+ Value formatValue;
+
+ // Eagerly validate the format parameter, ignoring if nullish since the input string nullish
+ // behavior takes precedence.
+ if (_format) {
+ formatValue = _format->evaluate(root);
+ if (!formatValue.nullish()) {
+ uassert(40684,
+ str::stream() << "$dateFromString requires that 'format' be a string, found: "
+ << typeName(formatValue.getType())
+ << " with value "
+ << formatValue.toString(),
+ formatValue.getType() == BSONType::String);
+
+ TimeZone::validateFromStringFormat(formatValue.getStringData());
+ }
+ }
+ // Evaluate the timezone parameter before checking for nullish input, as this will throw an
+ // exception for an invalid timezone string.
auto timeZone = makeTimeZone(getExpressionContext()->timeZoneDatabase, root, _timeZone.get());
- if (!timeZone || dateString.nullish()) {
- return Value(BSONNULL);
+ // Behavior for nullish input takes precedence over other nullish elements.
+ if (dateString.nullish()) {
+ return _onNull ? _onNull->evaluate(root) : Value(BSONNULL);
}
- uassert(40543,
- str::stream() << "$dateFromString requires that 'dateString' be a string, found: "
- << typeName(dateString.getType())
- << " with value "
- << dateString.toString(),
- dateString.getType() == BSONType::String);
- const auto& dateTimeString = dateString.getStringData();
+ try {
+ uassert(ErrorCodes::ConversionFailure,
+ str::stream() << "$dateFromString requires that 'dateString' be a string, found: "
+ << typeName(dateString.getType())
+ << " with value "
+ << dateString.toString(),
+ dateString.getType() == BSONType::String);
- if (_format) {
- const Value format = _format->evaluate(root);
+ const auto dateTimeString = dateString.getStringData();
- if (format.nullish()) {
+ if (!timeZone) {
return Value(BSONNULL);
}
- uassert(40684,
- str::stream() << "$dateFromString requires that 'format' be a string, found: "
- << typeName(format.getType())
- << " with value "
- << format.toString(),
- format.getType() == BSONType::String);
- const auto& formatString = format.getStringData();
+ if (_format) {
+ if (formatValue.nullish()) {
+ return Value(BSONNULL);
+ }
- TimeZone::validateFromStringFormat(formatString);
+ return Value(getExpressionContext()->timeZoneDatabase->fromString(
+ dateTimeString, timeZone, formatValue.getStringData()));
+ }
- return Value(getExpressionContext()->timeZoneDatabase->fromString(
- dateTimeString, timeZone, formatString));
+ return Value(
+ getExpressionContext()->timeZoneDatabase->fromString(dateTimeString, timeZone));
+ } catch (const ExceptionFor<ErrorCodes::ConversionFailure>&) {
+ if (_onError) {
+ return _onError->evaluate(root);
+ }
+ throw;
}
-
- return Value(getExpressionContext()->timeZoneDatabase->fromString(dateTimeString, timeZone));
}
void ExpressionDateFromString::_doAddDependencies(DepsTracker* deps) const {
@@ -1389,9 +1430,18 @@ void ExpressionDateFromString::_doAddDependencies(DepsTracker* deps) const {
if (_timeZone) {
_timeZone->addDependencies(deps);
}
+
if (_format) {
_format->addDependencies(deps);
}
+
+ if (_onNull) {
+ _onNull->addDependencies(deps);
+ }
+
+ if (_onError) {
+ _onError->addDependencies(deps);
+ }
}
/* ---------------------- ExpressionDateToParts ----------------------- */
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h
index 7f843caffcc..b70457cc06f 100644
--- a/src/mongo/db/pipeline/expression.h
+++ b/src/mongo/db/pipeline/expression.h
@@ -874,11 +874,15 @@ private:
ExpressionDateFromString(const boost::intrusive_ptr<ExpressionContext>& expCtx,
boost::intrusive_ptr<Expression> dateString,
boost::intrusive_ptr<Expression> timeZone,
- boost::intrusive_ptr<Expression> format);
+ boost::intrusive_ptr<Expression> format,
+ boost::intrusive_ptr<Expression> onNull,
+ boost::intrusive_ptr<Expression> onError);
boost::intrusive_ptr<Expression> _dateString;
boost::intrusive_ptr<Expression> _timeZone;
boost::intrusive_ptr<Expression> _format;
+ boost::intrusive_ptr<Expression> _onNull;
+ boost::intrusive_ptr<Expression> _onError;
};
class ExpressionDateFromParts final : public Expression {
diff --git a/src/mongo/db/pipeline/expression_date_test.cpp b/src/mongo/db/pipeline/expression_date_test.cpp
index bfe72aefa3b..fbceb9c5349 100644
--- a/src/mongo/db/pipeline/expression_date_test.cpp
+++ b/src/mongo/db/pipeline/expression_date_test.cpp
@@ -772,11 +772,34 @@ TEST_F(ExpressionDateFromStringTest, SerializesToObjectSyntax) {
ASSERT_VALUE_EQ(dateExp->serialize(true), expectedSerialization);
ASSERT_VALUE_EQ(dateExp->serialize(false), expectedSerialization);
+
+ spec = BSON("$dateFromString" << BSON("dateString"
+ << "2017-07-04T13:06:44Z"
+ << "timezone"
+ << "Europe/London"
+ << "format"
+ << "%Y-%d-%mT%H:%M:%S"
+ << "onNull"
+ << "nullDefault"
+ << "onError"
+ << "errorDefault"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ expectedSerialization =
+ Value(Document{{"$dateFromString",
+ Document{{"dateString", Document{{"$const", "2017-07-04T13:06:44Z"_sd}}},
+ {"timezone", Document{{"$const", "Europe/London"_sd}}},
+ {"format", Document{{"$const", "%Y-%d-%mT%H:%M:%S"_sd}}},
+ {"onNull", Document{{"$const", "nullDefault"_sd}}},
+ {"onError", Document{{"$const", "errorDefault"_sd}}}}}});
+
+ ASSERT_VALUE_EQ(dateExp->serialize(true), expectedSerialization);
+ ASSERT_VALUE_EQ(dateExp->serialize(false), expectedSerialization);
}
TEST_F(ExpressionDateFromStringTest, OptimizesToConstantIfAllInputsAreConstant) {
auto expCtx = getExpCtx();
- // Test that it becomes a constant with just the dateString.
+
+ // Test that it becomes a constant if all parameters evaluate to a constant value.
auto spec = BSON("$dateFromString" << BSON("dateString"
<< "2017-07-04T13:09:57Z"));
auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
@@ -785,7 +808,6 @@ TEST_F(ExpressionDateFromStringTest, OptimizesToConstantIfAllInputsAreConstant)
Date_t dateVal = Date_t::fromMillisSinceEpoch(1499173797000);
ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{}));
- // Test that it becomes a constant with the dateString and timezone being a constant.
spec = BSON("$dateFromString" << BSON("dateString"
<< "2017-07-04T13:09:57"
<< "timezone"
@@ -806,6 +828,29 @@ TEST_F(ExpressionDateFromStringTest, OptimizesToConstantIfAllInputsAreConstant)
dateVal = Date_t::fromMillisSinceEpoch(1499170197000);
ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{}));
+ spec = BSON("$dateFromString" << BSON("dateString"
+ << "2017-07-04T13:09:57"
+ << "onNull"
+ << "Null default"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
+
+ spec = BSON("$dateFromString" << BSON("dateString"
+ << "2017-07-04T13:09:57"
+ << "onError"
+ << "Error default"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
+
+ spec = BSON("$dateFromString" << BSON("dateString"
+ << "2017-07-04T13:09:57"
+ << "onError"
+ << "Error default"
+ << "onNull"
+ << "null default"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
+
// Test that it does *not* become a constant if dateString is not a constant.
spec = BSON("$dateFromString" << BSON("dateString"
<< "$date"));
@@ -829,6 +874,22 @@ TEST_F(ExpressionDateFromStringTest, OptimizesToConstantIfAllInputsAreConstant)
<< "$format"));
dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
ASSERT_FALSE(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
+
+ // Test that it does *not* become a constant if onNull is not a constant.
+ spec = BSON("$dateFromString" << BSON("dateString"
+ << "2017-07-04T13:09:57Z"
+ << "onNull"
+ << "$onNull"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_FALSE(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
+
+ // Test that it does *not* become a constant if onError is not a constant.
+ spec = BSON("$dateFromString" << BSON("dateString"
+ << "2017-07-04T13:09:57Z"
+ << "onError"
+ << "$onError"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_FALSE(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get()));
}
TEST_F(ExpressionDateFromStringTest, RejectsUnparsableString) {
@@ -837,7 +898,7 @@ TEST_F(ExpressionDateFromStringTest, RejectsUnparsableString) {
auto spec = BSON("$dateFromString" << BSON("dateString"
<< "60.Monday1770/06:59"));
auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40553);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure);
}
TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInString) {
@@ -846,12 +907,12 @@ TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInString) {
auto spec = BSON("$dateFromString" << BSON("dateString"
<< "2017-07-13T10:02:57 Europe/London"));
auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40553);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure);
spec = BSON("$dateFromString" << BSON("dateString"
<< "July 4, 2017 Europe/London"));
dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40553);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure);
}
TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInStringAndArgument) {
@@ -863,7 +924,7 @@ TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInStringAndArgument) {
<< "timezone"
<< "Europe/London"));
auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40551);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure);
// Test with timezone abbreviation and timezone
spec = BSON("$dateFromString" << BSON("dateString"
@@ -871,7 +932,7 @@ TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInStringAndArgument) {
<< "timezone"
<< "Europe/London"));
dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40551);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure);
// Test with GMT offset and timezone
spec = BSON("$dateFromString" << BSON("dateString"
@@ -879,7 +940,7 @@ TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInStringAndArgument) {
<< "timezone"
<< "Europe/London"));
dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40554);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure);
// Test with GMT offset and GMT timezone
spec = BSON("$dateFromString" << BSON("dateString"
@@ -887,7 +948,7 @@ TEST_F(ExpressionDateFromStringTest, RejectsTimeZoneInStringAndArgument) {
<< "timezone"
<< "GMT"));
dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40554);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure);
}
TEST_F(ExpressionDateFromStringTest, RejectsNonStringFormat) {
@@ -916,14 +977,14 @@ TEST_F(ExpressionDateFromStringTest, RejectsStringsThatDoNotMatchFormat) {
<< "format"
<< "%Y-%m-%d"));
auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40553);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure);
spec = BSON("$dateFromString" << BSON("dateString"
<< "2017-07"
<< "format"
<< "%m-%Y"));
dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
- ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40553);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, ErrorCodes::ConversionFailure);
}
TEST_F(ExpressionDateFromStringTest, EscapeCharacterAllowsPrefixUsage) {
@@ -1050,6 +1111,201 @@ TEST_F(ExpressionDateFromStringTest, ConvertStringWithISODateFormat) {
ASSERT_EQ("2017-01-08T00:00:00.000Z", dateExp->evaluate(Document{}).toString());
}
-} // namespace ExpressionDateFromStringTest
+TEST_F(ExpressionDateFromStringTest, ReturnsOnNullForNullishInput) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$dateFromString" << BSON("dateString" << BSONNULL << "onNull"
+ << "Null default"));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value("Null default"_sd), dateExp->evaluate(Document{}));
+
+ spec = BSON("$dateFromString" << BSON("dateString"
+ << "$missing"
+ << "onNull"
+ << "Null default"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value("Null default"_sd), dateExp->evaluate(Document{}));
+
+ spec = BSON("$dateFromString" << BSON("dateString"
+ << "$missing"
+ << "onNull"
+ << "$alsoMissing"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(), dateExp->evaluate(Document{}));
+
+ spec = BSON("$dateFromString" << BSON("dateString" << BSONNULL << "onNull" << BSONNULL));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(Document{}));
+}
+
+TEST_F(ExpressionDateFromStringTest, InvalidFormatTakesPrecedenceOverOnNull) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$dateFromString" << BSON("dateString" << BSONNULL << "onNull"
+ << "Null default"
+ << "format"
+ << 5));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40684);
+
+ spec = BSON("$dateFromString" << BSON("dateString" << BSONNULL << "onNull"
+ << "Null default"
+ << "format"
+ << "%"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 18535);
+}
+
+TEST_F(ExpressionDateFromStringTest, InvalidFormatTakesPrecedenceOverOnError) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$dateFromString" << BSON("dateString"
+ << "Invalid dateString"
+ << "onError"
+ << "Not used default"
+ << "format"
+ << 5));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40684);
+
+ spec = BSON("$dateFromString" << BSON("dateString" << 5 << "onError"
+ << "Not used default"
+ << "format"
+ << "%"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 18535);
+}
+
+TEST_F(ExpressionDateFromStringTest, InvalidTimezoneTakesPrecedenceOverOnNull) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$dateFromString" << BSON("dateString" << BSONNULL << "onNull"
+ << "Null default"
+ << "timezone"
+ << 5));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40517);
+ spec = BSON("$dateFromString" << BSON("dateString" << BSONNULL << "onNull"
+ << "Null default"
+ << "timezone"
+ << "invalid timezone string"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40485);
+}
+
+TEST_F(ExpressionDateFromStringTest, InvalidTimezoneTakesPrecedenceOverOnError) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$dateFromString" << BSON("dateString"
+ << "Invalid dateString"
+ << "onError"
+ << "On error default"
+ << "timezone"
+ << 5));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40517);
+
+ spec = BSON("$dateFromString" << BSON("dateString" << 5 << "onError"
+ << "On error default"
+ << "timezone"
+ << "invalid timezone string"));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_THROWS_CODE(dateExp->evaluate({}), AssertionException, 40485);
+}
+
+TEST_F(ExpressionDateFromStringTest, OnNullTakesPrecedenceOverOtherNullishParameters) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$dateFromString" << BSON("dateString" << BSONNULL << "onNull"
+ << "Null default"
+ << "timezone"
+ << BSONNULL));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value("Null default"_sd), dateExp->evaluate(Document{}));
+
+ spec = BSON("$dateFromString" << BSON("dateString" << BSONNULL << "onNull"
+ << "Null default"
+ << "format"
+ << BSONNULL));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value("Null default"_sd), dateExp->evaluate(Document{}));
+}
+
+TEST_F(ExpressionDateFromStringTest, OnNullOnlyUsedIfInputStringIsNullish) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$dateFromString" << BSON("dateString"
+ << "2018-02-14"
+ << "onNull"
+ << "Null default"
+ << "timezone"
+ << BSONNULL));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(Document{}));
+
+ spec = BSON("$dateFromString" << BSON("dateString"
+ << "2018-02-14"
+ << "onNull"
+ << "Null default"
+ << "format"
+ << BSONNULL));
+ dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value(BSONNULL), dateExp->evaluate(Document{}));
+}
+
+TEST_F(ExpressionDateFromStringTest, ReturnsOnErrorForParseFailures) {
+ auto expCtx = getExpCtx();
+
+ std::vector<std::string> invalidDates = {
+ "60.Monday1770/06:59", "July 4th", "12:50:53", "2017, 12:50:53"};
+ for (auto date : invalidDates) {
+ auto spec = BSON("$dateFromString" << BSON("dateString" << date << "onError"
+ << "Error default"));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value("Error default"_sd), dateExp->evaluate(Document{}));
+ }
+}
+
+TEST_F(ExpressionDateFromStringTest, ReturnsOnErrorForFormatMismatch) {
+ auto expCtx = getExpCtx();
+
+ const std::string date = "2018/02/06";
+ std::vector<std::string> unmatchedFormats = {"%Y", "%Y/%m/%d:%H", "Y/m/d"};
+ for (auto format : unmatchedFormats) {
+ auto spec =
+ BSON("$dateFromString" << BSON("dateString" << date << "format" << format << "onError"
+ << "Error default"));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_VALUE_EQ(Value("Error default"_sd), dateExp->evaluate(Document{}));
+ }
+}
+
+TEST_F(ExpressionDateFromStringTest, OnNullEvaluatedLazily) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$dateFromString" << BSON("dateString"
+ << "$date"
+ << "onNull"
+ << BSON("$divide" << BSON_ARRAY(1 << 0))));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_EQ("2018-02-14T00:00:00.000Z",
+ dateExp->evaluate(Document{{"date", "2018-02-14"_sd}}).toString());
+ ASSERT_THROWS_CODE(dateExp->evaluate(Document{}), AssertionException, 16608);
+}
+
+TEST_F(ExpressionDateFromStringTest, OnErrorEvaluatedLazily) {
+ auto expCtx = getExpCtx();
+
+ auto spec = BSON("$dateFromString" << BSON("dateString"
+ << "$date"
+ << "onError"
+ << BSON("$divide" << BSON_ARRAY(1 << 0))));
+ auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState);
+ ASSERT_EQ("2018-02-14T00:00:00.000Z",
+ dateExp->evaluate(Document{{"date", "2018-02-14"_sd}}).toString());
+ ASSERT_THROWS_CODE(dateExp->evaluate(Document{{"date", 5}}), AssertionException, 16608);
+}
+
+} // namespace ExpressionDateFromStringTest
} // namespace mongo
diff --git a/src/mongo/db/query/datetime/date_time_support.cpp b/src/mongo/db/query/datetime/date_time_support.cpp
index c13bce509c4..31a38dba606 100644
--- a/src/mongo/db/query/datetime/date_time_support.cpp
+++ b/src/mongo/db/query/datetime/date_time_support.cpp
@@ -259,7 +259,7 @@ Date_t TimeZoneDatabase::fromString(StringData dateString,
<< errors->warning_messages[i].character << "'";
}
- uasserted(40553, sb.str());
+ uasserted(ErrorCodes::ConversionFailure, sb.str());
}
// If the time portion is fully missing, initialize to 0. This allows for the '%Y-%m-%d' format
@@ -272,7 +272,7 @@ Date_t TimeZoneDatabase::fromString(StringData dateString,
if (parsedTime->y == TIMELIB_UNSET || parsedTime->m == TIMELIB_UNSET ||
parsedTime->d == TIMELIB_UNSET || parsedTime->h == TIMELIB_UNSET ||
parsedTime->i == TIMELIB_UNSET || parsedTime->s == TIMELIB_UNSET) {
- uasserted(40545,
+ uasserted(ErrorCodes::ConversionFailure,
str::stream()
<< "an incomplete date/time string has been found, with elements missing: \""
<< dateString
@@ -285,20 +285,20 @@ Date_t TimeZoneDatabase::fromString(StringData dateString,
// Do nothing, as this indicates there is no associated time zone information.
break;
case 1:
- uasserted(40554,
+ uasserted(ErrorCodes::ConversionFailure,
"you cannot pass in a date/time string with GMT "
"offset together with a timezone argument");
break;
case 2:
uasserted(
- 40551,
+ ErrorCodes::ConversionFailure,
str::stream()
<< "you cannot pass in a date/time string with time zone information ('"
<< parsedTime.get()->tz_abbr
<< "') together with a timezone argument");
break;
default: // should technically not be possible to reach
- uasserted(40552,
+ uasserted(ErrorCodes::ConversionFailure,
"you cannot pass in a date/time string with "
"time zone information and a timezone argument "
"at the same time");
diff --git a/src/mongo/db/query/datetime/date_time_support_test.cpp b/src/mongo/db/query/datetime/date_time_support_test.cpp
index aeccf4030fa..0d08f11d036 100644
--- a/src/mongo/db/query/datetime/date_time_support_test.cpp
+++ b/src/mongo/db/query/datetime/date_time_support_test.cpp
@@ -1000,10 +1000,10 @@ TEST(DateFromString, CorrectlyParsesStringThatMatchesFormat) {
TEST(DateFromString, RejectsStringWithInvalidYearFormat) {
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("201", kDefaultTimeZone, "%Y"_sd),
AssertionException,
- 40545);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("20i7", kDefaultTimeZone, "%Y"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
}
TEST(DateFromString, RejectsStringWithInvalidMinuteFormat) {
@@ -1011,11 +1011,11 @@ TEST(DateFromString, RejectsStringWithInvalidMinuteFormat) {
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString(
"2017-01-01T00:1:00", kDefaultTimeZone, "%Y-%m-%dT%H%M%S"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString(
"2017-01-01T00:0i:00", kDefaultTimeZone, "%Y-%m-%dT%H%M%S"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
}
TEST(DateFromString, RejectsStringWithInvalidSecondsFormat) {
@@ -1023,54 +1023,54 @@ TEST(DateFromString, RejectsStringWithInvalidSecondsFormat) {
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString(
"2017-01-01T00:00:1", kDefaultTimeZone, "%Y-%m-%dT%H%M%S"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString(
"2017-01-01T00:00:i0", kDefaultTimeZone, "%Y-%m-%dT%H%M%S"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
}
TEST(DateFromString, RejectsStringWithInvalidMillisecondsFormat) {
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString(
"2017-01-01T00:00:00.i", kDefaultTimeZone, "%Y-%m-%dT%H:%M:%S.%L"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
}
TEST(DateFromString, RejectsStringWithInvalidISOYear) {
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("20i7", kDefaultTimeZone, "%G"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
}
TEST(DateFromString, RejectsStringWithInvalidISOWeekOfYear) {
// ISO week of year must be between 1 and 53.
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("2017-55", kDefaultTimeZone, "%G-%V"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("2017-FF", kDefaultTimeZone, "%G-%V"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
}
TEST(DateFromString, RejectsStringWithInvalidISODayOfWeek) {
// Day of week must be single digit between 1 and 7.
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("2017-8", kDefaultTimeZone, "%G-%u"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("2017-0", kDefaultTimeZone, "%G-%u"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("2017-a", kDefaultTimeZone, "%G-%u"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("2017-11", kDefaultTimeZone, "%G-%u"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(
kDefaultTimeZoneDatabase.fromString("2017-123", kDefaultTimeZone, "%G-%u"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
}
TEST(DateFromString, RejectsStringWithInvalidTimezoneOffset) {
@@ -1078,24 +1078,24 @@ TEST(DateFromString, RejectsStringWithInvalidTimezoneOffset) {
ASSERT_THROWS_CODE(
kDefaultTimeZoneDatabase.fromString("2017 500", kDefaultTimeZone, "%G %Z"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(
kDefaultTimeZoneDatabase.fromString("2017 0500", kDefaultTimeZone, "%G %Z"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(
kDefaultTimeZoneDatabase.fromString("2017 +i00", kDefaultTimeZone, "%G %Z"_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
}
TEST(DateFromString, EmptyFormatStringThrowsForAllInputs) {
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("1/1/2017", kDefaultTimeZone, ""_sd),
AssertionException,
- 40553);
+ ErrorCodes::ConversionFailure);
ASSERT_THROWS_CODE(kDefaultTimeZoneDatabase.fromString("", kDefaultTimeZone, ""_sd),
AssertionException,
- 40545);
+ ErrorCodes::ConversionFailure);
}
} // namespace