summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline/expression.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/pipeline/expression.cpp')
-rw-r--r--src/mongo/db/pipeline/expression.cpp497
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,