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.cpp440
1 files changed, 276 insertions, 164 deletions
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index 2d4c9a61aaa..dc2aeeb7e0e 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -72,13 +72,25 @@ using std::pair;
using std::string;
using std::vector;
-/// Helper function to easily wrap constants with $const.
-static Value serializeConstant(Value val) {
+Value ExpressionConstant::serializeConstant(const SerializationOptions& opts,
+ Value val,
+ bool wrapRepresentativeValue) {
if (val.missing()) {
return Value("$$REMOVE"_sd);
}
+ // It's safer to wrap constants in $const when generating representative shapes to avoid
+ // ambiguity when re-parsing (SERVER-88296, SERVER-85376). However, we allow certain expressions
+ // to override this behavior in order to reduce shape verbosity if the expression takes many
+ // constant arguments (e.g. variadic expressions - SERVER-84159).
+ // Debug shapes never wrap constants in $const to reduce shape size (and because re-parsing
+ // support is not a consideration there).
+ if ((opts.literalPolicy == LiteralSerializationPolicy::kUnchanged) ||
+ (wrapRepresentativeValue &&
+ opts.literalPolicy == LiteralSerializationPolicy::kToRepresentativeParseableValue)) {
+ return Value(DOC("$const" << opts.serializeLiteral(val)));
+ }
- return Value(DOC("$const" << val));
+ return opts.serializeLiteral(val);
}
/* --------------------------- Expression ------------------------------ */
@@ -643,11 +655,16 @@ Value ExpressionArray::evaluate(const Document& root, Variables* variables) cons
return Value(std::move(values));
}
-Value ExpressionArray::serialize(bool explain) const {
+Value ExpressionArray::serialize(const SerializationOptions& options) const {
+ if (options.literalPolicy != LiteralSerializationPolicy::kUnchanged &&
+ selfAndChildrenAreConstant()) {
+ return ExpressionConstant::serializeConstant(
+ options, evaluate(Document{}, &(getExpressionContext()->variables)));
+ }
vector<Value> expressions;
expressions.reserve(_children.size());
for (auto&& expr : _children) {
- expressions.push_back(expr->serialize(explain));
+ expressions.push_back(expr->serialize(options));
}
return Value(std::move(expressions));
}
@@ -670,6 +687,15 @@ intrusive_ptr<Expression> ExpressionArray::optimize() {
return this;
}
+bool ExpressionArray::selfAndChildrenAreConstant() const {
+ for (auto&& exprPointer : _children) {
+ if (!exprPointer->selfAndChildrenAreConstant()) {
+ return false;
+ }
+ }
+ return true;
+}
+
const char* ExpressionArray::getOpName() const {
// This should never be called, but is needed to inherit from ExpressionNary.
return "$array";
@@ -970,11 +996,11 @@ Value ExpressionCoerceToBool::evaluate(const Document& root, Variables* variable
return Value(false);
}
-Value ExpressionCoerceToBool::serialize(bool explain) const {
+Value ExpressionCoerceToBool::serialize(const SerializationOptions& options) const {
// When not explaining, serialize to an $and expression. When parsed, the $and expression
// will be optimized back into a ExpressionCoerceToBool.
- const char* name = explain ? "$coerceToBool" : "$and";
- return Value(DOC(name << DOC_ARRAY(pExpression->serialize(explain))));
+ const char* name = options.verbosity ? "$coerceToBool" : "$and";
+ return Value(DOC(name << DOC_ARRAY(pExpression->serialize(options))));
}
/* ----------------------- ExpressionCompare --------------------------- */
@@ -1223,8 +1249,8 @@ Value ExpressionConstant::evaluate(const Document& root, Variables* variables) c
return _value;
}
-Value ExpressionConstant::serialize(bool explain) const {
- return serializeConstant(_value);
+Value ExpressionConstant::serialize(const SerializationOptions& options) const {
+ return ExpressionConstant::serializeConstant(options, _value);
}
REGISTER_STABLE_EXPRESSION(const, ExpressionConstant::parse);
@@ -1438,20 +1464,20 @@ intrusive_ptr<Expression> ExpressionDateFromParts::optimize() {
return this;
}
-Value ExpressionDateFromParts::serialize(bool explain) const {
+Value ExpressionDateFromParts::serialize(const SerializationOptions& options) const {
return Value(Document{
{"$dateFromParts",
- Document{{"year", _year ? _year->serialize(explain) : Value()},
- {"month", _month ? _month->serialize(explain) : Value()},
- {"day", _day ? _day->serialize(explain) : Value()},
- {"hour", _hour ? _hour->serialize(explain) : Value()},
- {"minute", _minute ? _minute->serialize(explain) : Value()},
- {"second", _second ? _second->serialize(explain) : Value()},
- {"millisecond", _millisecond ? _millisecond->serialize(explain) : Value()},
- {"isoWeekYear", _isoWeekYear ? _isoWeekYear->serialize(explain) : Value()},
- {"isoWeek", _isoWeek ? _isoWeek->serialize(explain) : Value()},
- {"isoDayOfWeek", _isoDayOfWeek ? _isoDayOfWeek->serialize(explain) : Value()},
- {"timezone", _timeZone ? _timeZone->serialize(explain) : Value()}}}});
+ Document{{"year", _year ? _year->serialize(options) : Value()},
+ {"month", _month ? _month->serialize(options) : Value()},
+ {"day", _day ? _day->serialize(options) : Value()},
+ {"hour", _hour ? _hour->serialize(options) : Value()},
+ {"minute", _minute ? _minute->serialize(options) : Value()},
+ {"second", _second ? _second->serialize(options) : Value()},
+ {"millisecond", _millisecond ? _millisecond->serialize(options) : Value()},
+ {"isoWeekYear", _isoWeekYear ? _isoWeekYear->serialize(options) : Value()},
+ {"isoWeek", _isoWeek ? _isoWeek->serialize(options) : Value()},
+ {"isoDayOfWeek", _isoDayOfWeek ? _isoDayOfWeek->serialize(options) : Value()},
+ {"timezone", _timeZone ? _timeZone->serialize(options) : Value()}}}});
}
bool ExpressionDateFromParts::evaluateNumberWithDefault(const Document& root,
@@ -1695,14 +1721,14 @@ intrusive_ptr<Expression> ExpressionDateFromString::optimize() {
return this;
}
-Value ExpressionDateFromString::serialize(bool explain) const {
+Value ExpressionDateFromString::serialize(const SerializationOptions& options) const {
return Value(
Document{{"$dateFromString",
- Document{{"dateString", _dateString->serialize(explain)},
- {"timezone", _timeZone ? _timeZone->serialize(explain) : Value()},
- {"format", _format ? _format->serialize(explain) : Value()},
- {"onNull", _onNull ? _onNull->serialize(explain) : Value()},
- {"onError", _onError ? _onError->serialize(explain) : Value()}}}});
+ Document{{"dateString", _dateString->serialize(options)},
+ {"timezone", _timeZone ? _timeZone->serialize(options) : Value()},
+ {"format", _format ? _format->serialize(options) : Value()},
+ {"onNull", _onNull ? _onNull->serialize(options) : Value()},
+ {"onError", _onError ? _onError->serialize(options) : Value()}}}});
}
Value ExpressionDateFromString::evaluate(const Document& root, Variables* variables) const {
@@ -1853,12 +1879,12 @@ intrusive_ptr<Expression> ExpressionDateToParts::optimize() {
return this;
}
-Value ExpressionDateToParts::serialize(bool explain) const {
+Value ExpressionDateToParts::serialize(const SerializationOptions& options) const {
return Value(
Document{{"$dateToParts",
- Document{{"date", _date->serialize(explain)},
- {"timezone", _timeZone ? _timeZone->serialize(explain) : Value()},
- {"iso8601", _iso8601 ? _iso8601->serialize(explain) : Value()}}}});
+ Document{{"date", _date->serialize(options)},
+ {"timezone", _timeZone ? _timeZone->serialize(options) : Value()},
+ {"iso8601", _iso8601 ? _iso8601->serialize(options) : Value()}}}});
}
boost::optional<int> ExpressionDateToParts::evaluateIso8601Flag(const Document& root,
@@ -2011,13 +2037,13 @@ intrusive_ptr<Expression> ExpressionDateToString::optimize() {
return this;
}
-Value ExpressionDateToString::serialize(bool explain) const {
+Value ExpressionDateToString::serialize(const SerializationOptions& options) const {
return Value(
Document{{"$dateToString",
- Document{{"date", _date->serialize(explain)},
- {"format", _format ? _format->serialize(explain) : Value()},
- {"timezone", _timeZone ? _timeZone->serialize(explain) : Value()},
- {"onNull", _onNull ? _onNull->serialize(explain) : Value()}}}});
+ Document{{"date", _date->serialize(options)},
+ {"format", _format ? _format->serialize(options) : Value()},
+ {"timezone", _timeZone ? _timeZone->serialize(options) : Value()},
+ {"onNull", _onNull ? _onNull->serialize(options) : Value()}}}});
}
Value ExpressionDateToString::evaluate(const Document& root, Variables* variables) const {
@@ -2158,14 +2184,14 @@ boost::intrusive_ptr<Expression> ExpressionDateDiff::optimize() {
return this;
};
-Value ExpressionDateDiff::serialize(bool explain) const {
+Value ExpressionDateDiff::serialize(const SerializationOptions& options) const {
return Value{Document{
{"$dateDiff"_sd,
- Document{{"startDate"_sd, _startDate->serialize(explain)},
- {"endDate"_sd, _endDate->serialize(explain)},
- {"unit"_sd, _unit->serialize(explain)},
- {"timezone"_sd, _timeZone ? _timeZone->serialize(explain) : Value{}},
- {"startOfWeek"_sd, _startOfWeek ? _startOfWeek->serialize(explain) : Value{}}}}}};
+ Document{{"startDate"_sd, _startDate->serialize(options)},
+ {"endDate"_sd, _endDate->serialize(options)},
+ {"unit"_sd, _unit->serialize(options)},
+ {"timezone"_sd, _timeZone ? _timeZone->serialize(options) : Value{}},
+ {"startOfWeek"_sd, _startOfWeek ? _startOfWeek->serialize(options) : Value{}}}}}};
};
Date_t ExpressionDateDiff::convertToDate(const Value& value, StringData parameterName) {
@@ -2379,10 +2405,24 @@ Value ExpressionObject::evaluate(const Document& root, Variables* variables) con
return outputDoc.freezeToValue();
}
-Value ExpressionObject::serialize(bool explain) const {
+bool ExpressionObject::selfAndChildrenAreConstant() const {
+ for (auto&& [_, exprPointer] : _expressions) {
+ if (!exprPointer->selfAndChildrenAreConstant()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+Value ExpressionObject::serialize(const SerializationOptions& options) const {
+ if (options.literalPolicy != LiteralSerializationPolicy::kUnchanged &&
+ selfAndChildrenAreConstant()) {
+ return ExpressionConstant::serializeConstant(options, Value(Document{}));
+ }
MutableDocument outputDoc;
for (auto&& pair : _expressions) {
- outputDoc.addField(pair.first, pair.second->serialize(explain));
+ outputDoc.addField(options.serializeFieldPathFromString(pair.first),
+ pair.second->serialize(options));
}
return outputDoc.freezeToValue();
}
@@ -2557,14 +2597,34 @@ Value ExpressionFieldPath::evaluate(const Document& root, Variables* variables)
}
}
-Value ExpressionFieldPath::serialize(bool explain) const {
- if (_fieldPath.getFieldName(0) == "CURRENT" && _fieldPath.getPathLength() > 1) {
+namespace {
+// Shared among expressions that need to serialize dotted paths and redact the path components.
+auto getPrefixAndPath(FieldPath path) {
+ if (path.getFieldName(0) == "CURRENT" && path.getPathLength() > 1) {
// use short form for "$$CURRENT.foo" but not just "$$CURRENT"
- return Value("$" + _fieldPath.tail().fullPath());
+ return std::make_pair(std::string("$"), path.tail());
} else {
- return Value("$$" + _fieldPath.fullPath());
+ return std::make_pair(std::string("$$"), path);
}
}
+} // namespace
+
+Value ExpressionFieldPath::serialize(const SerializationOptions& options) const {
+ auto [prefix, path] = getPrefixAndPath(_fieldPath);
+ // First handles special cases for redaction of system variables. User variables will fall
+ // through to the default full redaction case.
+ if (options.transformIdentifiers && prefix.length() == 2) {
+ if (path.getPathLength() == 1 && Variables::isBuiltin(_variable)) {
+ // Nothing to redact for builtin variables.
+ return Value(prefix + path.fullPath());
+ } else if (path.getPathLength() > 1 && Variables::isBuiltin(_variable)) {
+ // The first component of this path is a system variable, so keep that and redact
+ // the rest.
+ return Value(prefix + path.front() + "." + options.serializeFieldPath(path.tail()));
+ }
+ }
+ return Value(prefix + options.serializeFieldPath(path));
+}
Expression::ComputedPaths ExpressionFieldPath::getComputedPaths(const std::string& exprFieldPath,
Variables::Id renamingVar) const {
@@ -2705,14 +2765,14 @@ intrusive_ptr<Expression> ExpressionFilter::optimize() {
return this;
}
-Value ExpressionFilter::serialize(bool explain) const {
+Value ExpressionFilter::serialize(const SerializationOptions& options) const {
if (_limit) {
- return Value(DOC("$filter" << DOC("input" << _input->serialize(explain) << "as" << _varName
- << "cond" << _cond->serialize(explain) << "limit"
- << (*_limit)->serialize(explain))));
+ return Value(DOC("$filter" << DOC("input" << _input->serialize(options) << "as" << _varName
+ << "cond" << _cond->serialize(options) << "limit"
+ << (*_limit)->serialize(options))));
}
- return Value(DOC("$filter" << DOC("input" << _input->serialize(explain) << "as" << _varName
- << "cond" << _cond->serialize(explain))));
+ return Value(DOC("$filter" << DOC("input" << _input->serialize(options) << "as" << _varName
+ << "cond" << _cond->serialize(options))));
}
Value ExpressionFilter::evaluate(const Document& root, Variables* variables) const {
@@ -2904,15 +2964,19 @@ intrusive_ptr<Expression> ExpressionLet::optimize() {
return this;
}
-Value ExpressionLet::serialize(bool explain) const {
+Value ExpressionLet::serialize(const SerializationOptions& options) const {
MutableDocument vars;
for (VariableMap::const_iterator it = _variables.begin(), end = _variables.end(); it != end;
++it) {
- vars[it->second.name] = it->second.expression->serialize(explain);
+ auto key = it->second.name;
+ if (options.transformIdentifiers) {
+ key = options.transformIdentifiersCallback(key);
+ }
+ vars[key] = it->second.expression->serialize(options);
}
return Value(
- DOC("$let" << DOC("vars" << vars.freeze() << "in" << _subExpression->serialize(explain))));
+ DOC("$let" << DOC("vars" << vars.freeze() << "in" << _subExpression->serialize(options))));
}
Value ExpressionLet::evaluate(const Document& root, Variables* variables) const {
@@ -3006,9 +3070,9 @@ intrusive_ptr<Expression> ExpressionMap::optimize() {
return this;
}
-Value ExpressionMap::serialize(bool explain) const {
- return Value(DOC("$map" << DOC("input" << _input->serialize(explain) << "as" << _varName << "in"
- << _each->serialize(explain))));
+Value ExpressionMap::serialize(const SerializationOptions& options) const {
+ return Value(DOC("$map" << DOC("input" << _input->serialize(options) << "as" << _varName << "in"
+ << _each->serialize(options))));
}
Value ExpressionMap::evaluate(const Document& root, Variables* variables) const {
@@ -3150,7 +3214,7 @@ ExpressionMeta::ExpressionMeta(ExpressionContext* const expCtx, MetaType metaTyp
expCtx->sbeCompatible = false;
}
-Value ExpressionMeta::serialize(bool explain) const {
+Value ExpressionMeta::serialize(const SerializationOptions& options) const {
const auto nameIter = kMetaTypeToMetaName.find(_metaType);
invariant(nameIter != kMetaTypeToMetaName.end());
return Value(DOC("$meta" << nameIter->second));
@@ -3786,7 +3850,7 @@ Value ExpressionLn::evaluateNumericArg(const Value& numericArg) const {
if (numericArg.getType() == NumberDecimal) {
Decimal128 argDecimal = numericArg.getDecimal();
if (argDecimal.isGreater(Decimal128::kNormalizedZero))
- return Value(argDecimal.logarithm());
+ return Value(argDecimal.naturalLogarithm());
// Fall through for error case.
}
double argDouble = numericArg.coerceToDouble();
@@ -3940,9 +4004,9 @@ Value toValue(const std::array<std::uint8_t, 32>& buf) {
return Value(BSONBinData(vec.data(), vec.size(), BinDataType::Encrypt));
}
-Value ExpressionInternalFLEEqual::serialize(bool explain) const {
+Value ExpressionInternalFLEEqual::serialize(const SerializationOptions& options) const {
return Value(Document{{kInternalFleEq,
- Document{{"field", _children[0]->serialize(explain)},
+ Document{{"field", _children[0]->serialize(options)},
{"edc", toValue(_edcToken)},
{"counter", Value(static_cast<long long>(_contentionFactor))},
{"server", toValue(_serverToken)}}}});
@@ -4097,13 +4161,22 @@ void ExpressionNary::addOperand(const intrusive_ptr<Expression>& pExpression) {
_children.push_back(pExpression);
}
-Value ExpressionNary::serialize(bool explain) const {
+Value ExpressionNary::serialize(const SerializationOptions& options) const {
const size_t nOperand = _children.size();
vector<Value> array;
/* build up the array */
- for (size_t i = 0; i < nOperand; i++)
- array.push_back(_children[i]->serialize(explain));
-
+ for (size_t i = 0; i < nOperand; i++) {
+ // If this input is a constant, bypass the standard serialization that wraps the
+ // representative value in $const. This does not lead to ambiguity for variadic operators
+ // but avoids bloating the representative shape for operators that have many inputs.
+ ExpressionConstant const* exprConst = dynamic_cast<ExpressionConstant*>(_children[i].get());
+ if (exprConst) {
+ array.push_back(exprConst->serializeConstant(
+ options, exprConst->getValue(), false /* wrapRepresentativeValue */));
+ } else {
+ array.push_back(_children[i]->serialize(options));
+ }
+ }
return Value(DOC(getOpName() << array));
}
@@ -4568,11 +4641,11 @@ void ExpressionReduce::_doAddDependencies(DepsTracker* deps) const {
_in->addDependencies(deps);
}
-Value ExpressionReduce::serialize(bool explain) const {
+Value ExpressionReduce::serialize(const SerializationOptions& options) const {
return Value(Document{{"$reduce",
- Document{{"input", _input->serialize(explain)},
- {"initialValue", _initial->serialize(explain)},
- {"in", _in->serialize(explain)}}}});
+ Document{{"input", _input->serialize(options)},
+ {"initialValue", _initial->serialize(options)},
+ {"in", _in->serialize(options)}}}});
}
/* ------------------------ ExpressionReplaceBase ------------------------ */
@@ -4583,11 +4656,11 @@ void ExpressionReplaceBase::_doAddDependencies(DepsTracker* deps) const {
_replacement->addDependencies(deps);
}
-Value ExpressionReplaceBase::serialize(bool explain) const {
+Value ExpressionReplaceBase::serialize(const SerializationOptions& options) const {
return Value(Document{{getOpName(),
- Document{{"input", _input->serialize(explain)},
- {"find", _find->serialize(explain)},
- {"replacement", _replacement->serialize(explain)}}}});
+ Document{{"input", _input->serialize(options)},
+ {"find", _find->serialize(options)},
+ {"replacement", _replacement->serialize(options)}}}});
}
namespace {
@@ -4880,9 +4953,9 @@ void ExpressionSortArray::_doAddDependencies(DepsTracker* deps) const {
_input->addDependencies(deps);
}
-Value ExpressionSortArray::serialize(bool explain) const {
+Value ExpressionSortArray::serialize(const SerializationOptions& options) const {
return Value(Document{{kName,
- Document{{"input", _input->serialize(explain)},
+ Document{{"input", _input->serialize(options)},
{"sortBy", _sortBy.getOriginalElement()}}}});
}
@@ -5871,20 +5944,20 @@ boost::intrusive_ptr<Expression> ExpressionSwitch::optimize() {
return this;
}
-Value ExpressionSwitch::serialize(bool explain) const {
+Value ExpressionSwitch::serialize(const SerializationOptions& options) const {
std::vector<Value> serializedBranches;
serializedBranches.reserve(numBranches());
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)}}));
+ serializedBranches.push_back(Value(Document{{"case", caseExpr->serialize(options)},
+ {"then", thenExpr->serialize(options)}}));
}
if (defaultExpr()) {
return Value(Document{{"$switch",
Document{{"branches", Value(serializedBranches)},
- {"default", defaultExpr()->serialize(explain)}}}});
+ {"default", defaultExpr()->serialize(options)}}}});
}
return Value(Document{{"$switch", Document{{"branches", Value(serializedBranches)}}}});
@@ -6126,11 +6199,11 @@ boost::intrusive_ptr<Expression> ExpressionTrim::optimize() {
return this;
}
-Value ExpressionTrim::serialize(bool explain) const {
+Value ExpressionTrim::serialize(const SerializationOptions& options) const {
return Value(
Document{{_name,
- Document{{"input", _input->serialize(explain)},
- {"chars", _characters ? _characters->serialize(explain) : Value()}}}});
+ Document{{"input", _input->serialize(options)},
+ {"chars", _characters ? _characters->serialize(options) : Value()}}}});
}
void ExpressionTrim::_doAddDependencies(DepsTracker* deps) const {
@@ -6428,17 +6501,17 @@ boost::intrusive_ptr<Expression> ExpressionZip::optimize() {
return this;
}
-Value ExpressionZip::serialize(bool explain) const {
+Value ExpressionZip::serialize(const SerializationOptions& options) const {
vector<Value> serializedInput;
vector<Value> serializedDefaults;
Value serializedUseLongestLength = Value(_useLongestLength);
for (auto&& expr : _inputs) {
- serializedInput.push_back(expr.get()->serialize(explain));
+ serializedInput.push_back(expr.get()->serialize(options));
}
for (auto&& expr : _defaults) {
- serializedDefaults.push_back(expr.get()->serialize(explain));
+ serializedDefaults.push_back(expr.get()->serialize(options));
}
return Value(DOC("$zip" << DOC("inputs" << Value(serializedInput) << "defaults"
@@ -7019,14 +7092,6 @@ boost::intrusive_ptr<Expression> ExpressionConvert::optimize() {
return this;
}
-Value ExpressionConvert::serialize(bool explain) const {
- return Value(Document{{"$convert",
- Document{{"input", _input->serialize(explain)},
- {"to", _to->serialize(explain)},
- {"onError", _onError ? _onError->serialize(explain) : Value()},
- {"onNull", _onNull ? _onNull->serialize(explain) : Value()}}}});
-}
-
void ExpressionConvert::_doAddDependencies(DepsTracker* deps) const {
_input->addDependencies(deps);
_to->addDependencies(deps);
@@ -7038,6 +7103,29 @@ void ExpressionConvert::_doAddDependencies(DepsTracker* deps) const {
}
}
+Value ExpressionConvert::serialize(const SerializationOptions& options) const {
+ // Since the 'to' field is a parameter from a set of valid values and not free user input,
+ // we want to avoid boiling it down to the representative value in the query shape. The first
+ // condition is so that we can keep serializing correctly whenever the 'to' field is an
+ // expression that gets resolved down to a string of a valid type, or its corresponding
+ // numerical value. If it's just the constant, we want to wrap it in a $const except when the
+ // serialization policy is debug.
+ auto constExpr = dynamic_cast<ExpressionConstant*>(_to.get());
+ Value toField = Value();
+ if (!constExpr) {
+ toField = _to->serialize(options);
+ } else if (options.literalPolicy == LiteralSerializationPolicy::kToDebugTypeString) {
+ toField = constExpr->getValue();
+ } else {
+ toField = Value(DOC("$const" << constExpr->getValue()));
+ }
+ return Value(Document{{"$convert",
+ Document{{"input", _input->serialize(options)},
+ {"to", toField},
+ {"onError", _onError ? _onError->serialize(options) : Value()},
+ {"onNull", _onNull ? _onNull->serialize(options) : Value()}}}});
+}
+
BSONType ExpressionConvert::computeTargetType(Value targetTypeName) const {
BSONType targetType;
if (targetTypeName.getType() == BSONType::String) {
@@ -7300,12 +7388,12 @@ void ExpressionRegex::_compile(RegexExecutionState* executionState) const {
executionState->capturesBuffer.resize((1 + executionState->numCaptures) * 3);
}
-Value ExpressionRegex::serialize(bool explain) const {
+Value ExpressionRegex::serialize(const SerializationOptions& options) const {
return Value(
Document{{_opName,
- Document{{"input", _input->serialize(explain)},
- {"regex", _regex->serialize(explain)},
- {"options", _options ? _options->serialize(explain) : Value()}}}});
+ Document{{"input", _input->serialize(options)},
+ {"regex", _regex->serialize(options)},
+ {"options", _options ? _options->serialize(options) : Value()}}}});
}
void ExpressionRegex::_extractInputField(RegexExecutionState* executionState,
@@ -7568,7 +7656,7 @@ void ExpressionRandom::_doAddDependencies(DepsTracker* deps) const {
deps->needRandomGenerator = true;
}
-Value ExpressionRandom::serialize(const bool explain) const {
+Value ExpressionRandom::serialize(const SerializationOptions& options) const {
return Value(DOC(getOpName() << Document()));
}
@@ -7591,8 +7679,8 @@ Value ExpressionToHashedIndexKey::evaluate(const Document& root, Variables* vari
BSONElementHasher::DEFAULT_HASH_SEED));
}
-Value ExpressionToHashedIndexKey::serialize(bool explain) const {
- return Value(DOC("$toHashedIndexKey" << _children[0]->serialize(explain)));
+Value ExpressionToHashedIndexKey::serialize(const SerializationOptions& options) const {
+ return Value(DOC("$toHashedIndexKey" << _children[0]->serialize(options)));
}
void ExpressionToHashedIndexKey::_doAddDependencies(DepsTracker* deps) const {
@@ -7668,13 +7756,13 @@ boost::intrusive_ptr<Expression> ExpressionDateArithmetics::optimize() {
return intrusive_ptr<Expression>(this);
}
-Value ExpressionDateArithmetics::serialize(bool explain) const {
+Value ExpressionDateArithmetics::serialize(const SerializationOptions& options) const {
return Value(
Document{{_opName,
- Document{{"startDate", _startDate->serialize(explain)},
- {"unit", _unit->serialize(explain)},
- {"amount", _amount->serialize(explain)},
- {"timezone", _timeZone ? _timeZone->serialize(explain) : Value()}}}});
+ Document{{"startDate", _startDate->serialize(options)},
+ {"unit", _unit->serialize(options)},
+ {"amount", _amount->serialize(options)},
+ {"timezone", _timeZone ? _timeZone->serialize(options) : Value()}}}});
}
Value ExpressionDateArithmetics::evaluate(const Document& root, Variables* variables) const {
@@ -7867,14 +7955,14 @@ boost::intrusive_ptr<Expression> ExpressionDateTrunc::optimize() {
return this;
};
-Value ExpressionDateTrunc::serialize(bool explain) const {
+Value ExpressionDateTrunc::serialize(const SerializationOptions& options) const {
return Value{Document{
{"$dateTrunc"_sd,
- Document{{"date"_sd, _date->serialize(explain)},
- {"unit"_sd, _unit->serialize(explain)},
- {"binSize"_sd, _binSize ? _binSize->serialize(explain) : Value{}},
- {"timezone"_sd, _timeZone ? _timeZone->serialize(explain) : Value{}},
- {"startOfWeek"_sd, _startOfWeek ? _startOfWeek->serialize(explain) : Value{}}}}}};
+ Document{{"date"_sd, _date->serialize(options)},
+ {"unit"_sd, _unit->serialize(options)},
+ {"binSize"_sd, _binSize ? _binSize->serialize(options) : Value{}},
+ {"timezone"_sd, _timeZone ? _timeZone->serialize(options) : Value{}},
+ {"startOfWeek"_sd, _startOfWeek ? _startOfWeek->serialize(options) : Value{}}}}}};
};
Date_t ExpressionDateTrunc::convertToDate(const Value& value) {
@@ -8056,7 +8144,6 @@ Value ExpressionGetField::evaluate(const Document& root, Variables* variables) c
return Value();
}
-
return inputValue.getDocument().getField(fieldValue.getString());
}
@@ -8069,10 +8156,22 @@ void ExpressionGetField::_doAddDependencies(DepsTracker* deps) const {
_field->addDependencies(deps);
}
-Value ExpressionGetField::serialize(const bool explain) const {
+Value ExpressionGetField::serialize(const SerializationOptions& options) const {
+ // The parser guarantees that the '_field' expression evaluates to a constant string.
+ auto strPath = static_cast<ExpressionConstant*>(_field.get())->getValue().getString();
+
+ Value maybeRedactedPath{options.serializeFieldPathFromString(strPath)};
+ // This is a pretty unique option to serialize. It is both a constant and a field path, which
+ // means that it:
+ // - should be redacted (if that option is set).
+ // - should *not* be wrapped in $const iff we are serializing for a debug string
+ if (options.literalPolicy != LiteralSerializationPolicy::kToDebugTypeString) {
+ maybeRedactedPath = Value(Document{{"$const"_sd, maybeRedactedPath}});
+ }
+
return Value(Document{{"$getField"_sd,
- Document{{"field"_sd, _field->serialize(explain)},
- {"input"_sd, _input->serialize(explain)}}}});
+ Document{{"field"_sd, std::move(maybeRedactedPath)},
+ {"input"_sd, _input->serialize(options)}}}});
}
/* -------------------------- ExpressionSetField ------------------------------ */
@@ -8129,46 +8228,11 @@ intrusive_ptr<Expression> ExpressionSetField::parse(ExpressionContext* const exp
uassert(4161103, str::stream() << name << " requires 'value' to be specified", valueExpr);
uassert(4161109, str::stream() << name << " requires 'input' to be specified", inputExpr);
- // The 'field' argument to '$setField' must evaluate to a constant string, for example,
- // {$const: "$a.b"}. In case the user has forgotten to wrap the value into a '$const' or
- // '$literal' expression, we will raise an error with a more meaningful description.
- if (auto fieldPathExpr = dynamic_cast<ExpressionFieldPath*>(fieldExpr.get()); fieldPathExpr) {
- auto fp = fieldPathExpr->getFieldPath().fullPathWithPrefix();
- uasserted(4161108,
- str::stream() << "'" << fp
- << "' is a field path reference which is not allowed "
- "in this context. Did you mean {$literal: '"
- << fp << "'}?");
- }
-
- auto constFieldExpr = dynamic_cast<ExpressionConstant*>(fieldExpr.get());
- uassert(4161106,
- str::stream() << name
- << " requires 'field' to evaluate to a constant, "
- "but got a non-constant argument",
- constFieldExpr);
- uassert(4161107,
- str::stream() << name
- << " requires 'field' to evaluate to type String, "
- "but got "
- << typeName(constFieldExpr->getValue().getType()),
- constFieldExpr->getValue().getType() == BSONType::String);
-
-
- return make_intrusive<ExpressionSetField>(expCtx, fieldExpr, inputExpr, valueExpr);
+ return make_intrusive<ExpressionSetField>(
+ expCtx, std::move(fieldExpr), std::move(inputExpr), std::move(valueExpr));
}
Value ExpressionSetField::evaluate(const Document& root, Variables* variables) const {
- auto field = _field->evaluate(root, variables);
-
- // The parser guarantees that the '_field' expression evaluates to a constant string.
- tassert(4161104,
- str::stream() << kExpressionName
- << " requires 'field' to evaluate to type String, "
- "but got "
- << typeName(field.getType()),
- field.getType() == BSONType::String);
-
auto input = _input->evaluate(root, variables);
if (input.nullish()) {
return Value(BSONNULL);
@@ -8182,7 +8246,7 @@ Value ExpressionSetField::evaluate(const Document& root, Variables* variables) c
// Build output document and modify 'field'.
MutableDocument outputDoc(input.getDocument());
- outputDoc.setField(field.getString(), value);
+ outputDoc.setField(_fieldName, value);
return outputDoc.freezeToValue();
}
@@ -8196,11 +8260,59 @@ void ExpressionSetField::_doAddDependencies(DepsTracker* deps) const {
_value->addDependencies(deps);
}
-Value ExpressionSetField::serialize(const bool explain) const {
+Value ExpressionSetField::serialize(const SerializationOptions& options) const {
+ // The parser guarantees that the '_field' expression evaluates to a constant string.
+ auto strPath = static_cast<ExpressionConstant*>(_field.get())->getValue().getString();
+
+ Value maybeRedactedPath{options.serializeFieldPathFromString(strPath)};
+ // This is a pretty unique option to serialize. It is both a constant and a field path, which
+ // means that it:
+ // - should be redacted (if that option is set).
+ // - should *not* be wrapped in $const iff we are serializing for a debug string
+ if (options.literalPolicy != LiteralSerializationPolicy::kToDebugTypeString) {
+ maybeRedactedPath = Value(Document{{"$const"_sd, maybeRedactedPath}});
+ }
+
return Value(Document{{"$setField"_sd,
- Document{{"field"_sd, _field->serialize(explain)},
- {"input"_sd, _input->serialize(explain)},
- {"value"_sd, _value->serialize(explain)}}}});
+ Document{{"field"_sd, std::move(maybeRedactedPath)},
+ {"input"_sd, _input->serialize(options)},
+ {"value"_sd, _value->serialize(options)}}}});
+}
+
+std::string ExpressionSetField::getValidFieldName(boost::intrusive_ptr<Expression> fieldExpr) {
+ tassert(9534701,
+ str::stream() << kExpressionName << " requires 'field' to be specified",
+ fieldExpr);
+
+ // The 'field' argument to '$setField' must evaluate to a constant string, for example,
+ // {$const: "$a.b"}. In case the user has forgotten to wrap the value into a '$const' or
+ // '$literal' expression, we will raise an error with a more meaningful description.
+ if (auto fieldPathExpr = dynamic_cast<ExpressionFieldPath*>(fieldExpr.get()); fieldPathExpr) {
+ auto fp = fieldPathExpr->getFieldPath().fullPathWithPrefix();
+ uasserted(4161108,
+ str::stream() << "'" << fp
+ << "' is a field path reference which is not allowed "
+ "in this context. Did you mean {$literal: '"
+ << fp << "'}?");
+ }
+
+ auto constFieldExpr = dynamic_cast<ExpressionConstant*>(fieldExpr.get());
+ uassert(4161106,
+ str::stream() << kExpressionName
+ << " requires 'field' to evaluate to a constant, "
+ "but got a non-constant argument",
+ constFieldExpr);
+ uassert(4161107,
+ str::stream() << kExpressionName
+ << " requires 'field' to evaluate to type String, "
+ "but got "
+ << typeName(constFieldExpr->getValue().getType()),
+ constFieldExpr->getValue().getType() == BSONType::String);
+ uassert(9534700,
+ str::stream() << kExpressionName << ": 'field' cannot contain an embedded null byte",
+ constFieldExpr->getValue().getStringData().find('\0') == std::string::npos);
+
+ return constFieldExpr->getValue().getString();
}
/* ------------------------- ExpressionTsSecond ----------------------------- */
@@ -8289,12 +8401,12 @@ boost::intrusive_ptr<Expression> ExpressionInternalKeyStringValue::parse(
return make_intrusive<ExpressionInternalKeyStringValue>(expCtx, inputExpr, collationExpr);
}
-Value ExpressionInternalKeyStringValue::serialize(bool explain) const {
+Value ExpressionInternalKeyStringValue::serialize(const SerializationOptions& options) const {
return Value(
Document{{getOpName(),
- Document{{"input", _children[_kInput]->serialize(explain)},
+ Document{{"input", _children[_kInput]->serialize(options)},
{"collation",
- _children[_kCollation] ? _children[_kCollation]->serialize(explain)
+ _children[_kCollation] ? _children[_kCollation]->serialize(options)
: Value()}}}});
}