diff options
author | Derick Rethans <github@derickrethans.nl> | 2017-06-16 16:22:47 +0100 |
---|---|---|
committer | Derick Rethans <github@derickrethans.nl> | 2017-07-13 16:01:19 +0100 |
commit | a8b0a95eda28a6fbeb87fb347ec3449ce8eb3683 (patch) | |
tree | dfba4a34279409c19818e2b5b793cfe87aadfcd5 /src/mongo | |
parent | 943361fe17e0443d6b899ba10160fb1f68742f42 (diff) | |
download | mongo-a8b0a95eda28a6fbeb87fb347ec3449ce8eb3683.tar.gz |
SERVER-29208 Add the $dateFromString aggregation operator
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 73 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.h | 19 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_test.cpp | 42 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/date_time_support.cpp | 36 | ||||
-rw-r--r-- | src/mongo/db/query/datetime/date_time_support.h | 5 |
5 files changed, 174 insertions, 1 deletions
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 292ea5b5201..d9e8e2abf3e 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -1314,6 +1314,79 @@ void ExpressionDateFromParts::addDependencies(DepsTracker* deps) const { } } +/* ---------------------- ExpressionDateFromString --------------------- */ + +REGISTER_EXPRESSION(dateFromString, ExpressionDateFromString::parse); +intrusive_ptr<Expression> ExpressionDateFromString::parse( + const boost::intrusive_ptr<ExpressionContext>& expCtx, + BSONElement expr, + const VariablesParseState& vps) { + + uassert(40540, + str::stream() << "$dateFromString only supports an object as an argument, found: " + << typeName(expr.type()), + expr.type() == BSONType::Object); + + BSONElement dateStringElem; + const BSONObj args = expr.embeddedObject(); + for (auto&& arg : args) { + if (arg.fieldNameStringData() == "dateString"_sd) { + dateStringElem = arg; + } else { + uasserted(40541, + str::stream() << "Unrecognized argument to $dateFromString: " + << arg.fieldName()); + } + } + + uassert(40542, "Missing 'dateString' parameter to $dateFromString", dateStringElem); + + return new ExpressionDateFromString(expCtx, parseOperand(expCtx, dateStringElem, vps)); +} + +ExpressionDateFromString::ExpressionDateFromString( + const boost::intrusive_ptr<ExpressionContext>& expCtx, intrusive_ptr<Expression> dateString) + : Expression(expCtx), _dateString(dateString) {} + +intrusive_ptr<Expression> ExpressionDateFromString::optimize() { + _dateString = _dateString->optimize(); + + if (dynamic_cast<ExpressionConstant*>(_dateString.get())) { + // Everything is a constant, so we can turn into a constant. + return ExpressionConstant::create(getExpressionContext(), evaluate(Document{})); + } + return this; +} + +Value ExpressionDateFromString::serialize(bool explain) const { + return Value( + Document{{"$dateFromString", Document{{"dateString", _dateString->serialize(explain)}}}}); +} + +Value ExpressionDateFromString::evaluate(const Document& root) const { + const Value dateString = _dateString->evaluate(root); + + if (dateString.nullish()) { + return 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 std::string& dateTimeString = dateString.getString(); + + auto tzdb = TimeZoneDatabase::get(getExpressionContext()->opCtx->getServiceContext()); + + return Value(tzdb->fromString(dateTimeString)); +} + +void ExpressionDateFromString::addDependencies(DepsTracker* deps) const { + _dateString->addDependencies(deps); +} + /* ---------------------- ExpressionDateToParts ----------------------- */ REGISTER_EXPRESSION(dateToParts, ExpressionDateToParts::parse); diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h index 0a347bb6310..fde4d7c69e7 100644 --- a/src/mongo/db/pipeline/expression.h +++ b/src/mongo/db/pipeline/expression.h @@ -828,6 +828,25 @@ private: typedef ExpressionFixedArity<ExpressionCond, 3> Base; }; +class ExpressionDateFromString final : public Expression { +public: + boost::intrusive_ptr<Expression> optimize() final; + Value serialize(bool explain) const final; + Value evaluate(const Document&) const final; + void addDependencies(DepsTracker*) const final; + + static boost::intrusive_ptr<Expression> parse( + const boost::intrusive_ptr<ExpressionContext>& expCtx, + BSONElement expr, + const VariablesParseState& vps); + +private: + ExpressionDateFromString(const boost::intrusive_ptr<ExpressionContext>& expCtx, + boost::intrusive_ptr<Expression> dateString); + + boost::intrusive_ptr<Expression> _dateString; +}; + class ExpressionDateFromParts final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp index f4066e10d3a..ae2ed802f91 100644 --- a/src/mongo/db/pipeline/expression_test.cpp +++ b/src/mongo/db/pipeline/expression_test.cpp @@ -4904,6 +4904,7 @@ TEST_F(ExpressionDateToStringTest, SerializesToObjectSyntax) { 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); } @@ -4966,9 +4967,48 @@ TEST_F(ExpressionDateToStringTest, OptimizesToConstantIfAllInputsAreConstant) { dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); ASSERT_FALSE(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get())); } - } // namespace ExpressionDateToStringTest +namespace ExpressionDateFromStringTest { + +// 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 ExpressionDateFromStringTest = AggregationContextFixture; + +TEST_F(ExpressionDateFromStringTest, SerializesToObjectSyntax) { + auto expCtx = getExpCtx(); + + // Test that it serializes to the full format if given an object specification. + BSONObj spec = BSON("$dateFromString" << BSON("dateString" + << "2017-07-04T13:06:44Z")); + auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + auto expectedSerialization = Value( + Document{{"$dateFromString", + Document{{"dateString", Document{{"$const", "2017-07-04T13:06:44Z"_sd}}}}}}); + + ASSERT_VALUE_EQ(dateExp->serialize(true), expectedSerialization); + ASSERT_VALUE_EQ(dateExp->serialize(false), expectedSerialization); +} + +TEST_F(ExpressionDateFromStringTest, OptimizesToConstantIfAllInputsAreConstant) { + auto expCtx = getExpCtx(); + auto spec = BSON("$dateFromString" << BSON("dateString" + << "2017-07-04T13:09:57Z")); + auto dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get())); + + Date_t dateVal = Date_t::fromMillisSinceEpoch(1499173797000); + ASSERT_VALUE_EQ(Value(dateVal), dateExp->evaluate(Document{})); + + // Test that it does *not* become a constant if dateString is not a constant. + spec = BSON("$dateFromString" << BSON("dateString" + << "$date")); + dateExp = Expression::parseExpression(expCtx, spec, expCtx->variablesParseState); + ASSERT_FALSE(dynamic_cast<ExpressionConstant*>(dateExp->optimize().get())); +} + +} // namespace ExpressionDateFromStringTest + class All : public Suite { public: All() : Suite("expression") {} diff --git a/src/mongo/db/query/datetime/date_time_support.cpp b/src/mongo/db/query/datetime/date_time_support.cpp index 57dcd403458..71ce5033931 100644 --- a/src/mongo/db/query/datetime/date_time_support.cpp +++ b/src/mongo/db/query/datetime/date_time_support.cpp @@ -122,6 +122,42 @@ TimeZone TimeZoneDatabase::utcZone() { return TimeZone{nullptr}; } +static timelib_tzinfo* timezonedatabase_gettzinfowrapper(char* tz_id, + const _timelib_tzdb* db, + int* error) { + return nullptr; +} + +Date_t TimeZoneDatabase::fromString(StringData dateString) const { + std::unique_ptr<timelib_time, TimeZone::TimelibTimeDeleter> t( + timelib_strtotime(const_cast<char*>(dateString.toString().c_str()), + dateString.size(), + nullptr, + _timeZoneDatabase.get(), + timezonedatabase_gettzinfowrapper)); + + // If the time portion is fully missing, initialize to 0. This allows for the '%Y-%m-%d' format + // to be passed too, which is what the BI connector may request + if (t->h == TIMELIB_UNSET && t->i == TIMELIB_UNSET && t->s == TIMELIB_UNSET) { + t->h = t->i = t->s = t->f = 0; + } + + if (t->y == TIMELIB_UNSET || t->m == TIMELIB_UNSET || t->d == TIMELIB_UNSET || + t->h == TIMELIB_UNSET || t->i == TIMELIB_UNSET || t->s == TIMELIB_UNSET) { + uasserted(40545, + str::stream() + << "an incomplete date/time string has been found, with elements missing: \"" + << dateString + << "\""); + } + + timelib_update_ts(t.get(), nullptr); + timelib_unixtime2local(t.get(), t->sse); + + return Date_t::fromMillisSinceEpoch((static_cast<double>(t->sse) + static_cast<double>(t->f)) * + 1000); +} + TimeZone TimeZoneDatabase::getTimeZone(StringData timeZoneId) const { auto tz = _timeZones.find(timeZoneId); if (tz != _timeZones.end()) { diff --git a/src/mongo/db/query/datetime/date_time_support.h b/src/mongo/db/query/datetime/date_time_support.h index 3f2768d0616..7f2d891138d 100644 --- a/src/mongo/db/query/datetime/date_time_support.h +++ b/src/mongo/db/query/datetime/date_time_support.h @@ -319,6 +319,11 @@ public: std::unique_ptr<TimeZoneDatabase> timeZoneDatabase); /** + * Use the timezone database to create a Date_t from a string. + */ + Date_t fromString(StringData dateString) const; + + /** * Returns a TimeZone object representing the UTC time zone. */ static TimeZone utcZone(); |