diff options
Diffstat (limited to 'src/mongo/db/pipeline/expression.cpp')
| -rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 497 |
1 files changed, 339 insertions, 158 deletions
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index bf0915deddd..ecb1f2382de 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -188,7 +188,7 @@ void Expression::registerExpression( parserMap[key] = ParserRegistration{parser, allowedWithApiStrict, allowedWithClientType, requiredMinVersion}; // Add this expression to the global map of operator counters for expressions. - operatorCountersAggExpressions.addAggExpressionCounter(key); + operatorCountersAggExpressions.addCounter(key); } intrusive_ptr<Expression> Expression::parseExpression(ExpressionContext* const expCtx, @@ -308,111 +308,204 @@ const char* ExpressionAbs::getOpName() const { /* ------------------------- ExpressionAdd ----------------------------- */ -StatusWith<Value> ExpressionAdd::apply(Value lhs, Value rhs) { - BSONType diffType = Value::getWidestNumeric(rhs.getType(), lhs.getType()); - - if (diffType == NumberDecimal) { - Decimal128 left = lhs.coerceToDecimal(); - Decimal128 right = rhs.coerceToDecimal(); - return Value(left.add(right)); - } else if (diffType == NumberDouble) { - double right = rhs.coerceToDouble(); - double left = lhs.coerceToDouble(); - return Value(left + right); - } else if (diffType == NumberLong) { - long long result; +namespace { - // If there is an overflow, convert the values to doubles. - if (overflow::add(lhs.coerceToLong(), rhs.coerceToLong(), &result)) { - return Value(lhs.coerceToDouble() + rhs.coerceToDouble()); +/** + * We'll try to return the narrowest possible result value while avoiding overflow or implicit use + * of decimal types. To do that, compute separate sums for long, double and decimal values, and + * track the current widest type. The long sum will be converted to double when the first double + * value is seen or when long arithmetic would overflow. + */ +class AddState { +public: + /** + * Update the internal state with another operand. It is up to the caller to validate that the + * operand is of a proper type. + */ + void operator+=(const Value& operand) { + auto oldWidestType = widestType; + // Dates are represented by the long number of milliseconds since the unix epoch, so we can + // treat them as regular numeric values for the purposes of addition after making sure that + // only one date is present in the operand list. + Value valToAdd; + if (operand.getType() == Date) { + uassert(16612, "only one date allowed in an $add expression", !isDate); + Value oldValue = getValue(); + longTotal = 0; + addToDateValue(oldValue); + isDate = true; + valToAdd = Value(operand.getDate().toMillisSinceEpoch()); + } else { + widestType = Value::getWidestNumeric(widestType, operand.getType()); + valToAdd = operand; } - return Value(result); - } else if (diffType == NumberInt) { - long long right = rhs.coerceToLong(); - long long left = lhs.coerceToLong(); - return Value::createIntOrLong(left + right); - } else if (lhs.nullish() || rhs.nullish()) { - return Value(BSONNULL); - } else { - return Status(ErrorCodes::TypeMismatch, - str::stream() << "cannot $add a" << typeName(rhs.getType()) << " from a " - << typeName(lhs.getType())); - } -} -Value ExpressionAdd::evaluate(const Document& root, Variables* variables) const { - // We'll try to return the narrowest possible result value while avoiding overflow, loss - // of precision due to intermediate rounding or implicit use of decimal types. To do that, - // compute a compensated sum for non-decimal values and a separate decimal sum for decimal - // values, and track the current narrowest type. - DoubleDoubleSummation nonDecimalTotal; - Decimal128 decimalTotal; - BSONType totalType = NumberInt; - bool haveDate = false; + if (isDate) { + addToDateValue(valToAdd); + return; + } - const size_t n = _children.size(); - for (size_t i = 0; i < n; ++i) { - Value val = _children[i]->evaluate(root, variables); + // If this operation widens the return type, perform any necessary type conversions. + if (oldWidestType != widestType) { + switch (widestType) { + case NumberLong: + // Int -> Long is handled by the same sum. + break; + case NumberDouble: + // Int/Long -> Double converts the existing longTotal to a doubleTotal. + doubleTotal = longTotal; + break; + case NumberDecimal: + // Convert the right total to NumberDecimal by looking at the old widest type. + switch (oldWidestType) { + case NumberInt: + case NumberLong: + decimalTotal = Decimal128(longTotal); + break; + case NumberDouble: + decimalTotal = Decimal128(doubleTotal); + break; + default: + MONGO_UNREACHABLE; + } + break; + default: + MONGO_UNREACHABLE; + } + } - switch (val.getType()) { - case NumberDecimal: - decimalTotal = decimalTotal.add(val.getDecimal()); - totalType = NumberDecimal; + // Perform the add operation. + switch (widestType) { + case NumberInt: + case NumberLong: + // If the long long arithmetic overflows, promote the result to a NumberDouble and + // start incrementing the doubleTotal. + long long newLongTotal; + if (overflow::add(longTotal, valToAdd.coerceToLong(), &newLongTotal)) { + widestType = NumberDouble; + doubleTotal = longTotal + valToAdd.coerceToDouble(); + } else { + longTotal = newLongTotal; + } break; case NumberDouble: - nonDecimalTotal.addDouble(val.getDouble()); - if (totalType != NumberDecimal) - totalType = NumberDouble; + doubleTotal += valToAdd.coerceToDouble(); break; - case NumberLong: - nonDecimalTotal.addLong(val.getLong()); - if (totalType == NumberInt) - totalType = NumberLong; + case NumberDecimal: + decimalTotal = decimalTotal.add(valToAdd.coerceToDecimal()); break; + default: + uasserted(ErrorCodes::TypeMismatch, + str::stream() << "$add only supports numeric or date types, not " + << typeName(valToAdd.getType())); + } + } + + Value getValue() const { + // If one of the operands was a date, then return long value as Date. + if (isDate) { + return Value(Date_t::fromMillisSinceEpoch(longTotal)); + } else { + switch (widestType) { + case NumberInt: + return Value::createIntOrLong(longTotal); + case NumberLong: + return Value(longTotal); + case NumberDouble: + return Value(doubleTotal); + case NumberDecimal: + return Value(decimalTotal); + default: + MONGO_UNREACHABLE; + } + } + } + +private: + // Convert 'valToAdd' into the data type used for dates (long long) and add it to 'longTotal'. + void addToDateValue(Value valToAdd) { + switch (valToAdd.getType()) { case NumberInt: - nonDecimalTotal.addDouble(val.getInt()); + case NumberLong: + if (overflow::add(longTotal, valToAdd.coerceToLong(), &longTotal)) { + uasserted(ErrorCodes::Overflow, "date overflow"); + } break; - case Date: - uassert(16612, "only one date allowed in an $add expression", !haveDate); - haveDate = true; - nonDecimalTotal.addLong(val.getDate().toMillisSinceEpoch()); + case NumberDouble: { + using limits = std::numeric_limits<long long>; + double doubleToAdd = valToAdd.coerceToDouble(); + uassert(ErrorCodes::Overflow, + "date overflow", + // The upper bound is exclusive because it rounds up when it is cast to + // a double. + doubleToAdd >= static_cast<double>(limits::min()) && + doubleToAdd < static_cast<double>(limits::max())); + + if (overflow::add(longTotal, llround(doubleToAdd), &longTotal)) { + uasserted(ErrorCodes::Overflow, "date overflow"); + } + break; + } + case NumberDecimal: { + Decimal128 decimalToAdd = valToAdd.coerceToDecimal(); + + std::uint32_t signalingFlags = Decimal128::SignalingFlag::kNoFlag; + std::int64_t longToAdd = decimalToAdd.toLong(&signalingFlags); + if (signalingFlags != Decimal128::SignalingFlag::kNoFlag || + overflow::add(longTotal, longToAdd, &longTotal)) { + uasserted(ErrorCodes::Overflow, "date overflow"); + } break; + } default: - uassert(16554, - str::stream() << "$add only supports numeric or date types, not " - << typeName(val.getType()), - val.nullish()); - return Value(BSONNULL); + MONGO_UNREACHABLE; } } - if (haveDate) { - int64_t longTotal; - if (totalType == NumberDecimal) { - longTotal = decimalTotal.add(nonDecimalTotal.getDecimal()).toLong(); - } else { - uassert(ErrorCodes::Overflow, "date overflow in $add", nonDecimalTotal.fitsLong()); - longTotal = nonDecimalTotal.getLong(); - } - return Value(Date_t::fromMillisSinceEpoch(longTotal)); + long long longTotal = 0; + double doubleTotal = 0; + Decimal128 decimalTotal; + BSONType widestType = NumberInt; + bool isDate = false; +}; + +Status checkAddOperandType(Value val) { + if (!val.numeric() && val.getType() != Date) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << "$add only supports numeric or date types, not " + << typeName(val.getType())); } - switch (totalType) { - case NumberDecimal: - return Value(decimalTotal.add(nonDecimalTotal.getDecimal())); - case NumberLong: - dassert(nonDecimalTotal.isInteger()); - if (nonDecimalTotal.fitsLong()) - return Value(nonDecimalTotal.getLong()); - // Fallthrough. - case NumberInt: - if (nonDecimalTotal.fitsLong()) - return Value::createIntOrLong(nonDecimalTotal.getLong()); - // Fallthrough. - case NumberDouble: - return Value(nonDecimalTotal.getDouble()); - default: - massert(16417, "$add resulted in a non-numeric type", false); + + return Status::OK(); +} +} // namespace + +StatusWith<Value> ExpressionAdd::apply(Value lhs, Value rhs) { + if (lhs.nullish()) + return Value(BSONNULL); + if (Status s = checkAddOperandType(lhs); !s.isOK()) + return s; + if (rhs.nullish()) + return Value(BSONNULL); + if (Status s = checkAddOperandType(rhs); !s.isOK()) + return s; + + AddState state; + state += lhs; + state += rhs; + return state.getValue(); +} + +Value ExpressionAdd::evaluate(const Document& root, Variables* variables) const { + AddState state; + for (auto&& child : _children) { + Value val = child->evaluate(root, variables); + if (val.nullish()) + return Value(BSONNULL); + uassertStatusOK(checkAddOperandType(val)); + state += val; } + return state.getValue(); } REGISTER_STABLE_EXPRESSION(add, ExpressionAdd::parse); @@ -2134,6 +2227,17 @@ void ExpressionDateDiff::_doAddDependencies(DepsTracker* deps) const { } } +monotonic::State ExpressionDateDiff::getMonotonicState(const FieldPath& sortedFieldPath) const { + if (!ExpressionConstant::allNullOrConstant({_unit, _timeZone, _startOfWeek})) { + return monotonic::State::NonMonotonic; + } + // Because the result of this expression can be negative, this works the same way as + // ExpressionSubtract. Edge cases with DST and other timezone changes are handled correctly + // according to dateDiff. + return monotonic::combine(_endDate->getMonotonicState(sortedFieldPath), + monotonic::opposite(_startDate->getMonotonicState(sortedFieldPath))); +} + /* ----------------------- ExpressionDivide ---------------------------- */ Value ExpressionDivide::evaluate(const Document& root, Variables* variables) const { @@ -2507,6 +2611,11 @@ std::unique_ptr<Expression> ExpressionFieldPath::copyWithSubstitution( return nullptr; } +monotonic::State ExpressionFieldPath::getMonotonicState(const FieldPath& sortedFieldPath) const { + return getFieldPathWithoutCurrentPrefix() == sortedFieldPath ? monotonic::State::Increasing + : monotonic::State::NonMonotonic; +} + /* ------------------------- ExpressionFilter ----------------------------- */ REGISTER_STABLE_EXPRESSION(filter, ExpressionFilter::parse); @@ -2983,9 +3092,11 @@ const std::string sortKeyName = "sortKey"; const std::string searchScoreDetailsName = "searchScoreDetails"; const std::string timeseriesBucketMinTimeName = "timeseriesBucketMinTime"; const std::string timeseriesBucketMaxTimeName = "timeseriesBucketMaxTime"; +const std::string vectorSearchScoreName = "vectorSearchScore"; using MetaType = DocumentMetadataFields::MetaType; const StringMap<DocumentMetadataFields::MetaType> kMetaNameToMetaType = { + {vectorSearchScoreName, MetaType::kVectorSearchScore}, {geoNearDistanceName, MetaType::kGeoNearDist}, {geoNearPointName, MetaType::kGeoNearPoint}, {indexKeyName, MetaType::kIndexKey}, @@ -3001,6 +3112,7 @@ const StringMap<DocumentMetadataFields::MetaType> kMetaNameToMetaType = { }; const stdx::unordered_map<DocumentMetadataFields::MetaType, StringData> kMetaTypeToMetaName = { + {MetaType::kVectorSearchScore, vectorSearchScoreName}, {MetaType::kGeoNearDist, geoNearDistanceName}, {MetaType::kGeoNearPoint, geoNearPointName}, {MetaType::kIndexKey, indexKeyName}, @@ -3044,6 +3156,9 @@ Value ExpressionMeta::serialize(bool explain) const { Value ExpressionMeta::evaluate(const Document& root, Variables* variables) const { const auto& metadata = root.metadata(); switch (_metaType) { + case MetaType::kVectorSearchScore: + return metadata.hasVectorSearchScore() ? Value(metadata.getVectorSearchScore()) + : Value(); case MetaType::kTextScore: return metadata.hasTextScore() ? Value(metadata.getTextScore()) : Value(); case MetaType::kRandVal: @@ -5535,14 +5650,45 @@ StatusWith<Value> ExpressionSubtract::apply(Value lhs, Value rhs) { } else if (lhs.nullish() || rhs.nullish()) { return Value(BSONNULL); } else if (lhs.getType() == Date) { - if (rhs.getType() == Date) { - return Value(durationCount<Milliseconds>(lhs.getDate() - rhs.getDate())); - } else if (rhs.numeric()) { - return Value(lhs.getDate() - Milliseconds(rhs.coerceToLong())); - } else { - return Status(ErrorCodes::TypeMismatch, - str::stream() - << "can't $subtract " << typeName(rhs.getType()) << " from Date"); + BSONType rhsType = rhs.getType(); + switch (rhsType) { + case Date: + return Value(durationCount<Milliseconds>(lhs.getDate() - rhs.getDate())); + case NumberInt: + case NumberLong: { + long long longDiff = lhs.getDate().toMillisSinceEpoch(); + if (overflow::sub(longDiff, rhs.coerceToLong(), &longDiff)) { + return Status(ErrorCodes::Overflow, str::stream() << "date overflow"); + } + return Value(Date_t::fromMillisSinceEpoch(longDiff)); + } + case NumberDouble: { + using limits = std::numeric_limits<long long>; + long long longDiff = lhs.getDate().toMillisSinceEpoch(); + double doubleRhs = rhs.coerceToDouble(); + // check the doubleRhs should not exceed int64 limit and result will not overflow + if (doubleRhs >= static_cast<double>(limits::min()) && + doubleRhs < static_cast<double>(limits::max()) && + !overflow::sub(longDiff, llround(doubleRhs), &longDiff)) { + return Value(Date_t::fromMillisSinceEpoch(longDiff)); + } + return Status(ErrorCodes::Overflow, str::stream() << "date overflow"); + } + case NumberDecimal: { + long long longDiff = lhs.getDate().toMillisSinceEpoch(); + Decimal128 decimalRhs = rhs.coerceToDecimal(); + std::uint32_t signalingFlags = Decimal128::SignalingFlag::kNoFlag; + std::int64_t longRhs = decimalRhs.toLong(&signalingFlags); + if (signalingFlags != Decimal128::SignalingFlag::kNoFlag || + overflow::sub(longDiff, longRhs, &longDiff)) { + return Status(ErrorCodes::Overflow, str::stream() << "date overflow"); + } + return Value(Date_t::fromMillisSinceEpoch(longDiff)); + } + default: + return Status(ErrorCodes::TypeMismatch, + str::stream() + << "can't $subtract " << typeName(rhs.getType()) << " from Date"); } } else { return Status(ErrorCodes::TypeMismatch, @@ -5556,24 +5702,35 @@ const char* ExpressionSubtract::getOpName() const { return "$subtract"; } +monotonic::State ExpressionSubtract::getMonotonicState(const FieldPath& sortedFieldPath) const { + // 1. Get monotonic states of the both children. + // 2. Apply monotonic::opposite to the state of the second child, because it is negated. + // 3. Combine children. Function monotonic::combine correctly handles all the cases where, for + // example, argumemnts are both monotonic, but in the opposite directions. + return monotonic::combine( + getChildren()[0]->getMonotonicState(sortedFieldPath), + monotonic::opposite(getChildren()[1]->getMonotonicState(sortedFieldPath))); +} + /* ------------------------- ExpressionSwitch ------------------------------ */ REGISTER_STABLE_EXPRESSION(switch, ExpressionSwitch::parse); Value ExpressionSwitch::evaluate(const Document& root, Variables* variables) const { - for (auto&& branch : _branches) { - Value caseExpression(branch.first->evaluate(root, variables)); + for (int i = 0; i < numBranches(); ++i) { + auto [caseExpr, thenExpr] = getBranch(i); + Value caseResult = caseExpr->evaluate(root, variables); - if (caseExpression.coerceToBool()) { - return branch.second->evaluate(root, variables); + if (caseResult.coerceToBool()) { + return thenExpr->evaluate(root, variables); } } uassert(40066, "$switch could not find a matching branch for an input, and no default was specified.", - _default); + defaultExpr()); - return _default->evaluate(root, variables); + return defaultExpr()->evaluate(root, variables); } boost::intrusive_ptr<Expression> ExpressionSwitch::parse(ExpressionContext* const expCtx, @@ -5582,7 +5739,7 @@ boost::intrusive_ptr<Expression> ExpressionSwitch::parse(ExpressionContext* cons uassert(40060, str::stream() << "$switch requires an object as an argument, found: " << typeName(expr.type()), - expr.type() == Object); + expr.type() == BSONType::Object); boost::intrusive_ptr<Expression> expDefault; std::vector<boost::intrusive_ptr<Expression>> children; @@ -5594,13 +5751,13 @@ boost::intrusive_ptr<Expression> ExpressionSwitch::parse(ExpressionContext* cons uassert(40061, str::stream() << "$switch expected an array for 'branches', found: " << typeName(elem.type()), - elem.type() == Array); + elem.type() == BSONType::Array); for (auto&& branch : elem.Array()) { uassert(40062, str::stream() << "$switch expected each branch to be an object, found: " << typeName(branch.type()), - branch.type() == Object); + branch.type() == BSONType::Object); boost::intrusive_ptr<Expression> switchCase, switchThen; @@ -5632,80 +5789,77 @@ boost::intrusive_ptr<Expression> ExpressionSwitch::parse(ExpressionContext* cons uasserted(40067, str::stream() << "$switch found an unknown argument: " << field); } } + + // The the 'default' expression is always the final child. If no 'default' expression is + // provided, then the final child is nullptr. children.push_back(std::move(expDefault)); - // Obtain references to the case and branch expressions two-by-two from the children vector, - // ignore the last. - std::vector<ExpressionPair> branches; - boost::optional<boost::intrusive_ptr<Expression>&> first; - for (auto&& child : children) { - if (first) { - branches.emplace_back(*first, child); - first = boost::none; - } else { - first = child; - } - } - uassert(40068, "$switch requires at least one branch.", !branches.empty()); + return new ExpressionSwitch(expCtx, std::move(children)); +} - return new ExpressionSwitch(expCtx, std::move(children), std::move(branches)); +void ExpressionSwitch::deleteBranch(int i) { + invariant(i >= 0); + invariant(i < numBranches()); + // Delete the two elements corresponding to this branch at positions 2i and 2i + 1. + _children.erase(std::next(_children.begin(), i * 2), std::next(_children.begin(), i * 2 + 2)); } void ExpressionSwitch::_doAddDependencies(DepsTracker* deps) const { - for (auto&& branch : _branches) { - branch.first->addDependencies(deps); - branch.second->addDependencies(deps); - } - - if (_default) { - _default->addDependencies(deps); + for (auto&& child : _children) { + // Check for nullptr, since we leave a nullptr as the final child when the 'default' + // expression is missing. + if (child) { + child->addDependencies(deps); + } } } boost::intrusive_ptr<Expression> ExpressionSwitch::optimize() { - if (_default) { - _default = _default->optimize(); + if (defaultExpr()) { + _children.back() = _children.back()->optimize(); } - std::vector<ExpressionPair>::iterator it = _branches.begin(); - bool true_const = false; + bool trueConst = false; - while (!true_const && it != _branches.end()) { - (it->first) = (it->first)->optimize(); + int i = 0; + while (!trueConst && i < numBranches()) { + boost::intrusive_ptr<Expression>& caseExpr = _children[i * 2]; + boost::intrusive_ptr<Expression>& thenExpr = _children[i * 2 + 1]; + caseExpr = caseExpr->optimize(); - if (auto* val = dynamic_cast<ExpressionConstant*>((it->first).get())) { - if (!((val->getValue()).coerceToBool())) { + if (auto* val = dynamic_cast<ExpressionConstant*>(caseExpr.get())) { + if (!val->getValue().coerceToBool()) { // Case is constant and evaluates to false, so it is removed. - it = _branches.erase(it); + deleteBranch(i); } else { - // Case is constant and true so it is set to default and then removed. - true_const = true; - - // Optimizing this case's then, so that default will remain optimized. - (it->second) = (it->second)->optimize(); - _default = it->second; - it = _branches.erase(it); + // Case optimized to a constant true value. Set the optimized version of the + // corresponding 'then' expression as the new 'default'. Break out of the loop and + // fall through to the logic to remove this and all subsequent branches. + trueConst = true; + _children.back() = thenExpr->optimize(); + break; } } else { // Since case is not removed from the switch, its then is now optimized. - (it->second) = (it->second)->optimize(); - ++it; + thenExpr = thenExpr->optimize(); + ++i; } } // Erasing the rest of the cases because found a default true value. - if (true_const) { - _branches.erase(it, _branches.end()); + if (trueConst) { + while (i < numBranches()) { + deleteBranch(i); + } } // If there are no cases, make the switch its default. - if (_branches.size() == 0 && _default) { - return _default; - } else if (_branches.size() == 0) { + if (numBranches() == 0) { uassert(40069, - "One cannot execute a switch statement where all the cases evaluate to false " - "without a default.", - _branches.size()); + "Cannot execute a switch statement where all the cases evaluate to false " + "without a default", + defaultExpr()); + return _children.back(); } return this; @@ -5713,17 +5867,18 @@ boost::intrusive_ptr<Expression> ExpressionSwitch::optimize() { Value ExpressionSwitch::serialize(bool explain) const { std::vector<Value> serializedBranches; - serializedBranches.reserve(_branches.size()); + serializedBranches.reserve(numBranches()); - for (auto&& branch : _branches) { - serializedBranches.push_back(Value(Document{{"case", branch.first->serialize(explain)}, - {"then", branch.second->serialize(explain)}})); + for (int i = 0; i < numBranches(); ++i) { + auto [caseExpr, thenExpr] = getBranch(i); + serializedBranches.push_back(Value(Document{{"case", caseExpr->serialize(explain)}, + {"then", thenExpr->serialize(explain)}})); } - if (_default) { + if (defaultExpr()) { return Value(Document{{"$switch", Document{{"branches", Value(serializedBranches)}, - {"default", _default->serialize(explain)}}}}); + {"default", defaultExpr()->serialize(explain)}}}}); } return Value(Document{{"$switch", Document{{"branches", Value(serializedBranches)}}}}); @@ -7551,6 +7706,15 @@ Value ExpressionDateArithmetics::evaluate(const Document& root, Variables* varia startDate.coerceToDate(), unit, amount.coerceToLong(), timezone.get()); } +monotonic::State ExpressionDateArithmetics::getMonotonicState( + const FieldPath& sortedFieldPath) const { + if (!ExpressionConstant::allNullOrConstant({_unit, _timeZone})) { + return monotonic::State::NonMonotonic; + } + return combineMonotonicStateOfArguments(_startDate->getMonotonicState(sortedFieldPath), + _amount->getMonotonicState(sortedFieldPath)); +} + /* ----------------------- ExpressionDateAdd ---------------------------- */ REGISTER_STABLE_EXPRESSION(dateAdd, ExpressionDateAdd::parse); @@ -7575,6 +7739,11 @@ Value ExpressionDateAdd::evaluateDateArithmetics(Date_t date, return Value(dateAdd(date, unit, amount, timezone)); } +monotonic::State ExpressionDateAdd::combineMonotonicStateOfArguments( + monotonic::State startDataMonotonicState, monotonic::State amountMonotonicState) const { + return monotonic::combine(startDataMonotonicState, amountMonotonicState); +} + /* ----------------------- ExpressionDateSubtract ---------------------------- */ REGISTER_STABLE_EXPRESSION(dateSubtract, ExpressionDateSubtract::parse); @@ -7604,6 +7773,11 @@ Value ExpressionDateSubtract::evaluateDateArithmetics(Date_t date, return Value(dateAdd(date, unit, -amount, timezone)); } +monotonic::State ExpressionDateSubtract::combineMonotonicStateOfArguments( + monotonic::State startDataMonotonicState, monotonic::State amountMonotonicState) const { + return monotonic::combine(startDataMonotonicState, amountMonotonicState); +} + /* ----------------------- ExpressionDateTrunc ---------------------------- */ REGISTER_STABLE_EXPRESSION(dateTrunc, ExpressionDateTrunc::parse); @@ -7776,6 +7950,13 @@ void ExpressionDateTrunc::_doAddDependencies(DepsTracker* deps) const { } } +monotonic::State ExpressionDateTrunc::getMonotonicState(const FieldPath& sortedFieldPath) const { + if (!ExpressionConstant::allNullOrConstant({_unit, _binSize, _timeZone, _startOfWeek})) { + return monotonic::State::NonMonotonic; + } + return _date->getMonotonicState(sortedFieldPath); +} + /* -------------------------- ExpressionGetField ------------------------------ */ REGISTER_EXPRESSION_WITH_MIN_VERSION( getField, |
