diff options
author | Derick Rethans <github@derickrethans.nl> | 2017-07-04 14:44:15 +0100 |
---|---|---|
committer | Derick Rethans <github@derickrethans.nl> | 2017-07-12 11:46:23 +0100 |
commit | c92d85fea435f64aedec07a2b42cbc16833ede82 (patch) | |
tree | 48138656fe46f2f299f7148d7c5a3bb148657a92 /src/mongo/db/pipeline | |
parent | 99a0dfa12c700fe5ec4fd2170f17de37b24e9d55 (diff) | |
download | mongo-c92d85fea435f64aedec07a2b42cbc16833ede82.tar.gz |
SERVER-28610 Added timezone support (and %z and %Z) to $dateToString
Diffstat (limited to 'src/mongo/db/pipeline')
-rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 39 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.h | 6 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_test.cpp | 85 |
3 files changed, 123 insertions, 7 deletions
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 798d05ca285..292ea5b5201 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -1471,12 +1471,15 @@ intrusive_ptr<Expression> ExpressionDateToString::parse( BSONElement formatElem; BSONElement dateElem; + BSONElement timeZoneElem; const BSONObj args = expr.embeddedObject(); BSONForEach(arg, args) { if (str::equals(arg.fieldName(), "format")) { formatElem = arg; } else if (str::equals(arg.fieldName(), "date")) { dateElem = arg; + } else if (str::equals(arg.fieldName(), "timezone")) { + timeZoneElem = arg; } else { uasserted(18534, str::stream() << "Unrecognized argument to $dateToString: " @@ -1495,37 +1498,63 @@ intrusive_ptr<Expression> ExpressionDateToString::parse( TimeZone::validateFormat(format); - return new ExpressionDateToString(expCtx, format, parseOperand(expCtx, dateElem, vps)); + return new ExpressionDateToString(expCtx, + format, + parseOperand(expCtx, dateElem, vps), + timeZoneElem ? parseOperand(expCtx, timeZoneElem, vps) + : nullptr); } ExpressionDateToString::ExpressionDateToString( const boost::intrusive_ptr<ExpressionContext>& expCtx, const string& format, - intrusive_ptr<Expression> date) - : Expression(expCtx), _format(format), _date(date) {} + intrusive_ptr<Expression> date, + intrusive_ptr<Expression> timeZone) + : Expression(expCtx), _format(format), _date(date), _timeZone(timeZone) {} intrusive_ptr<Expression> ExpressionDateToString::optimize() { _date = _date->optimize(); + if (_timeZone) { + _timeZone = _timeZone->optimize(); + } + + if (ExpressionConstant::allNullOrConstant({_date, _timeZone})) { + // Everything is a constant, so we can turn into a constant. + return ExpressionConstant::create(getExpressionContext(), evaluate(Document{})); + } + return this; } Value ExpressionDateToString::serialize(bool explain) const { return Value( - DOC("$dateToString" << DOC("format" << _format << "date" << _date->serialize(explain)))); + Document{{"$dateToString", + Document{{"format", _format}, + {"date", _date->serialize(explain)}, + {"timezone", _timeZone ? _timeZone->serialize(explain) : Value()}}}}); } Value ExpressionDateToString::evaluate(const Document& root) const { const Value date = _date->evaluate(root); + auto timeZone = makeTimeZone( + TimeZoneDatabase::get(getExpressionContext()->opCtx->getServiceContext()), root, _timeZone); + if (!timeZone) { + return Value(BSONNULL); + } + if (date.nullish()) { return Value(BSONNULL); } - return Value(TimeZoneDatabase::utcZone().formatDate(_format, date.coerceToDate())); + return Value(timeZone->formatDate(_format, date.coerceToDate())); } void ExpressionDateToString::addDependencies(DepsTracker* deps) const { _date->addDependencies(deps); + if (_timeZone) { + _timeZone->addDependencies(deps); + } } /* ----------------------- ExpressionDivide ---------------------------- */ diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h index aad2ced9b55..0a347bb6310 100644 --- a/src/mongo/db/pipeline/expression.h +++ b/src/mongo/db/pipeline/expression.h @@ -921,11 +921,13 @@ public: private: ExpressionDateToString(const boost::intrusive_ptr<ExpressionContext>& expCtx, - const std::string& format, // the format string - boost::intrusive_ptr<Expression> date); // the date to format + const std::string& format, // The format string. + boost::intrusive_ptr<Expression> date, // The date to format. + boost::intrusive_ptr<Expression> timeZone); // The optional timezone. const std::string _format; boost::intrusive_ptr<Expression> _date; + boost::intrusive_ptr<Expression> _timeZone; }; class ExpressionDayOfMonth final : public DateExpressionAcceptingTimeZone<ExpressionDayOfMonth> { diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp index f89735aff56..f4066e10d3a 100644 --- a/src/mongo/db/pipeline/expression_test.cpp +++ b/src/mongo/db/pipeline/expression_test.cpp @@ -4884,6 +4884,91 @@ TEST_F(DateExpressionTest, DoesResultInNullIfGivenNullishInput) { } // namespace DateExpressionsTest +namespace ExpressionDateToStringTest { + +// This provides access to an ExpressionContext that has a valid ServiceContext with a +// TimeZoneDatabase via getExpCtx(), but we'll use a different name for this test suite. +using ExpressionDateToStringTest = AggregationContextFixture; + +TEST_F(ExpressionDateToStringTest, SerializesToObjectSyntax) { + auto expCtx = getExpCtx(); + + // Test that it serializes to the full format if given an object specification. + BSONObj spec = BSON("$dateToString" << BSON("date" << Date_t{} << "timezone" + << "Europe/London" + << "format" + << "%Y-%m-%d")); + auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + auto expectedSerialization = + Value(Document{{"$dateToString", + Document{{"format", "%Y-%m-%d"_sd}, + {"date", Document{{"$const", Date_t{}}}}, + {"timezone", Document{{"$const", "Europe/London"_sd}}}}}}); + ASSERT_VALUE_EQ(dateExp->serialize(true), expectedSerialization); + ASSERT_VALUE_EQ(dateExp->serialize(false), expectedSerialization); +} + +TEST_F(ExpressionDateToStringTest, OptimizesToConstantIfAllInputsAreConstant) { + auto expCtx = getExpCtx(); + + // Test that it becomes a constant if both format and date are constant, and timezone is + // missing. + auto spec = BSON("$dateToString" << BSON("format" + << "%Y-%m-%d" + << "date" + << Date_t{})); + auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get())); + + // Test that it becomes a constant if both format, date and timezone are provided, and are both + // constants. + spec = BSON("$dateToString" << BSON("format" + << "%Y-%m-%d" + << "date" + << Date_t{} + << "timezone" + << "Europe/Amsterdam")); + dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get())); + + // Test that it becomes a constant if both format, date and timezone are provided, and are both + // expressions which evaluate to constants. + spec = BSON("$dateToString" << BSON("format" + << "%Y-%m%d" + << "date" + << BSON("$add" << BSON_ARRAY(Date_t{} << 1000)) + << "timezone" + << BSON("$concat" << BSON_ARRAY("Europe" + << "/" + << "London")))); + dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get())); + + // Test that it does *not* become a constant if both format, date and timezone are provided, but + // date is not a constant. + spec = BSON("$dateToString" << BSON("format" + << "%Y-%m-%d" + << "date" + << "$date" + << "timezone" + << "Europe/London")); + dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT_FALSE(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get())); + + // Test that it does *not* become a constant if both format, date and timezone are provided, but + // timezone is not a constant. + spec = BSON("$dateToString" << BSON("format" + << "%Y-%m-%d" + << "date" + << Date_t{} + << "timezone" + << "$tz")); + dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT_FALSE(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get())); +} + +} // namespace ExpressionDateToStringTest + class All : public Suite { public: All() : Suite("expression") {} |