summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline
diff options
context:
space:
mode:
authorDerick Rethans <github@derickrethans.nl>2017-07-04 14:44:15 +0100
committerDerick Rethans <github@derickrethans.nl>2017-07-12 11:46:23 +0100
commitc92d85fea435f64aedec07a2b42cbc16833ede82 (patch)
tree48138656fe46f2f299f7148d7c5a3bb148657a92 /src/mongo/db/pipeline
parent99a0dfa12c700fe5ec4fd2170f17de37b24e9d55 (diff)
downloadmongo-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.cpp39
-rw-r--r--src/mongo/db/pipeline/expression.h6
-rw-r--r--src/mongo/db/pipeline/expression_test.cpp85
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") {}