summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorDerick Rethans <github@derickrethans.nl>2017-06-16 16:22:47 +0100
committerDerick Rethans <github@derickrethans.nl>2017-07-13 16:01:19 +0100
commita8b0a95eda28a6fbeb87fb347ec3449ce8eb3683 (patch)
treedfba4a34279409c19818e2b5b793cfe87aadfcd5 /src/mongo
parent943361fe17e0443d6b899ba10160fb1f68742f42 (diff)
downloadmongo-a8b0a95eda28a6fbeb87fb347ec3449ce8eb3683.tar.gz
SERVER-29208 Add the $dateFromString aggregation operator
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/pipeline/expression.cpp73
-rw-r--r--src/mongo/db/pipeline/expression.h19
-rw-r--r--src/mongo/db/pipeline/expression_test.cpp42
-rw-r--r--src/mongo/db/query/datetime/date_time_support.cpp36
-rw-r--r--src/mongo/db/query/datetime/date_time_support.h5
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();