From 9355b6a325e2606e41198caa07ded85e68a5b353 Mon Sep 17 00:00:00 2001 From: Ted Tuckman Date: Tue, 14 Feb 2023 16:26:06 +0000 Subject: SERVER-73324 Literal and field name redaction for ExpressionFieldPath and ExpressionConstant --- src/mongo/db/pipeline/accumulator_multi.cpp | 6 +- src/mongo/db/pipeline/accumulator_multi.h | 2 +- src/mongo/db/pipeline/expression.cpp | 255 +++++++++++---------- src/mongo/db/pipeline/expression.h | 75 +++--- .../db/pipeline/expression_field_path_test.cpp | 65 ++++++ src/mongo/db/pipeline/expression_find_internal.h | 6 +- src/mongo/db/pipeline/expression_function.cpp | 4 +- src/mongo/db/pipeline/expression_function.h | 2 +- src/mongo/db/pipeline/expression_js_emit.cpp | 4 +- src/mongo/db/pipeline/expression_js_emit.h | 2 +- src/mongo/db/pipeline/expression_test.cpp | 25 ++ .../db/pipeline/expression_test_api_version.cpp | 2 +- .../db/pipeline/expression_test_api_version.h | 2 +- src/mongo/db/query/serialization_options.h | 22 +- src/mongo/s/commands/sharding_expressions.cpp | 4 +- src/mongo/s/commands/sharding_expressions.h | 2 +- 16 files changed, 304 insertions(+), 174 deletions(-) diff --git a/src/mongo/db/pipeline/accumulator_multi.cpp b/src/mongo/db/pipeline/accumulator_multi.cpp index 5a478e7e7d5..c8b33426cb8 100644 --- a/src/mongo/db/pipeline/accumulator_multi.cpp +++ b/src/mongo/db/pipeline/accumulator_multi.cpp @@ -158,10 +158,10 @@ void AccumulatorN::updateAndCheckMemUsage(size_t memAdded) { void AccumulatorN::serializeHelper(const boost::intrusive_ptr& initializer, const boost::intrusive_ptr& argument, - bool explain, + SerializationOptions options, MutableDocument& md) { - md.addField(kFieldNameN, Value(initializer->serialize(explain))); - md.addField(kFieldNameInput, Value(argument->serialize(explain))); + md.addField(kFieldNameN, Value(initializer->serialize(options))); + md.addField(kFieldNameInput, Value(argument->serialize(options))); } template diff --git a/src/mongo/db/pipeline/accumulator_multi.h b/src/mongo/db/pipeline/accumulator_multi.h index 1118a9421ff..f7d5e6b68bb 100644 --- a/src/mongo/db/pipeline/accumulator_multi.h +++ b/src/mongo/db/pipeline/accumulator_multi.h @@ -85,7 +85,7 @@ public: */ static void serializeHelper(const boost::intrusive_ptr& initializer, const boost::intrusive_ptr& argument, - bool explain, + SerializationOptions options, MutableDocument& md); protected: diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 3a5e4107e27..8981a1c6731 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -639,11 +639,11 @@ Value ExpressionArray::evaluate(const Document& root, Variables* variables) cons return Value(std::move(values)); } -Value ExpressionArray::serialize(bool explain) const { +Value ExpressionArray::serialize(SerializationOptions options) const { vector 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)); } @@ -962,11 +962,11 @@ Value ExpressionCoerceToBool::evaluate(const Document& root, Variables* variable return Value(false); } -Value ExpressionCoerceToBool::serialize(bool explain) const { +Value ExpressionCoerceToBool::serialize(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(_children[_kExpression]->serialize(explain)))); + const char* name = options.explain ? "$coerceToBool" : "$and"; + return Value(DOC(name << DOC_ARRAY(_children[_kExpression]->serialize(options)))); } /* ----------------------- ExpressionCompare --------------------------- */ @@ -1211,7 +1211,10 @@ Value ExpressionConstant::evaluate(const Document& root, Variables* variables) c return _value; } -Value ExpressionConstant::serialize(bool explain) const { +Value ExpressionConstant::serialize(SerializationOptions options) const { + if (options.replacementForLiteralArgs) { + return Value(DOC("$const" << *options.replacementForLiteralArgs)); + } return serializeConstant(_value); } @@ -1424,25 +1427,25 @@ intrusive_ptr ExpressionDateFromParts::optimize() { return this; } -Value ExpressionDateFromParts::serialize(bool explain) const { +Value ExpressionDateFromParts::serialize(SerializationOptions options) const { return Value(Document{ {"$dateFromParts", Document{ - {"year", _children[_kYear] ? _children[_kYear]->serialize(explain) : Value()}, - {"month", _children[_kMonth] ? _children[_kMonth]->serialize(explain) : Value()}, - {"day", _children[_kDay] ? _children[_kDay]->serialize(explain) : Value()}, - {"hour", _children[_kHour] ? _children[_kHour]->serialize(explain) : Value()}, - {"minute", _children[_kMinute] ? _children[_kMinute]->serialize(explain) : Value()}, - {"second", _children[_kSecond] ? _children[_kSecond]->serialize(explain) : Value()}, + {"year", _children[_kYear] ? _children[_kYear]->serialize(options) : Value()}, + {"month", _children[_kMonth] ? _children[_kMonth]->serialize(options) : Value()}, + {"day", _children[_kDay] ? _children[_kDay]->serialize(options) : Value()}, + {"hour", _children[_kHour] ? _children[_kHour]->serialize(options) : Value()}, + {"minute", _children[_kMinute] ? _children[_kMinute]->serialize(options) : Value()}, + {"second", _children[_kSecond] ? _children[_kSecond]->serialize(options) : Value()}, {"millisecond", - _children[_kMillisecond] ? _children[_kMillisecond]->serialize(explain) : Value()}, + _children[_kMillisecond] ? _children[_kMillisecond]->serialize(options) : Value()}, {"isoWeekYear", - _children[_kIsoWeekYear] ? _children[_kIsoWeekYear]->serialize(explain) : Value()}, - {"isoWeek", _children[_kIsoWeek] ? _children[_kIsoWeek]->serialize(explain) : Value()}, + _children[_kIsoWeekYear] ? _children[_kIsoWeekYear]->serialize(options) : Value()}, + {"isoWeek", _children[_kIsoWeek] ? _children[_kIsoWeek]->serialize(options) : Value()}, {"isoDayOfWeek", - _children[_kIsoDayOfWeek] ? _children[_kIsoDayOfWeek]->serialize(explain) : Value()}, + _children[_kIsoDayOfWeek] ? _children[_kIsoDayOfWeek]->serialize(options) : Value()}, {"timezone", - _children[_kTimeZone] ? _children[_kTimeZone]->serialize(explain) : Value()}}}}); + _children[_kTimeZone] ? _children[_kTimeZone]->serialize(options) : Value()}}}}); } bool ExpressionDateFromParts::evaluateNumberWithDefault(const Document& root, @@ -1668,17 +1671,17 @@ intrusive_ptr ExpressionDateFromString::optimize() { return this; } -Value ExpressionDateFromString::serialize(bool explain) const { +Value ExpressionDateFromString::serialize(SerializationOptions options) const { return Value(Document{ {"$dateFromString", Document{ - {"dateString", _children[_kDateString]->serialize(explain)}, + {"dateString", _children[_kDateString]->serialize(options)}, {"timezone", - _children[_kTimeZone] ? _children[_kTimeZone]->serialize(explain) : Value()}, - {"format", _children[_kFormat] ? _children[_kFormat]->serialize(explain) : Value()}, - {"onNull", _children[_kOnNull] ? _children[_kOnNull]->serialize(explain) : Value()}, + _children[_kTimeZone] ? _children[_kTimeZone]->serialize(options) : Value()}, + {"format", _children[_kFormat] ? _children[_kFormat]->serialize(options) : Value()}, + {"onNull", _children[_kOnNull] ? _children[_kOnNull]->serialize(options) : Value()}, {"onError", - _children[_kOnError] ? _children[_kOnError]->serialize(explain) : Value()}}}}); + _children[_kOnError] ? _children[_kOnError]->serialize(options) : Value()}}}}); } Value ExpressionDateFromString::evaluate(const Document& root, Variables* variables) const { @@ -1821,14 +1824,14 @@ intrusive_ptr ExpressionDateToParts::optimize() { return this; } -Value ExpressionDateToParts::serialize(bool explain) const { +Value ExpressionDateToParts::serialize(SerializationOptions options) const { return Value(Document{ {"$dateToParts", - Document{{"date", _children[_kDate]->serialize(explain)}, + Document{{"date", _children[_kDate]->serialize(options)}, {"timezone", - _children[_kTimeZone] ? _children[_kTimeZone]->serialize(explain) : Value()}, + _children[_kTimeZone] ? _children[_kTimeZone]->serialize(options) : Value()}, {"iso8601", - _children[_kIso8601] ? _children[_kIso8601]->serialize(explain) : Value()}}}}); + _children[_kIso8601] ? _children[_kIso8601]->serialize(options) : Value()}}}}); } boost::optional ExpressionDateToParts::evaluateIso8601Flag(const Document& root, @@ -1976,16 +1979,16 @@ intrusive_ptr ExpressionDateToString::optimize() { return this; } -Value ExpressionDateToString::serialize(bool explain) const { +Value ExpressionDateToString::serialize(SerializationOptions options) const { return Value(Document{ {"$dateToString", Document{ - {"date", _children[_kDate]->serialize(explain)}, - {"format", _children[_kFormat] ? _children[_kFormat]->serialize(explain) : Value()}, + {"date", _children[_kDate]->serialize(options)}, + {"format", _children[_kFormat] ? _children[_kFormat]->serialize(options) : Value()}, {"timezone", - _children[_kTimeZone] ? _children[_kTimeZone]->serialize(explain) : Value()}, + _children[_kTimeZone] ? _children[_kTimeZone]->serialize(options) : Value()}, {"onNull", - _children[_kOnNull] ? _children[_kOnNull]->serialize(explain) : Value()}}}}); + _children[_kOnNull] ? _children[_kOnNull]->serialize(options) : Value()}}}}); } Value ExpressionDateToString::evaluate(const Document& root, Variables* variables) const { @@ -2142,16 +2145,16 @@ boost::intrusive_ptr ExpressionDateDiff::optimize() { return this; }; -Value ExpressionDateDiff::serialize(bool explain) const { +Value ExpressionDateDiff::serialize(SerializationOptions options) const { return Value{Document{ {"$dateDiff"_sd, - Document{{"startDate"_sd, _children[_kStartDate]->serialize(explain)}, - {"endDate"_sd, _children[_kEndDate]->serialize(explain)}, - {"unit"_sd, _children[_kUnit]->serialize(explain)}, + Document{{"startDate"_sd, _children[_kStartDate]->serialize(options)}, + {"endDate"_sd, _children[_kEndDate]->serialize(options)}, + {"unit"_sd, _children[_kUnit]->serialize(options)}, {"timezone"_sd, - _children[_kTimeZone] ? _children[_kTimeZone]->serialize(explain) : Value{}}, + _children[_kTimeZone] ? _children[_kTimeZone]->serialize(options) : Value{}}, {"startOfWeek"_sd, - _children[_kStartOfWeek] ? _children[_kStartOfWeek]->serialize(explain) + _children[_kStartOfWeek] ? _children[_kStartOfWeek]->serialize(options) : Value{}}}}}}; }; @@ -2365,10 +2368,10 @@ Value ExpressionObject::evaluate(const Document& root, Variables* variables) con return outputDoc.freezeToValue(); } -Value ExpressionObject::serialize(bool explain) const { +Value ExpressionObject::serialize(SerializationOptions options) const { MutableDocument outputDoc; for (auto&& pair : _expressions) { - outputDoc.addField(pair.first, pair.second->serialize(explain)); + outputDoc.addField(pair.first, pair.second->serialize(options)); } return outputDoc.freezeToValue(); } @@ -2531,12 +2534,34 @@ Value ExpressionFieldPath::evaluate(const Document& root, Variables* variables) } } -Value ExpressionFieldPath::serialize(bool explain) const { - if (_fieldPath.getFieldName(0) == "CURRENT" && _fieldPath.getPathLength() > 1) { - // use short form for "$$CURRENT.foo" but not just "$$CURRENT" - return Value("$" + _fieldPath.tail().fullPath()); +Value ExpressionFieldPath::serialize(SerializationOptions options) const { + auto&& [prefix, path] = [&]() { + if (_fieldPath.getFieldName(0) == "CURRENT" && _fieldPath.getPathLength() > 1) { + // use short form for "$$CURRENT.foo" but not just "$$CURRENT" + return std::make_pair(std::string("$"), _fieldPath.tail()); + } else { + return std::make_pair(std::string("$$"), _fieldPath); + } + }(); + if (options.redactFieldNames) { + std::stringstream redacted; + redacted << prefix; + size_t startPos = 0; + // Check if our prefix indicates this path begins with a system variable. + if (prefix.length() == 2) { + // Don't redact a variable reference. + redacted << path.getFieldName(0); + ++startPos; + } + for (size_t i = startPos; i < path.getPathLength(); ++i) { + if (i > 0) { + redacted << "."; + } + redacted << options.redactFieldNamesStrategy(path.getFieldName(i)); + } + return Value(redacted.str()); } else { - return Value("$$" + _fieldPath.fullPath()); + return Value(prefix + path.fullPath()); } } @@ -2677,16 +2702,16 @@ intrusive_ptr ExpressionFilter::optimize() { return this; } -Value ExpressionFilter::serialize(bool explain) const { +Value ExpressionFilter::serialize(SerializationOptions options) const { if (_limit) { return Value(DOC( - "$filter" << DOC("input" << _children[_kInput]->serialize(explain) << "as" << _varName - << "cond" << _children[_kCond]->serialize(explain) << "limit" - << (_children[*_limit])->serialize(explain)))); + "$filter" << DOC("input" << _children[_kInput]->serialize(options) << "as" << _varName + << "cond" << _children[_kCond]->serialize(options) << "limit" + << (_children[*_limit])->serialize(options)))); } return Value( - DOC("$filter" << DOC("input" << _children[_kInput]->serialize(explain) << "as" << _varName - << "cond" << _children[_kCond]->serialize(explain)))); + DOC("$filter" << DOC("input" << _children[_kInput]->serialize(options) << "as" << _varName + << "cond" << _children[_kCond]->serialize(options)))); } Value ExpressionFilter::evaluate(const Document& root, Variables* variables) const { @@ -2870,15 +2895,15 @@ intrusive_ptr ExpressionLet::optimize() { return this; } -Value ExpressionLet::serialize(bool explain) const { +Value ExpressionLet::serialize(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); + vars[it->second.name] = it->second.expression->serialize(options); } return Value(DOC("$let" << DOC("vars" << vars.freeze() << "in" - << _children[_kSubExpression]->serialize(explain)))); + << _children[_kSubExpression]->serialize(options)))); } Value ExpressionLet::evaluate(const Document& root, Variables* variables) const { @@ -2958,10 +2983,10 @@ intrusive_ptr ExpressionMap::optimize() { return this; } -Value ExpressionMap::serialize(bool explain) const { +Value ExpressionMap::serialize(SerializationOptions options) const { return Value( - DOC("$map" << DOC("input" << _children[_kInput]->serialize(explain) << "as" << _varName - << "in" << _children[_kEach]->serialize(explain)))); + DOC("$map" << DOC("input" << _children[_kInput]->serialize(options) << "as" << _varName + << "in" << _children[_kEach]->serialize(options)))); } Value ExpressionMap::evaluate(const Document& root, Variables* variables) const { @@ -3109,7 +3134,7 @@ ExpressionMeta::ExpressionMeta(ExpressionContext* const expCtx, MetaType metaTyp expCtx->sbeCompatible = false; } -Value ExpressionMeta::serialize(bool explain) const { +Value ExpressionMeta::serialize(SerializationOptions options) const { const auto nameIter = kMetaTypeToMetaName.find(_metaType); invariant(nameIter != kMetaTypeToMetaName.end()); return Value(DOC("$meta" << nameIter->second)); @@ -3859,10 +3884,10 @@ Value toValue(const std::array& buf) { return Value(BSONBinData(vec.data(), vec.size(), BinDataType::Encrypt)); } -Value ExpressionInternalFLEEqual::serialize(bool explain) const { +Value ExpressionInternalFLEEqual::serialize(SerializationOptions options) const { return Value(Document{ {kInternalFleEq, - Document{{"field", _children[0]->serialize(explain)}, + Document{{"field", _children[0]->serialize(options)}, {"edc", toValue(_evaluator.edcTokens()[0])}, {"counter", Value(static_cast(_evaluator.contentionFactor()))}, {"server", toValue(_evaluator.serverToken())}}}}); @@ -3935,7 +3960,7 @@ intrusive_ptr ExpressionInternalFLEBetween::parse(ExpressionContext* expCtx, std::move(fieldExpr), serverTokenPair.second, cf, edcTokens); } -Value ExpressionInternalFLEBetween::serialize(bool explain) const { +Value ExpressionInternalFLEBetween::serialize(SerializationOptions options) const { std::vector edcValues; edcValues.reserve(_evaluator.edcTokens().size()); for (auto& token : _evaluator.edcTokens()) { @@ -3943,7 +3968,7 @@ Value ExpressionInternalFLEBetween::serialize(bool explain) const { } return Value(Document{ {kInternalFleBetween, - Document{{"field", _children[0]->serialize(explain)}, + Document{{"field", _children[0]->serialize(options)}, {"edc", Value(edcValues)}, {"counter", Value(static_cast(_evaluator.contentionFactor()))}, {"server", toValue(_evaluator.serverToken())}}}}); @@ -4097,12 +4122,12 @@ void ExpressionNary::addOperand(const intrusive_ptr& pExpression) { _children.push_back(pExpression); } -Value ExpressionNary::serialize(bool explain) const { +Value ExpressionNary::serialize(SerializationOptions options) const { const size_t nOperand = _children.size(); vector array; /* build up the array */ for (size_t i = 0; i < nOperand; i++) - array.push_back(_children[i]->serialize(explain)); + array.push_back(_children[i]->serialize(options)); return Value(DOC(getOpName() << array)); } @@ -4562,21 +4587,21 @@ intrusive_ptr ExpressionReduce::optimize() { return this; } -Value ExpressionReduce::serialize(bool explain) const { +Value ExpressionReduce::serialize(SerializationOptions options) const { return Value(Document{{"$reduce", - Document{{"input", _children[_kInput]->serialize(explain)}, - {"initialValue", _children[_kInitial]->serialize(explain)}, - {"in", _children[_kIn]->serialize(explain)}}}}); + Document{{"input", _children[_kInput]->serialize(options)}, + {"initialValue", _children[_kInitial]->serialize(options)}, + {"in", _children[_kIn]->serialize(options)}}}}); } /* ------------------------ ExpressionReplaceBase ------------------------ */ -Value ExpressionReplaceBase::serialize(bool explain) const { +Value ExpressionReplaceBase::serialize(SerializationOptions options) const { return Value( Document{{getOpName(), - Document{{"input", _children[_kInput]->serialize(explain)}, - {"find", _children[_kFind]->serialize(explain)}, - {"replacement", _children[_kReplacement]->serialize(explain)}}}}); + Document{{"input", _children[_kInput]->serialize(options)}, + {"find", _children[_kFind]->serialize(options)}, + {"replacement", _children[_kReplacement]->serialize(options)}}}}); } namespace { @@ -4860,9 +4885,9 @@ intrusive_ptr ExpressionSortArray::optimize() { return this; } -Value ExpressionSortArray::serialize(bool explain) const { +Value ExpressionSortArray::serialize(SerializationOptions options) const { return Value(Document{{kName, - Document{{"input", _children[_kInput]->serialize(explain)}, + Document{{"input", _children[_kInput]->serialize(options)}, {"sortBy", _sortBy.getOriginalElement()}}}}); } @@ -5807,20 +5832,20 @@ boost::intrusive_ptr ExpressionSwitch::optimize() { return this; } -Value ExpressionSwitch::serialize(bool explain) const { +Value ExpressionSwitch::serialize(SerializationOptions options) const { std::vector 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)}}}}); @@ -6062,12 +6087,12 @@ boost::intrusive_ptr ExpressionTrim::optimize() { return this; } -Value ExpressionTrim::serialize(bool explain) const { +Value ExpressionTrim::serialize(SerializationOptions options) const { return Value( Document{{_name, - Document{{"input", _children[_kInput]->serialize(explain)}, + Document{{"input", _children[_kInput]->serialize(options)}, {"chars", - _children[_kCharacters] ? _children[_kCharacters]->serialize(explain) + _children[_kCharacters] ? _children[_kCharacters]->serialize(options) : Value()}}}}); } @@ -6359,17 +6384,17 @@ boost::intrusive_ptr ExpressionZip::optimize() { return this; } -Value ExpressionZip::serialize(bool explain) const { +Value ExpressionZip::serialize(SerializationOptions options) const { vector serializedInput; vector 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" @@ -6938,15 +6963,15 @@ boost::intrusive_ptr ExpressionConvert::optimize() { return this; } -Value ExpressionConvert::serialize(bool explain) const { +Value ExpressionConvert::serialize(SerializationOptions options) const { return Value(Document{ {"$convert", Document{ - {"input", _children[_kInput]->serialize(explain)}, - {"to", _children[_kTo]->serialize(explain)}, - {"onError", _children[_kOnError] ? _children[_kOnError]->serialize(explain) : Value()}, + {"input", _children[_kInput]->serialize(options)}, + {"to", _children[_kTo]->serialize(options)}, + {"onError", _children[_kOnError] ? _children[_kOnError]->serialize(options) : Value()}, {"onNull", - _children[_kOnNull] ? _children[_kOnNull]->serialize(explain) : Value()}}}}); + _children[_kOnNull] ? _children[_kOnNull]->serialize(options) : Value()}}}}); } BSONType ExpressionConvert::computeTargetType(Value targetTypeName) const { @@ -7134,13 +7159,13 @@ void ExpressionRegex::_compile(RegexExecutionState* executionState) const { executionState->numCaptures = executionState->pcrePtr->captureCount(); } -Value ExpressionRegex::serialize(bool explain) const { +Value ExpressionRegex::serialize(SerializationOptions options) const { return Value(Document{ {_opName, - Document{{"input", _children[_kInput]->serialize(explain)}, - {"regex", _children[_kRegex]->serialize(explain)}, + Document{{"input", _children[_kInput]->serialize(options)}, + {"regex", _children[_kRegex]->serialize(options)}, {"options", - _children[_kOptions] ? _children[_kOptions]->serialize(explain) : Value()}}}}); + _children[_kOptions] ? _children[_kOptions]->serialize(options) : Value()}}}}); } void ExpressionRegex::_extractInputField(RegexExecutionState* executionState, @@ -7394,7 +7419,7 @@ intrusive_ptr ExpressionRandom::optimize() { return intrusive_ptr(this); } -Value ExpressionRandom::serialize(const bool explain) const { +Value ExpressionRandom::serialize(SerializationOptions options) const { return Value(DOC(getOpName() << Document())); } @@ -7417,8 +7442,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(SerializationOptions options) const { + return Value(DOC("$toHashedIndexKey" << _children[0]->serialize(options))); } /* ------------------------- ExpressionDateArithmetics -------------------------- */ @@ -7501,14 +7526,14 @@ boost::intrusive_ptr ExpressionDateArithmetics::optimize() { return intrusive_ptr(this); } -Value ExpressionDateArithmetics::serialize(bool explain) const { +Value ExpressionDateArithmetics::serialize(SerializationOptions options) const { return Value(Document{ {_opName, - Document{{"startDate", _children[_kStartDate]->serialize(explain)}, - {"unit", _children[_kUnit]->serialize(explain)}, - {"amount", _children[_kAmount]->serialize(explain)}, + Document{{"startDate", _children[_kStartDate]->serialize(options)}, + {"unit", _children[_kUnit]->serialize(options)}, + {"amount", _children[_kAmount]->serialize(options)}, {"timezone", - _children[_kTimeZone] ? _children[_kTimeZone]->serialize(explain) : Value()}}}}); + _children[_kTimeZone] ? _children[_kTimeZone]->serialize(options) : Value()}}}}); } Value ExpressionDateArithmetics::evaluate(const Document& root, Variables* variables) const { @@ -7744,17 +7769,17 @@ boost::intrusive_ptr ExpressionDateTrunc::optimize() { return this; }; -Value ExpressionDateTrunc::serialize(bool explain) const { +Value ExpressionDateTrunc::serialize(SerializationOptions options) const { return Value{Document{ {"$dateTrunc"_sd, - Document{{"date"_sd, _children[_kDate]->serialize(explain)}, - {"unit"_sd, _children[_kUnit]->serialize(explain)}, + Document{{"date"_sd, _children[_kDate]->serialize(options)}, + {"unit"_sd, _children[_kUnit]->serialize(options)}, {"binSize"_sd, - _children[_kBinSize] ? _children[_kBinSize]->serialize(explain) : Value{}}, + _children[_kBinSize] ? _children[_kBinSize]->serialize(options) : Value{}}, {"timezone"_sd, - _children[_kTimeZone] ? _children[_kTimeZone]->serialize(explain) : Value{}}, + _children[_kTimeZone] ? _children[_kTimeZone]->serialize(options) : Value{}}, {"startOfWeek"_sd, - _children[_kStartOfWeek] ? _children[_kStartOfWeek]->serialize(explain) + _children[_kStartOfWeek] ? _children[_kStartOfWeek]->serialize(options) : Value{}}}}}}; }; @@ -7948,10 +7973,10 @@ intrusive_ptr ExpressionGetField::optimize() { return intrusive_ptr(this); } -Value ExpressionGetField::serialize(const bool explain) const { +Value ExpressionGetField::serialize(SerializationOptions options) const { return Value(Document{{"$getField"_sd, - Document{{"field"_sd, _children[_kField]->serialize(explain)}, - {"input"_sd, _children[_kInput]->serialize(explain)}}}}); + Document{{"field"_sd, _children[_kField]->serialize(options)}, + {"input"_sd, _children[_kInput]->serialize(options)}}}}); } /* -------------------------- ExpressionSetField ------------------------------ */ @@ -8061,11 +8086,11 @@ intrusive_ptr ExpressionSetField::optimize() { return intrusive_ptr(this); } -Value ExpressionSetField::serialize(const bool explain) const { +Value ExpressionSetField::serialize(SerializationOptions options) const { return Value(Document{{"$setField"_sd, - Document{{"field"_sd, _children[_kField]->serialize(explain)}, - {"input"_sd, _children[_kInput]->serialize(explain)}, - {"value"_sd, _children[_kValue]->serialize(explain)}}}}); + Document{{"field"_sd, _children[_kField]->serialize(options)}, + {"input"_sd, _children[_kInput]->serialize(options)}, + {"value"_sd, _children[_kValue]->serialize(options)}}}}); } /* ------------------------- ExpressionTsSecond ----------------------------- */ diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h index 4224063ae04..f000c07bd2d 100644 --- a/src/mongo/db/pipeline/expression.h +++ b/src/mongo/db/pipeline/expression.h @@ -54,6 +54,7 @@ #include "mongo/db/query/allowed_contexts.h" #include "mongo/db/query/datetime/date_time_support.h" #include "mongo/db/query/query_feature_flags_gen.h" +#include "mongo/db/query/serialization_options.h" #include "mongo/db/query/sort_pattern.h" #include "mongo/db/server_options.h" #include "mongo/db/update/pattern_cmp.h" @@ -207,7 +208,7 @@ public: * If 'explain' is false, the returned Value must result in the same Expression when parsed by * parseOperand(). */ - virtual Value serialize(bool explain) const = 0; + virtual Value serialize(SerializationOptions options) const = 0; /** * Evaluate the expression with respect to the Document given by 'root' and the Variables given @@ -373,7 +374,7 @@ private: class ExpressionNary : public Expression { public: boost::intrusive_ptr optimize() override; - Value serialize(bool explain) const override; + Value serialize(SerializationOptions options) const override; /* Add an operand to the n-ary expression. @@ -553,9 +554,9 @@ public: return AccumulatorN::kName.rawData(); } - Value serialize(bool explain) const { + Value serialize(SerializationOptions options) const { MutableDocument md; - AccumulatorN::serializeHelper(_n, _output, explain, md); + AccumulatorN::serializeHelper(_n, _output, options, md); return Value(DOC(getOpName() << md.freeze())); } @@ -673,7 +674,7 @@ public: boost::intrusive_ptr optimize() final; Value evaluate(const Document& root, Variables* variables) const final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; const char* getOpName() const; @@ -770,10 +771,10 @@ public: * Always serializes to the full {date: , timezone: } format, leaving * off the timezone if not specified. */ - Value serialize(bool explain) const final { - auto timezone = _children[_kTimeZone] ? _children[_kTimeZone]->serialize(explain) : Value(); + Value serialize(SerializationOptions options) const final { + auto timezone = _children[_kTimeZone] ? _children[_kTimeZone]->serialize(options) : Value(); return Value(Document{{_opName, - Document{{"date", _children[_kDate]->serialize(explain)}, + Document{{"date", _children[_kDate]->serialize(options)}, {"timezone", std::move(timezone)}}}}); } @@ -1047,7 +1048,7 @@ public: } Value evaluate(const Document& root, Variables* variables) const final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; static boost::intrusive_ptr create( ExpressionContext* const expCtx, std::vector>&& children) { @@ -1223,7 +1224,7 @@ class ExpressionCoerceToBool final : public Expression { public: boost::intrusive_ptr optimize() final; Value evaluate(const Document& root, Variables* variables) const final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; static boost::intrusive_ptr create( ExpressionContext* expCtx, boost::intrusive_ptr pExpression); @@ -1384,7 +1385,7 @@ public: boost::intrusive_ptr onError); boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr parse(ExpressionContext* expCtx, @@ -1426,7 +1427,7 @@ public: boost::intrusive_ptr timeZone); boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr parse(ExpressionContext* expCtx, @@ -1506,7 +1507,7 @@ public: boost::intrusive_ptr iso8601); boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr parse(ExpressionContext* expCtx, @@ -1540,7 +1541,7 @@ public: boost::intrusive_ptr timeZone, boost::intrusive_ptr onNull); boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr parse(ExpressionContext* expCtx, @@ -1652,7 +1653,7 @@ public: boost::intrusive_ptr timezone, boost::intrusive_ptr startOfWeek); boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr parse(ExpressionContext* expCtx, BSONElement expr, @@ -1791,7 +1792,7 @@ public: boost::intrusive_ptr optimize() final; Value evaluate(const Document& root, Variables* variables) const; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; /* Create a field path expression using old semantics (rooted off of CURRENT). @@ -1892,7 +1893,7 @@ private: class ExpressionFilter final : public Expression { public: boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr parse(ExpressionContext* expCtx, @@ -2127,7 +2128,7 @@ public: class ExpressionLet final : public Expression { public: boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr parse(ExpressionContext* expCtx, @@ -2240,7 +2241,7 @@ public: ConstDataRange serverToken, int64_t contentionFactor, std::vector edcTokens); - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const; @@ -2268,7 +2269,7 @@ public: ConstDataRange serverToken, int64_t contentionFactor, ConstDataRange edcToken); - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; const char* getOpName() const; @@ -2299,7 +2300,7 @@ public: boost::intrusive_ptr each); // yields results to be added to output array boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr parse(ExpressionContext* expCtx, @@ -2328,7 +2329,7 @@ class ExpressionMeta final : public Expression { public: ExpressionMeta(ExpressionContext* expCtx, DocumentMetadataFields::MetaType metaType); - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; static boost::intrusive_ptr parse(ExpressionContext* expCtx, @@ -2518,7 +2519,7 @@ class ExpressionObject final : public Expression { public: boost::intrusive_ptr optimize() final; Value evaluate(const Document& root, Variables* variables) const final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; static boost::intrusive_ptr create( ExpressionContext* expCtx, @@ -2655,7 +2656,7 @@ public: static boost::intrusive_ptr parse(ExpressionContext* expCtx, BSONElement expr, const VariablesParseState& vps); - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; void acceptVisitor(ExpressionMutableVisitor* visitor) final { return visitor->visit(this); @@ -2686,7 +2687,7 @@ public: virtual const char* getOpName() const = 0; Value evaluate(const Document& root, Variables* variables) const final; boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; protected: virtual Value _doEval(StringData input, StringData find, StringData replacement) const = 0; @@ -2963,7 +2964,7 @@ public: static boost::intrusive_ptr parse(ExpressionContext* expCtx, BSONElement expr, const VariablesParseState& vps); - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; void acceptVisitor(ExpressionMutableVisitor* visitor) final { return visitor->visit(this); @@ -3325,7 +3326,7 @@ public: static boost::intrusive_ptr parse(ExpressionContext* expCtx, BSONElement expr, const VariablesParseState& vpsIn); - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; void acceptVisitor(ExpressionMutableVisitor* visitor) final { return visitor->visit(this); @@ -3435,7 +3436,7 @@ public: static boost::intrusive_ptr parse(ExpressionContext* expCtx, BSONElement expr, const VariablesParseState& vpsIn); - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; void acceptVisitor(ExpressionMutableVisitor* visitor) final { return visitor->visit(this); @@ -3684,7 +3685,7 @@ public: static boost::intrusive_ptr parse(ExpressionContext* expCtx, BSONElement expr, const VariablesParseState& vpsIn); - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; void acceptVisitor(ExpressionMutableVisitor* visitor) final { return visitor->visit(this); @@ -3721,7 +3722,7 @@ public: Value evaluate(const Document& root, Variables* variables) const final; boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; void acceptVisitor(ExpressionMutableVisitor* visitor) final { return visitor->visit(this); @@ -3823,7 +3824,7 @@ public: boost::optional, std::string>> getConstantPatternAndOptions() const; - Value serialize(bool explain) const; + Value serialize(SerializationOptions options) const; const std::string& getOpName() const { return _opName; @@ -3935,7 +3936,7 @@ public: BSONElement exprElement, const VariablesParseState& vps); - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; @@ -3978,7 +3979,7 @@ public: } Value evaluate(const Document& root, Variables* variables) const; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; }; class ExpressionDateArithmetics : public Expression { @@ -3995,7 +3996,7 @@ public: _opName(opName) {} boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; protected: @@ -4127,7 +4128,7 @@ public: boost::intrusive_ptr timezone, boost::intrusive_ptr startOfWeek); boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; void acceptVisitor(ExpressionMutableVisitor* visitor) final { return visitor->visit(this); @@ -4225,7 +4226,7 @@ public: expCtx->sbeCompatible = false; } - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; @@ -4264,7 +4265,7 @@ public: expCtx->sbeCompatible = false; } - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; diff --git a/src/mongo/db/pipeline/expression_field_path_test.cpp b/src/mongo/db/pipeline/expression_field_path_test.cpp index 13b59c1b4c4..9507cc0000a 100644 --- a/src/mongo/db/pipeline/expression_field_path_test.cpp +++ b/src/mongo/db/pipeline/expression_field_path_test.cpp @@ -61,6 +61,10 @@ Document fromBson(BSONObj obj) { return Document(obj); } +std::string redactFieldNameForTest(StringData s) { + return str::stream() << "HASH(" << s << ")"; +} + namespace FieldPath { /** The provided field path does not pass validation. */ @@ -200,6 +204,67 @@ TEST(FieldPath, ScalarVariableWithDottedFieldPathOptimizesToConstantMissingValue ASSERT_VALUE_EQ(Value(), constantExpr->getValue()); } +TEST(FieldPath, SerializeWithRedaction) { + SerializationOptions options; + options.redactFieldNamesStrategy = redactFieldNameForTest; + options.redactFieldNames = true; + + auto expCtx = ExpressionContextForTest{}; + intrusive_ptr expression = + ExpressionFieldPath::createPathFromString(&expCtx, "bar", expCtx.variablesParseState); + ASSERT_BSONOBJ_BINARY_EQ(BSON("foo" + << "$HASH(bar)"), + BSON("foo" << expression->serialize(options))); + // Repeat with a dotted path. + expression = + ExpressionFieldPath::createPathFromString(&expCtx, "a.b.c", expCtx.variablesParseState); + ASSERT_BSONOBJ_BINARY_EQ(BSON("foo" + << "$HASH(a).HASH(b).HASH(c)"), + BSON("foo" << expression->serialize(options))); + + // Expression with multiple field paths. + // {$and: [{$gt: ["$foo", 5]}, {$lt: [$foo, 10]}]} => {$and: [{$gt: ["$HASH(foo)", 5]}, {$lt: + // [$HASH(foo), 10]}]} + auto expressionBSON = BSON("$and" << BSON_ARRAY(BSON("$gt" << BSON_ARRAY("$foo" << 5)) + << BSON("$lt" << BSON_ARRAY("$foo" << 10)))); + expression = Expression::parseExpression(&expCtx, expressionBSON, expCtx.variablesParseState); + auto redactedBSON = BSON( + "$and" << BSON_ARRAY(BSON("$gt" << BSON_ARRAY("$HASH(foo)" << BSON("$const" << 5))) + << BSON("$lt" << BSON_ARRAY("$HASH(foo)" << BSON("$const" << 10))))); + ASSERT_BSONOBJ_BINARY_EQ(BSON("field" << redactedBSON), + BSON("field" << expression->serialize(options))); + + // Test that a variable followed by user fields is properly hashed. + std::string replacementChar = "?"; + options.replacementForLiteralArgs = replacementChar; + expressionBSON = BSON("$gt" << BSON_ARRAY("$$ROOT.a.b" + << "5")); + expression = Expression::parseExpression(&expCtx, expressionBSON, expCtx.variablesParseState); + redactedBSON = BSON("$gt" << BSON_ARRAY("$$ROOT.HASH(a).HASH(b)" << BSON("$const" + << "?"))); + ASSERT_BSONOBJ_BINARY_EQ(BSON("field" << redactedBSON), + BSON("field" << expression->serialize(options))); + + // Test that a system variable is preserved. + expressionBSON = BSON("$gt" << BSON_ARRAY("$foo" + << "$$NOW")); + expression = Expression::parseExpression(&expCtx, expressionBSON, expCtx.variablesParseState); + redactedBSON = BSON("$gt" << BSON_ARRAY("$HASH(foo)" + << "$$NOW")); + ASSERT_BSONOBJ_BINARY_EQ(BSON("field" << redactedBSON), + BSON("field" << expression->serialize(options))); + + + expressionBSON = BSON("$gt" << BSON_ARRAY("$foo.a.b" + << "$$NOW")); + // Repeat the above test with a dotted path. + expression = Expression::parseExpression(&expCtx, expressionBSON, expCtx.variablesParseState); + redactedBSON = BSON("$gt" << BSON_ARRAY("$HASH(foo).HASH(a).HASH(b)" + << "$$NOW")); + ASSERT_BSONOBJ_BINARY_EQ(BSON("field" << redactedBSON), + BSON("field" << expression->serialize(options))); +} + /** The field path itself is a dependency. */ class Dependencies { public: diff --git a/src/mongo/db/pipeline/expression_find_internal.h b/src/mongo/db/pipeline/expression_find_internal.h index 7eb727a55ae..9baf15b3958 100644 --- a/src/mongo/db/pipeline/expression_find_internal.h +++ b/src/mongo/db/pipeline/expression_find_internal.h @@ -78,7 +78,7 @@ public: return visitor->visit(this); } - Value serialize(bool explain) const final { + Value serialize(SerializationOptions options) const final { MONGO_UNREACHABLE; } @@ -148,7 +148,7 @@ public: return visitor->visit(this); } - Value serialize(bool explain) const final { + Value serialize(SerializationOptions options) const final { MONGO_UNREACHABLE; } @@ -201,7 +201,7 @@ public: return visitor->visit(this); } - Value serialize(bool explain) const final { + Value serialize(SerializationOptions options) const final { MONGO_UNREACHABLE; } diff --git a/src/mongo/db/pipeline/expression_function.cpp b/src/mongo/db/pipeline/expression_function.cpp index 4b35b9fa0b3..da3a0b1e374 100644 --- a/src/mongo/db/pipeline/expression_function.cpp +++ b/src/mongo/db/pipeline/expression_function.cpp @@ -46,10 +46,10 @@ ExpressionFunction::ExpressionFunction(ExpressionContext* const expCtx, expCtx->sbeCompatible = false; } -Value ExpressionFunction::serialize(bool explain) const { +Value ExpressionFunction::serialize(SerializationOptions options) const { MutableDocument d; d["body"] = Value(_funcSource); - d["args"] = Value(_passedArgs->serialize(explain)); + d["args"] = Value(_passedArgs->serialize(options)); d["lang"] = Value(_lang); // This field will only be seralized when desugaring $where in $expr + $_internalJs if (_assignFirstArgToThis) { diff --git a/src/mongo/db/pipeline/expression_function.h b/src/mongo/db/pipeline/expression_function.h index c6d5987820b..29111b21f64 100644 --- a/src/mongo/db/pipeline/expression_function.h +++ b/src/mongo/db/pipeline/expression_function.h @@ -69,7 +69,7 @@ public: Value evaluate(const Document& root, Variables* variables) const final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; void acceptVisitor(ExpressionMutableVisitor* visitor) final { return visitor->visit(this); diff --git a/src/mongo/db/pipeline/expression_js_emit.cpp b/src/mongo/db/pipeline/expression_js_emit.cpp index 5af7c69b456..3a45690f302 100644 --- a/src/mongo/db/pipeline/expression_js_emit.cpp +++ b/src/mongo/db/pipeline/expression_js_emit.cpp @@ -128,10 +128,10 @@ boost::intrusive_ptr ExpressionInternalJsEmit::parse(ExpressionConte return new ExpressionInternalJsEmit(expCtx, std::move(thisRef), std::move(funcSourceString)); } -Value ExpressionInternalJsEmit::serialize(bool explain) const { +Value ExpressionInternalJsEmit::serialize(SerializationOptions options) const { return Value( Document{{kExpressionName, - Document{{"eval", _funcSource}, {"this", _thisRef->serialize(explain)}}}}); + Document{{"eval", _funcSource}, {"this", _thisRef->serialize(options)}}}}); } Value ExpressionInternalJsEmit::evaluate(const Document& root, Variables* variables) const { diff --git a/src/mongo/db/pipeline/expression_js_emit.h b/src/mongo/db/pipeline/expression_js_emit.h index 5b346baf871..18540866001 100644 --- a/src/mongo/db/pipeline/expression_js_emit.h +++ b/src/mongo/db/pipeline/expression_js_emit.h @@ -55,7 +55,7 @@ public: Value evaluate(const Document& root, Variables* variables) const final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; void acceptVisitor(ExpressionMutableVisitor* visitor) final { return visitor->visit(this); diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp index ae017b10df3..f9e7cb5a234 100644 --- a/src/mongo/db/pipeline/expression_test.cpp +++ b/src/mongo/db/pipeline/expression_test.cpp @@ -813,6 +813,31 @@ TEST(ExpressionConstantTest, ConstantOfValueMissingSerializesToRemoveSystemVar) BSON("field" << expression->serialize(false))); } +TEST(ExpressionConstantTest, ConstantRedaction) { + SerializationOptions options; + std::string replacementChar = "?"; + options.replacementForLiteralArgs = replacementChar; + + // Test that a constant is replaced. + auto expCtx = ExpressionContextForTest{}; + intrusive_ptr expression = ExpressionConstant::create(&expCtx, Value("my_ssn"_sd)); + ASSERT_BSONOBJ_BINARY_EQ(BSON("field" << BSON("$const" << replacementChar)), + BSON("field" << expression->serialize(options))); + + // Test an expression with multiple ExpressionConst children. + // {$and: [{$gt: ["$foo", 5]}, {$lt: [$foo, 10]}]} => {$and: [{$gt: ["$foo", "?"]}, {$lt: [$foo, + // "?"]}]} + auto expressionBSON = BSON("$and" << BSON_ARRAY(BSON("$gt" << BSON_ARRAY("$foo" << 5)) + << BSON("$lt" << BSON_ARRAY("$foo" << 10)))); + expression = Expression::parseExpression(&expCtx, expressionBSON, expCtx.variablesParseState); + auto redactedBSON = + BSON("$and" << BSON_ARRAY( + BSON("$gt" << BSON_ARRAY("$foo" << BSON("$const" << replacementChar))) + << BSON("$lt" << BSON_ARRAY("$foo" << BSON("$const" << replacementChar))))); + ASSERT_BSONOBJ_BINARY_EQ(BSON("field" << redactedBSON), + BSON("field" << expression->serialize(options))); +} + } // namespace Constant TEST(ExpressionFromAccumulators, Avg) { diff --git a/src/mongo/db/pipeline/expression_test_api_version.cpp b/src/mongo/db/pipeline/expression_test_api_version.cpp index 7602535be84..6a4c46b73c4 100644 --- a/src/mongo/db/pipeline/expression_test_api_version.cpp +++ b/src/mongo/db/pipeline/expression_test_api_version.cpp @@ -89,7 +89,7 @@ boost::intrusive_ptr ExpressionTestApiVersion::parse(ExpressionConte return new ExpressionTestApiVersion(expCtx, unstableField, deprecatedField); } -Value ExpressionTestApiVersion::serialize(bool explain) const { +Value ExpressionTestApiVersion::serialize(SerializationOptions options) const { return Value(Document{{"$_testApiVersion", Document{{"unstable", _unstable ? Value(_unstable) : Value()}, {"deprecated", _deprecated ? Value(_deprecated) : Value()}}}}); diff --git a/src/mongo/db/pipeline/expression_test_api_version.h b/src/mongo/db/pipeline/expression_test_api_version.h index 2608bb2dd01..19f2dfc891e 100644 --- a/src/mongo/db/pipeline/expression_test_api_version.h +++ b/src/mongo/db/pipeline/expression_test_api_version.h @@ -48,7 +48,7 @@ public: Value evaluate(const Document& root, Variables* variables) const final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; void acceptVisitor(ExpressionMutableVisitor* visitor) final { return visitor->visit(this); diff --git a/src/mongo/db/query/serialization_options.h b/src/mongo/db/query/serialization_options.h index 9ab6fbc1c33..f72116bd7fb 100644 --- a/src/mongo/db/query/serialization_options.h +++ b/src/mongo/db/query/serialization_options.h @@ -29,17 +29,24 @@ #pragma once +namespace mongo { +namespace { +// Should never be called, throw to ensure we catch this in tests. +std::string defaultRedactionStrategy(StringData s) { + MONGO_UNREACHABLE_TASSERT(7332410); +} +} // namespace + #include #include "mongo/base/string_data.h" -namespace mongo { - /** - * A struct with options for how you want to serialize a match expression. + * A struct with options for how you want to serialize a match or aggregation expression. */ struct SerializationOptions { SerializationOptions() {} + SerializationOptions(bool explain_) : explain(explain_) {} // 'replacementForLiteralArgs' is an independent option to serialize in a genericized format // with the aim of similar "shaped" queries serializing to the same object. For example, if @@ -51,7 +58,11 @@ struct SerializationOptions { // 4, so the serialization expected would be {$and: [{a: {$gt: '?'}}, {b: {$lt: '?'}}]}. boost::optional replacementForLiteralArgs = boost::none; - // TODO SERVER-73663 'redactFieldNames' could be here - a callback function? + // If true the caller must set redactFieldNamesStrategy. 'redactFieldNames' if set along with + // a strategy the redaction strategy will be called on any field paths/names encountered + // before serializing them. + bool redactFieldNames = false; + std::function redactFieldNamesStrategy = defaultRedactionStrategy; // If set, serializes without including the path. For example {a: {$gt: 2}} would serialize // as just {$gt: 2}. @@ -66,6 +77,9 @@ struct SerializationOptions { // The $elemMatch will serialize {a: {$elemMatch: }} and the EQ will serialize just // {$eq: 2} instead of its usual {a: {$eq: 2}}. bool includePath = true; + + // For aggregation indicate whether we should use the more verbose serialization format. + bool explain = false; }; } // namespace mongo diff --git a/src/mongo/s/commands/sharding_expressions.cpp b/src/mongo/s/commands/sharding_expressions.cpp index cf2bdac9a99..407dd622eab 100644 --- a/src/mongo/s/commands/sharding_expressions.cpp +++ b/src/mongo/s/commands/sharding_expressions.cpp @@ -463,7 +463,7 @@ boost::intrusive_ptr ExpressionInternalIndexKey::optimize() { return this; } -Value ExpressionInternalIndexKey::serialize(bool explain) const { +Value ExpressionInternalIndexKey::serialize(SerializationOptions options) const { invariant(_doc); invariant(_spec); @@ -472,7 +472,7 @@ Value ExpressionInternalIndexKey::serialize(bool explain) const { // The 'spec' is always treated as a constant so do not call '_spec->serialize()' which would // wrap the value in an unnecessary '$const' object. - return Value(DOC(opName << DOC(kDocField << _doc->serialize(explain) << kSpecField + return Value(DOC(opName << DOC(kDocField << _doc->serialize(options) << kSpecField << specExprConstant->getValue()))); } diff --git a/src/mongo/s/commands/sharding_expressions.h b/src/mongo/s/commands/sharding_expressions.h index e46218132a8..809d3a057d2 100644 --- a/src/mongo/s/commands/sharding_expressions.h +++ b/src/mongo/s/commands/sharding_expressions.h @@ -131,7 +131,7 @@ public: boost::intrusive_ptr optimize() final; - Value serialize(bool explain) const final; + Value serialize(SerializationOptions options) const final; Value evaluate(const Document& root, Variables* variables) const final; -- cgit v1.2.1