diff options
author | Charlie Swanson <charlie.swanson@mongodb.com> | 2017-07-07 10:28:54 -0400 |
---|---|---|
committer | Charlie Swanson <charlie.swanson@mongodb.com> | 2017-07-07 10:28:54 -0400 |
commit | 56c8b7660fd85c02b5a8ade15e1dfdb49cbfea46 (patch) | |
tree | 1b5251064b4b2cb913a4ba045697e0145887374a /src/mongo/db/pipeline/expression.h | |
parent | 2b198988a626df6f36c9e606b08b266c462cceb4 (diff) | |
download | mongo-56c8b7660fd85c02b5a8ade15e1dfdb49cbfea46.tar.gz |
ERVER-28611 Add timezone argument for date expressions
Diffstat (limited to 'src/mongo/db/pipeline/expression.h')
-rw-r--r-- | src/mongo/db/pipeline/expression.h | 346 |
1 files changed, 261 insertions, 85 deletions
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h index 85451aeefcf..e14e451964d 100644 --- a/src/mongo/db/pipeline/expression.h +++ b/src/mongo/db/pipeline/expression.h @@ -30,6 +30,7 @@ #include "mongo/platform/basic.h" +#include <algorithm> #include <boost/intrusive_ptr.hpp> #include <map> #include <string> @@ -411,6 +412,200 @@ public: virtual Value evaluateNumericArg(const Value& numericArg) const = 0; }; +/** + * A constant expression. Repeated calls to evaluate() will always return the same thing. + */ +class ExpressionConstant final : public Expression { +public: + boost::intrusive_ptr<Expression> optimize() final; + void addDependencies(DepsTracker* deps) const final; + Value evaluate(const Document& root) const final; + Value serialize(bool explain) const final; + + const char* getOpName() const; + + /** + * Creates a new ExpressionConstant with value 'value'. + */ + static boost::intrusive_ptr<ExpressionConstant> create( + const boost::intrusive_ptr<ExpressionContext>& expCtx, const Value& value); + + static boost::intrusive_ptr<Expression> parse( + const boost::intrusive_ptr<ExpressionContext>& expCtx, + BSONElement bsonExpr, + const VariablesParseState& vps); + + /** + * Returns true if 'expression' is nullptr or if 'expression' is an instance of an + * ExpressionConstant. + */ + static bool isNullOrConstant(boost::intrusive_ptr<Expression> expression) { + return !expression || dynamic_cast<ExpressionConstant*>(expression.get()); + } + + /** + * Returns true if every expression in 'expressions' is either a nullptr or an instance of an + * ExpressionConstant. + */ + static bool allNullOrConstant( + const std::initializer_list<boost::intrusive_ptr<Expression>>& expressions) { + return std::all_of(expressions.begin(), expressions.end(), [](auto exp) { + return ExpressionConstant::isNullOrConstant(exp); + }); + } + + /** + * Returns the constant value represented by this Expression. + */ + Value getValue() const { + return _value; + } + +private: + ExpressionConstant(const boost::intrusive_ptr<ExpressionContext>& expCtx, const Value& value); + + Value _value; +}; + +/** + * Inherit from this class if your expression works with date types, and accepts either a single + * argument which is a date, or an object {date: <date>, timezone: <string>}. + */ +template <typename SubClass> +class DateExpressionAcceptingTimeZone : public Expression { +public: + virtual ~DateExpressionAcceptingTimeZone() {} + + Value evaluate(const Document& root) const final { + auto dateVal = _date->evaluate(root); + if (dateVal.nullish()) { + return Value(BSONNULL); + } + auto date = dateVal.coerceToDate(); + + if (!_timeZone) { + return evaluateDate(date, TimeZoneDatabase::utcZone()); + } + auto timeZoneId = _timeZone->evaluate(root); + if (timeZoneId.nullish()) { + return Value(BSONNULL); + } + + uassert(40533, + str::stream() << _opName + << " requires a string for the timezone argument, but was given a " + << typeName(timeZoneId.getType()) + << " (" + << timeZoneId.toString() + << ")", + timeZoneId.getType() == BSONType::String); + auto timeZone = TimeZoneDatabase::get(getExpressionContext()->opCtx->getServiceContext()) + ->getTimeZone(timeZoneId.getString()); + return evaluateDate(date, timeZone); + } + + void addDependencies(DepsTracker* deps) const final { + _date->addDependencies(deps); + if (_timeZone) { + _timeZone->addDependencies(deps); + } + } + + /** + * Always serializes to the full {date: <date arg>, timezone: <timezone arg>} format, leaving + * off the timezone if not specified. + */ + Value serialize(bool explain) const final { + return Value(Document{ + {_opName, + Document{{"date", _date->serialize(explain)}, + {"timezone", _timeZone ? _timeZone->serialize(explain) : Value()}}}}); + } + + boost::intrusive_ptr<Expression> optimize() final { + _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; + } + + static boost::intrusive_ptr<Expression> parse( + const boost::intrusive_ptr<ExpressionContext>& expCtx, + BSONElement operatorElem, + const VariablesParseState& variablesParseState) { + boost::intrusive_ptr<DateExpressionAcceptingTimeZone> dateExpression{new SubClass(expCtx)}; + if (operatorElem.type() == BSONType::Object) { + if (operatorElem.embeddedObject().firstElementFieldName()[0] == '$') { + // Assume this is an expression specification representing the date argument + // like {$add: [<date>, 1000]}. + dateExpression->_date = Expression::parseObject( + expCtx, operatorElem.embeddedObject(), variablesParseState); + return dateExpression; + } else { + // It's an object specifying the date and timezone options like {date: <date>, + // timezone: <timezone>}. + for (const auto& subElem : operatorElem.embeddedObject()) { + auto argName = subElem.fieldNameStringData(); + if (argName == "date"_sd) { + dateExpression->_date = + Expression::parseOperand(expCtx, subElem, variablesParseState); + } else if (argName == "timezone"_sd) { + dateExpression->_timeZone = + Expression::parseOperand(expCtx, subElem, variablesParseState); + } else { + auto opName = operatorElem.fieldNameStringData(); + uasserted(40535, + str::stream() << "unrecognized option to " << opName << ": \"" + << argName + << "\""); + } + } + } + return dateExpression; + } else if (operatorElem.type() == BSONType::Array) { + auto elems = operatorElem.Array(); + uassert( + 40536, + str::stream() << dateExpression->_opName + << " accepts exactly one argument if given an array, but was given " + << elems.size(), + elems.size() == 1); + // We accept an argument wrapped in a single array. For example, either {$week: <date>} + // or {$week: [<date>]} are valid, but not {$week: [{date: <date>}]}. + operatorElem = elems[0]; + } + // It's some literal value, or the first element of a user-specified array. Either way it + // should be treated as the date argument. + dateExpression->_date = Expression::parseOperand(expCtx, operatorElem, variablesParseState); + return dateExpression; + } + +protected: + explicit DateExpressionAcceptingTimeZone(StringData opName, + const boost::intrusive_ptr<ExpressionContext>& expCtx) + : Expression(expCtx), _opName(opName) {} + + /** + * Subclasses should implement this to do their actual date-related logic. Uses 'timezone' to + * evaluate the expression against 'data'. If the user did not specify a time zone, 'timezone' + * will represent the UTC zone. + */ + virtual Value evaluateDate(Date_t date, const TimeZone& timezone) const = 0; + +private: + // The name of this expression, e.g. $week or $month. + StringData _opName; + + // The expression representing the date argument. + boost::intrusive_ptr<Expression> _date; + // The expression representing the timezone argument, nullptr if not specified. + boost::intrusive_ptr<Expression> _timeZone = nullptr; +}; class ExpressionAbs final : public ExpressionSingleNumericArg<ExpressionAbs> { public: @@ -629,39 +824,6 @@ private: typedef ExpressionFixedArity<ExpressionCond, 3> Base; }; - -class ExpressionConstant final : public Expression { -public: - boost::intrusive_ptr<Expression> optimize() final; - void addDependencies(DepsTracker* deps) const final; - Value evaluate(const Document& root) const final; - Value serialize(bool explain) const final; - - const char* getOpName() const; - - static boost::intrusive_ptr<ExpressionConstant> create( - const boost::intrusive_ptr<ExpressionContext>& expCtx, const Value& pValue); - - static boost::intrusive_ptr<Expression> parse( - const boost::intrusive_ptr<ExpressionContext>& expCtx, - BSONElement bsonExpr, - const VariablesParseState& vps); - - /* - Get the constant value represented by this Expression. - - @returns the value - */ - Value getValue() const { - return pValue; - } - -private: - ExpressionConstant(const boost::intrusive_ptr<ExpressionContext>& expCtx, const Value& pValue); - - Value pValue; -}; - class ExpressionDateFromParts final : public Expression { public: boost::intrusive_ptr<Expression> optimize() final; @@ -762,33 +924,36 @@ private: boost::intrusive_ptr<Expression> _date; }; -class ExpressionDayOfMonth final : public ExpressionFixedArity<ExpressionDayOfMonth, 1> { +class ExpressionDayOfMonth final : public DateExpressionAcceptingTimeZone<ExpressionDayOfMonth> { public: explicit ExpressionDayOfMonth(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionDayOfMonth, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionDayOfMonth>("$dayOfMonth", expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.dateParts(date).dayOfMonth); + } }; -class ExpressionDayOfWeek final : public ExpressionFixedArity<ExpressionDayOfWeek, 1> { +class ExpressionDayOfWeek final : public DateExpressionAcceptingTimeZone<ExpressionDayOfWeek> { public: explicit ExpressionDayOfWeek(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionDayOfWeek, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionDayOfWeek>("$dayOfWeek", expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.dayOfWeek(date)); + } }; -class ExpressionDayOfYear final : public ExpressionFixedArity<ExpressionDayOfYear, 1> { +class ExpressionDayOfYear final : public DateExpressionAcceptingTimeZone<ExpressionDayOfYear> { public: explicit ExpressionDayOfYear(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionDayOfYear, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionDayOfYear>("$dayOfYear", expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.dayOfYear(date)); + } }; @@ -916,13 +1081,14 @@ public: }; -class ExpressionHour final : public ExpressionFixedArity<ExpressionHour, 1> { +class ExpressionHour final : public DateExpressionAcceptingTimeZone<ExpressionHour> { public: explicit ExpressionHour(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionHour, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionHour>("$hour", expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.dateParts(date).hour); + } }; @@ -1089,23 +1255,25 @@ private: MetaType _metaType; }; -class ExpressionMillisecond final : public ExpressionFixedArity<ExpressionMillisecond, 1> { +class ExpressionMillisecond final : public DateExpressionAcceptingTimeZone<ExpressionMillisecond> { public: explicit ExpressionMillisecond(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionMillisecond, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionMillisecond>("$millisecond", expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.dateParts(date).millisecond); + } }; -class ExpressionMinute final : public ExpressionFixedArity<ExpressionMinute, 1> { +class ExpressionMinute final : public DateExpressionAcceptingTimeZone<ExpressionMinute> { public: explicit ExpressionMinute(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionMinute, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionMinute>("$minute", expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.dateParts(date).minute); + } }; @@ -1137,13 +1305,14 @@ public: }; -class ExpressionMonth final : public ExpressionFixedArity<ExpressionMonth, 1> { +class ExpressionMonth final : public DateExpressionAcceptingTimeZone<ExpressionMonth> { public: explicit ExpressionMonth(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionMonth, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionMonth>("$month", expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.dateParts(date).month); + } }; @@ -1272,13 +1441,14 @@ private: }; -class ExpressionSecond final : public ExpressionFixedArity<ExpressionSecond, 1> { +class ExpressionSecond final : public DateExpressionAcceptingTimeZone<ExpressionSecond> { public: explicit ExpressionSecond(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionSecond, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionSecond>("$second", expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.dateParts(date).second); + } }; @@ -1537,53 +1707,59 @@ public: }; -class ExpressionWeek final : public ExpressionFixedArity<ExpressionWeek, 1> { +class ExpressionWeek final : public DateExpressionAcceptingTimeZone<ExpressionWeek> { public: explicit ExpressionWeek(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionWeek, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionWeek>("$week"_sd, expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.week(date)); + } }; -class ExpressionIsoWeekYear final : public ExpressionFixedArity<ExpressionIsoWeekYear, 1> { +class ExpressionIsoWeekYear final : public DateExpressionAcceptingTimeZone<ExpressionIsoWeekYear> { public: explicit ExpressionIsoWeekYear(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionIsoWeekYear, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionIsoWeekYear>("$isoWeekYear"_sd, expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.isoYear(date)); + } }; -class ExpressionIsoDayOfWeek final : public ExpressionFixedArity<ExpressionIsoDayOfWeek, 1> { +class ExpressionIsoDayOfWeek final + : public DateExpressionAcceptingTimeZone<ExpressionIsoDayOfWeek> { public: explicit ExpressionIsoDayOfWeek(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionIsoDayOfWeek, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionIsoDayOfWeek>("$isoDayOfWeek"_sd, expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.isoDayOfWeek(date)); + } }; -class ExpressionIsoWeek final : public ExpressionFixedArity<ExpressionIsoWeek, 1> { +class ExpressionIsoWeek final : public DateExpressionAcceptingTimeZone<ExpressionIsoWeek> { public: explicit ExpressionIsoWeek(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionIsoWeek, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionIsoWeek>("$isoWeek", expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.isoWeek(date)); + } }; -class ExpressionYear final : public ExpressionFixedArity<ExpressionYear, 1> { +class ExpressionYear final : public DateExpressionAcceptingTimeZone<ExpressionYear> { public: explicit ExpressionYear(const boost::intrusive_ptr<ExpressionContext>& expCtx) - : ExpressionFixedArity<ExpressionYear, 1>(expCtx) {} + : DateExpressionAcceptingTimeZone<ExpressionYear>("$year", expCtx) {} - Value evaluate(const Document& root) const final; - const char* getOpName() const final; + Value evaluateDate(Date_t date, const TimeZone& timeZone) const final { + return Value(timeZone.dateParts(date).year); + } }; |