diff options
author | Jess Balint <jbalint@gmail.com> | 2023-02-23 22:59:47 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-02-24 05:30:37 +0000 |
commit | 7c793c6e717490b64bfd08dfccddc65567e94824 (patch) | |
tree | a0da99a3fb399e29f8a24cdb229899c07c2879b3 /src/mongo/db | |
parent | 3b0428bc373dc939cd156ca7fc5cdd4409a71534 (diff) | |
download | mongo-7c793c6e717490b64bfd08dfccddc65567e94824.tar.gz |
SERVER-73663 Field name redaction for LeafMatchExpressions #10765
SERVER-73676 Query shape (literal redaction) for leftover non-leaf MatchExpressions, pt. 2
Diffstat (limited to 'src/mongo/db')
34 files changed, 224 insertions, 126 deletions
diff --git a/src/mongo/db/matcher/expression_array.cpp b/src/mongo/db/matcher/expression_array.cpp index 5f69a1211de..db1ef7dc7b6 100644 --- a/src/mongo/db/matcher/expression_array.cpp +++ b/src/mongo/db/matcher/expression_array.cpp @@ -104,9 +104,7 @@ void ElemMatchObjectMatchExpression::debugString(StringBuilder& debug, int inden } BSONObj ElemMatchObjectMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { - SerializationOptions opts; - opts.replacementForLiteralArgs = replacementForLiteralArgs; + SerializationOptions opts) const { return BSON("$elemMatch" << _sub->serialize(opts)); } @@ -175,14 +173,11 @@ void ElemMatchValueMatchExpression::debugString(StringBuilder& debug, int indent } } -BSONObj ElemMatchValueMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { +BSONObj ElemMatchValueMatchExpression::getSerializedRightHandSide(SerializationOptions opts) const { BSONObjBuilder emBob; + opts.includePath = false; for (auto&& child : _subs) { - SerializationOptions opts; - opts.includePath = false; - opts.replacementForLiteralArgs = replacementForLiteralArgs; child->serialize(&emBob, opts); } @@ -224,10 +219,12 @@ void SizeMatchExpression::debugString(StringBuilder& debug, int indentationLevel } } -BSONObj SizeMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { - // TODO SERVER-73676 respect 'replacementForLiteralArgs.' - return BSON("$size" << _size); +BSONObj SizeMatchExpression::getSerializedRightHandSide(SerializationOptions opts) const { + const char* opName = "$size"; + if (opts.replacementForLiteralArgs) { + return BSON(opName << *opts.replacementForLiteralArgs); + } + return BSON(opName << _size); } bool SizeMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/expression_array.h b/src/mongo/db/matcher/expression_array.h index c8f1ede74a3..4c26756d33f 100644 --- a/src/mongo/db/matcher/expression_array.h +++ b/src/mongo/db/matcher/expression_array.h @@ -90,8 +90,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; std::vector<std::unique_ptr<MatchExpression>>* getChildVector() final { return nullptr; @@ -159,8 +158,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; std::vector<std::unique_ptr<MatchExpression>>* getChildVector() final { return &_subs; @@ -235,8 +233,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; virtual bool equivalent(const MatchExpression* other) const; diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp index 22eaccdf1ff..36a9e3f3107 100644 --- a/src/mongo/db/matcher/expression_geo.cpp +++ b/src/mongo/db/matcher/expression_geo.cpp @@ -447,8 +447,7 @@ void GeoMatchExpression::debugString(StringBuilder& debug, int indentationLevel) debug << "\n"; } -BSONObj GeoMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { +BSONObj GeoMatchExpression::getSerializedRightHandSide(SerializationOptions opts) const { BSONObjBuilder subobj; // TODO SERVER-73672 looks like we'll need to traverse '_rawObj' if 'replacementForLiteralArgs' // is set. @@ -508,8 +507,7 @@ void GeoNearMatchExpression::debugString(StringBuilder& debug, int indentationLe debug << "\n"; } -BSONObj GeoNearMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { +BSONObj GeoNearMatchExpression::getSerializedRightHandSide(SerializationOptions opts) const { // TODO SERVER-73672 looks like we'll need to traverse '_rawObj' if 'replacementForLiteralArgs' // is set. BSONObjBuilder objBuilder; diff --git a/src/mongo/db/matcher/expression_geo.h b/src/mongo/db/matcher/expression_geo.h index 2c92e2250ad..1e784e0ba0b 100644 --- a/src/mongo/db/matcher/expression_geo.h +++ b/src/mongo/db/matcher/expression_geo.h @@ -106,8 +106,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -211,8 +210,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -255,7 +253,7 @@ public: : LeafMatchExpression(INTERNAL_2D_POINT_IN_ANNULUS, twoDPath), _annulus(annulus) {} void serialize(BSONObjBuilder* out, SerializationOptions opts) const final { - out->append("TwoDPtInAnnulusExpression", true); + out->append("$TwoDPtInAnnulusExpression", true); } bool matchesSingleElement(const BSONElement& e, MatchDetails* details = nullptr) const final { @@ -273,9 +271,7 @@ public: // These won't be called. // - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final { - // TODO SERVER-73676 is this going to be a problem? I don't see how this is unreachable... + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final { MONGO_UNREACHABLE; } diff --git a/src/mongo/db/matcher/expression_internal_bucket_geo_within.cpp b/src/mongo/db/matcher/expression_internal_bucket_geo_within.cpp index 001992fafa0..e7618378833 100644 --- a/src/mongo/db/matcher/expression_internal_bucket_geo_within.cpp +++ b/src/mongo/db/matcher/expression_internal_bucket_geo_within.cpp @@ -193,13 +193,23 @@ bool InternalBucketGeoWithinMatchExpression::_matchesBSONObj(const BSONObj& obj) void InternalBucketGeoWithinMatchExpression::serialize(BSONObjBuilder* builder, SerializationOptions opts) const { - // TODO SERVER-73676 respect 'opts'. BSONObjBuilder bob(builder->subobjStart(InternalBucketGeoWithinMatchExpression::kName)); + // Serialize the geometry shape. BSONObjBuilder withinRegionBob( bob.subobjStart(InternalBucketGeoWithinMatchExpression::kWithinRegion)); - withinRegionBob.append(_geoContainer->getGeoElement()); + if (opts.replacementForLiteralArgs) { + bob.append(_geoContainer->getGeoElement().fieldName(), *opts.replacementForLiteralArgs); + } else { + withinRegionBob.append(_geoContainer->getGeoElement()); + } withinRegionBob.doneFast(); - bob.append(InternalBucketGeoWithinMatchExpression::kField, _field); + // Serialize the field which is being searched over. + if (opts.redactFieldNames) { + bob.append(InternalBucketGeoWithinMatchExpression::kField, + opts.redactFieldNamesStrategy(_field)); + } else { + bob.append(InternalBucketGeoWithinMatchExpression::kField, _field); + } bob.doneFast(); } diff --git a/src/mongo/db/matcher/expression_leaf.cpp b/src/mongo/db/matcher/expression_leaf.cpp index 7e0bd6f2791..fe39d9298ce 100644 --- a/src/mongo/db/matcher/expression_leaf.cpp +++ b/src/mongo/db/matcher/expression_leaf.cpp @@ -94,10 +94,9 @@ void ComparisonMatchExpressionBase::debugString(StringBuilder& debug, int indent debug << "\n"; } -BSONObj ComparisonMatchExpressionBase::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { - if (replacementForLiteralArgs) { - return BSON(name() << *replacementForLiteralArgs); +BSONObj ComparisonMatchExpressionBase::getSerializedRightHandSide(SerializationOptions opts) const { + if (opts.replacementForLiteralArgs) { + return BSON(name() << *opts.replacementForLiteralArgs); } else { return BSON(name() << _rhs); } @@ -290,13 +289,12 @@ void RegexMatchExpression::debugString(StringBuilder& debug, int indentationLeve debug << "\n"; } -BSONObj RegexMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { +BSONObj RegexMatchExpression::getSerializedRightHandSide(SerializationOptions opts) const { BSONObjBuilder regexBuilder; - regexBuilder.append("$regex", replacementForLiteralArgs.value_or(_regex)); + regexBuilder.append("$regex", opts.replacementForLiteralArgs.value_or(_regex)); if (!_flags.empty()) { - regexBuilder.append("$options", replacementForLiteralArgs.value_or(_flags)); + regexBuilder.append("$options", opts.replacementForLiteralArgs.value_or(_flags)); } return regexBuilder.obj(); @@ -372,9 +370,8 @@ void ModMatchExpression::debugString(StringBuilder& debug, int indentationLevel) debug << "\n"; } -BSONObj ModMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { - if (auto str = replacementForLiteralArgs) { +BSONObj ModMatchExpression::getSerializedRightHandSide(SerializationOptions opts) const { + if (auto str = opts.replacementForLiteralArgs) { return BSON("$mod" << *str); } else { return BSON("$mod" << BSON_ARRAY(_divisor << _remainder)); @@ -413,10 +410,9 @@ void ExistsMatchExpression::debugString(StringBuilder& debug, int indentationLev debug << "\n"; } -BSONObj ExistsMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { - if (replacementForLiteralArgs) { - return BSON("$exists" << *replacementForLiteralArgs); +BSONObj ExistsMatchExpression::getSerializedRightHandSide(SerializationOptions opts) const { + if (opts.replacementForLiteralArgs) { + return BSON("$exists" << *opts.replacementForLiteralArgs); } else { return BSON("$exists" << true); } @@ -503,11 +499,10 @@ void InMatchExpression::debugString(StringBuilder& debug, int indentationLevel) debug << "\n"; } -BSONObj InMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { - if (replacementForLiteralArgs) { +BSONObj InMatchExpression::getSerializedRightHandSide(SerializationOptions opts) const { + if (opts.replacementForLiteralArgs) { // In this case, treat an '$in' with any number of arguments as equivalent. - return BSON("$in" << BSON_ARRAY(*replacementForLiteralArgs)); + return BSON("$in" << BSON_ARRAY(*opts.replacementForLiteralArgs)); } BSONObjBuilder inBob; @@ -863,8 +858,7 @@ void BitTestMatchExpression::debugString(StringBuilder& debug, int indentationLe } } -BSONObj BitTestMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { +BSONObj BitTestMatchExpression::getSerializedRightHandSide(SerializationOptions opts) const { std::string opString = ""; switch (matchType()) { @@ -884,8 +878,8 @@ BSONObj BitTestMatchExpression::getSerializedRightHandSide( MONGO_UNREACHABLE; } - if (replacementForLiteralArgs) { - return BSON(opString << *replacementForLiteralArgs); + if (opts.replacementForLiteralArgs) { + return BSON(opString << *opts.replacementForLiteralArgs); } BSONArrayBuilder arrBob; diff --git a/src/mongo/db/matcher/expression_leaf.h b/src/mongo/db/matcher/expression_leaf.h index ad2556a44e1..82b62000365 100644 --- a/src/mongo/db/matcher/expression_leaf.h +++ b/src/mongo/db/matcher/expression_leaf.h @@ -161,8 +161,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -509,8 +508,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; void serializeToBSONTypeRegex(BSONObjBuilder* out) const; @@ -592,8 +590,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -660,8 +657,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -695,8 +691,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; virtual bool equivalent(const MatchExpression* other) const; @@ -839,8 +834,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel) const; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; virtual bool equivalent(const MatchExpression* other) const; diff --git a/src/mongo/db/matcher/expression_path.h b/src/mongo/db/matcher/expression_path.h index 295495bc305..07db4a167ed 100644 --- a/src/mongo/db/matcher/expression_path.h +++ b/src/mongo/db/matcher/expression_path.h @@ -165,10 +165,16 @@ public: // bit confused over 'includePath' semantics here. Before we changed anything for query // shape, it looks like 'includePath' was not forwarded through, so it's either not needed // or there was a pre-existing bug. + auto&& rhs = getSerializedRightHandSide(opts); if (opts.includePath) { - out->append(path(), getSerializedRightHandSide(opts.replacementForLiteralArgs)); + if (opts.redactFieldNames) { + auto redactedFieldName = opts.redactFieldNamesStrategy(path()); + out->append(redactedFieldName, rhs); + } else { + out->append(path(), rhs); + } } else { - out->appendElements(getSerializedRightHandSide(opts.replacementForLiteralArgs)); + out->appendElements(rhs); } } @@ -178,12 +184,12 @@ public: * line with the expression. For example {x: {$not: {$eq: 1}}}, where $eq is the * PathMatchExpression. * - * If the 'replacementForLiteralArgs' option is set, then any literal argument (like the number - * 1 in the example above), should be replaced with this string. 'literal' here is in contrast - * to another expression, if that is possible syntactically. + * Serialization options should be respected for any descendent expressions. Eg, if the + * 'replacementForLiteralArgs' option is set, then any literal argument (like the number 1 in + * the example above), should be replaced with this string. 'literal' here is in contrast to + * another expression, if that is possible syntactically. */ - virtual BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs = boost::none) const = 0; + virtual BSONObj getSerializedRightHandSide(SerializationOptions opts = {}) const = 0; private: // ElementPath holds a FieldRef, which owns the underlying path string. diff --git a/src/mongo/db/matcher/expression_text_base.cpp b/src/mongo/db/matcher/expression_text_base.cpp index 68af83a2932..b5c3825c679 100644 --- a/src/mongo/db/matcher/expression_text_base.cpp +++ b/src/mongo/db/matcher/expression_text_base.cpp @@ -57,12 +57,19 @@ void TextMatchExpressionBase::debugString(StringBuilder& debug, int indentationL } void TextMatchExpressionBase::serialize(BSONObjBuilder* out, SerializationOptions opts) const { - // TODO support 'opts' const fts::FTSQuery& ftsQuery = getFTSQuery(); - out->append("$text", - BSON("$search" << ftsQuery.getQuery() << "$language" << ftsQuery.getLanguage() - << "$caseSensitive" << ftsQuery.getCaseSensitive() - << "$diacriticSensitive" << ftsQuery.getDiacriticSensitive())); + if (opts.replacementForLiteralArgs) { + out->append("$text", + BSON("$search" << *opts.replacementForLiteralArgs << "$language" + << *opts.replacementForLiteralArgs << "$caseSensitive" + << *opts.replacementForLiteralArgs << "$diacriticSensitive" + << *opts.replacementForLiteralArgs)); + } else { + out->append("$text", + BSON("$search" << ftsQuery.getQuery() << "$language" << ftsQuery.getLanguage() + << "$caseSensitive" << ftsQuery.getCaseSensitive() + << "$diacriticSensitive" << ftsQuery.getDiacriticSensitive())); + } } bool TextMatchExpressionBase::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/expression_text_base.h b/src/mongo/db/matcher/expression_text_base.h index 954d5272d13..92b7b5a8c94 100644 --- a/src/mongo/db/matcher/expression_text_base.h +++ b/src/mongo/db/matcher/expression_text_base.h @@ -60,9 +60,7 @@ public: */ virtual const fts::FTSQuery& getFTSQuery() const = 0; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final { - // TODO SERVER-73676 is this going to be a problem? I don't see how this is unreachable... + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final { MONGO_UNREACHABLE; } diff --git a/src/mongo/db/matcher/expression_tree.cpp b/src/mongo/db/matcher/expression_tree.cpp index 2f3f376931b..ba00bc78ad0 100644 --- a/src/mongo/db/matcher/expression_tree.cpp +++ b/src/mongo/db/matcher/expression_tree.cpp @@ -455,9 +455,12 @@ void NotMatchExpression::serializeNotExpressionToNor(MatchExpression* exp, } void NotMatchExpression::serialize(BSONObjBuilder* out, SerializationOptions opts) const { - // TODO SERVER-73676 respect 'opts.' if (_exp->matchType() == MatchType::AND && _exp->numChildren() == 0) { - out->append("$alwaysFalse", 1); + if (opts.replacementForLiteralArgs) { + out->append("$alwaysFalse", *opts.replacementForLiteralArgs); + } else { + out->append("$alwaysFalse", 1); + } return; } @@ -492,10 +495,15 @@ void NotMatchExpression::serialize(BSONObjBuilder* out, SerializationOptions opt // 1}}. if (auto pathMatch = dynamic_cast<PathMatchExpression*>(expressionToNegate); pathMatch && !dynamic_cast<TextMatchExpressionBase*>(expressionToNegate)) { - const auto path = pathMatch->path(); - BSONObjBuilder pathBob(out->subobjStart(path)); - pathBob.append("$not", - pathMatch->getSerializedRightHandSide(opts.replacementForLiteralArgs)); + auto append = [&](StringData path) { + BSONObjBuilder pathBob(out->subobjStart(path)); + pathBob.append("$not", pathMatch->getSerializedRightHandSide(opts)); + }; + if (opts.redactFieldNames) { + append(opts.redactFieldNamesStrategy(pathMatch->path())); + } else { + append(pathMatch->path()); + } return; } return serializeNotExpressionToNor(expressionToNegate, out, opts); diff --git a/src/mongo/db/matcher/expression_type.h b/src/mongo/db/matcher/expression_type.h index b45b0e90774..3e8eecf836b 100644 --- a/src/mongo/db/matcher/expression_type.h +++ b/src/mongo/db/matcher/expression_type.h @@ -82,8 +82,7 @@ public: debug << "\n"; } - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final { + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final { // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder subBuilder; BSONArrayBuilder arrBuilder(subBuilder.subarrayStart(name())); @@ -256,8 +255,7 @@ public: debug << "\n"; } - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final { + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final { // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder bob; bob.append(name(), _binDataSubType); diff --git a/src/mongo/db/matcher/expression_where_base.cpp b/src/mongo/db/matcher/expression_where_base.cpp index 17568f76321..3508260bb16 100644 --- a/src/mongo/db/matcher/expression_where_base.cpp +++ b/src/mongo/db/matcher/expression_where_base.cpp @@ -47,8 +47,11 @@ void WhereMatchExpressionBase::debugString(StringBuilder& debug, int indentation } void WhereMatchExpressionBase::serialize(BSONObjBuilder* out, SerializationOptions opts) const { - // TODO SERVER-73676 respect 'opts.' - out->appendCode("$where", getCode()); + if (opts.replacementForLiteralArgs) { + out->append("$where", *opts.replacementForLiteralArgs); + } else { + out->appendCode("$where", getCode()); + } } bool WhereMatchExpressionBase::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/parsed_match_expression_for_test.h b/src/mongo/db/matcher/parsed_match_expression_for_test.h index 89e965a45ca..4c4d8a9dc43 100644 --- a/src/mongo/db/matcher/parsed_match_expression_for_test.h +++ b/src/mongo/db/matcher/parsed_match_expression_for_test.h @@ -48,7 +48,12 @@ public: : _obj(fromjson(str)) { _expCtx = make_intrusive<ExpressionContextForTest>(); _expCtx->setCollator(CollatorInterface::cloneCollator(collator)); - StatusWithMatchExpression result = MatchExpressionParser::parse(_obj, _expCtx); + StatusWithMatchExpression result = + MatchExpressionParser::parse(_obj, + _expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kDefaultSpecialFeatures | + MatchExpressionParser::AllowedFeatures::kJavascript); ASSERT_OK(result.getStatus()); _expr = std::move(result.getValue()); } diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp index 6fbeb59be73..9b0c0fa6f74 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp @@ -77,14 +77,14 @@ void InternalSchemaAllElemMatchFromIndexMatchExpression::debugString(StringBuild } BSONObj InternalSchemaAllElemMatchFromIndexMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { + SerializationOptions opts) const { // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder allElemMatchBob; BSONArrayBuilder subArray(allElemMatchBob.subarrayStart(kName)); subArray.append(_index); { BSONObjBuilder eBuilder(subArray.subobjStart()); - _expression->getFilter()->serialize(&eBuilder, {}); + _expression->getFilter()->serialize(&eBuilder, opts); eBuilder.doneFast(); } subArray.doneFast(); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h index 9ca495448f9..1a385142116 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h @@ -78,8 +78,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp index 496dac6a29d..2919bf1a65f 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp @@ -70,7 +70,7 @@ void InternalSchemaEqMatchExpression::debugString(StringBuilder& debug, } BSONObj InternalSchemaEqMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { + SerializationOptions opts) const { // TODO SERVER-73678 respect 'replacementForLiteralArgs.' BSONObjBuilder eqObj; eqObj.appendAs(_rhsElem, kName); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_eq.h b/src/mongo/db/matcher/schema/expression_internal_schema_eq.h index e53110c3aa6..c4cb5f0d235 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_eq.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_eq.h @@ -59,8 +59,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp index 887f382a0ad..4dd56a4884f 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp @@ -77,7 +77,7 @@ void InternalSchemaFmodMatchExpression::debugString(StringBuilder& debug, } BSONObj InternalSchemaFmodMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { + SerializationOptions opts) const { // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objMatchBob; BSONArrayBuilder arrBuilder(objMatchBob.subarrayStart("$_internalSchemaFmod")); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.h b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.h index a48a5ce88c7..46e3f39f860 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.h @@ -58,8 +58,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.cpp index a3b53e6890f..0792b33b2fe 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.cpp @@ -73,7 +73,7 @@ bool InternalSchemaMatchArrayIndexMatchExpression::equivalent(const MatchExpress } BSONObj InternalSchemaMatchArrayIndexMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { + SerializationOptions opts) const { // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objBuilder; { @@ -82,7 +82,7 @@ BSONObj InternalSchemaMatchArrayIndexMatchExpression::getSerializedRightHandSide matchArrayElemSubobj.append("namePlaceholder", _expression->getPlaceholder().value_or("")); { BSONObjBuilder subexprSubObj(matchArrayElemSubobj.subobjStart("expression")); - _expression->getFilter()->serialize(&subexprSubObj, {}); + _expression->getFilter()->serialize(&subexprSubObj, opts); subexprSubObj.doneFast(); } matchArrayElemSubobj.doneFast(); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.h b/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.h index 56d115cc5ab..f3230bf0cbd 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.h @@ -74,8 +74,7 @@ public: return _expression->matchesBSONElement(element, details); } - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; std::unique_ptr<MatchExpression> shallowClone() const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.cpp index 705f726e447..b4d0d069bad 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.cpp @@ -57,7 +57,7 @@ void InternalSchemaNumArrayItemsMatchExpression::debugString(StringBuilder& debu } BSONObj InternalSchemaNumArrayItemsMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { + SerializationOptions opts) const { // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objBuilder; objBuilder.append(_name, _numItems); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.h b/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.h index beb9cadd3f7..24eb787df55 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_num_array_items.h @@ -52,8 +52,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp index e057d5c2d26..d1716c00b4f 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp @@ -62,11 +62,11 @@ void InternalSchemaObjectMatchExpression::debugString(StringBuilder& debug, } BSONObj InternalSchemaObjectMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { + SerializationOptions opts) const { // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objMatchBob; BSONObjBuilder subBob(objMatchBob.subobjStart(kName)); - _sub->serialize(&subBob, {}); + _sub->serialize(&subBob, opts); subBob.doneFast(); return objMatchBob.obj(); } diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h index 44e74fdd8bc..081f3382721 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_object_match.h @@ -50,8 +50,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_str_length.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_str_length.cpp index b643393b548..f5b90ecd350 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_str_length.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_str_length.cpp @@ -57,7 +57,7 @@ void InternalSchemaStrLengthMatchExpression::debugString(StringBuilder& debug, } BSONObj InternalSchemaStrLengthMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { + SerializationOptions opts) const { // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objBuilder; objBuilder.append(_name, _strLen); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_str_length.h b/src/mongo/db/matcher/schema/expression_internal_schema_str_length.h index 9dd2493da99..6292f567ccc 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_str_length.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_str_length.h @@ -62,8 +62,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp index a5dcfd9c9cd..90c7a4b69c4 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp @@ -60,7 +60,7 @@ bool InternalSchemaUniqueItemsMatchExpression::equivalent(const MatchExpression* } BSONObj InternalSchemaUniqueItemsMatchExpression::getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const { + SerializationOptions opts) const { // TODO SERVER-73678 respect 'replacementForLiteralArgs.' BSONObjBuilder bob; bob.append(kName, true); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h index eaf12148879..98f7d779377 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h @@ -86,8 +86,7 @@ public: bool equivalent(const MatchExpression* other) const final; - BSONObj getSerializedRightHandSide( - boost::optional<StringData> replacementForLiteralArgs) const final; + BSONObj getSerializedRightHandSide(SerializationOptions opts) const final; std::unique_ptr<MatchExpression> shallowClone() const final; diff --git a/src/mongo/db/query/query_shape.cpp b/src/mongo/db/query/query_shape.cpp index cc6829a72f4..fcfe0cb361e 100644 --- a/src/mongo/db/query/query_shape.cpp +++ b/src/mongo/db/query/query_shape.cpp @@ -36,4 +36,14 @@ BSONObj predicateShape(const MatchExpression* predicate) { opts.replacementForLiteralArgs = kLiteralArgString; return predicate->serialize(opts); } + +BSONObj predicateShape(const MatchExpression* predicate, + std::function<std::string(StringData)> redactFieldNamesStrategy) { + SerializationOptions opts; + opts.replacementForLiteralArgs = kLiteralArgString; + opts.redactFieldNamesStrategy = redactFieldNamesStrategy; + opts.redactFieldNames = true; + return predicate->serialize(opts); +} + } // namespace mongo::query_shape diff --git a/src/mongo/db/query/query_shape.h b/src/mongo/db/query/query_shape.h index 78733ac54b9..c072fa8b95e 100644 --- a/src/mongo/db/query/query_shape.h +++ b/src/mongo/db/query/query_shape.h @@ -51,4 +51,7 @@ constexpr StringData kLiteralArgString = "?"_sd; */ BSONObj predicateShape(const MatchExpression* predicate); +BSONObj predicateShape(const MatchExpression* predicate, + std::function<std::string(StringData)> redactFieldNamesStrategy); + } // namespace mongo::query_shape diff --git a/src/mongo/db/query/query_shape_test.cpp b/src/mongo/db/query/query_shape_test.cpp index 47c5e2bdea6..c764831200a 100644 --- a/src/mongo/db/query/query_shape_test.cpp +++ b/src/mongo/db/query/query_shape_test.cpp @@ -29,16 +29,26 @@ #include "mongo/bson/bsonmisc.h" #include "mongo/bson/bsonobj.h" +#include "mongo/db/matcher/expression_geo.h" +#include "mongo/db/matcher/extensions_callback_real.h" #include "mongo/db/matcher/parsed_match_expression_for_test.h" -#include "mongo/db/operation_context.h" #include "mongo/db/pipeline/expression_context_for_test.h" -#include "mongo/db/query/optimizer/utils/unit_test_utils.h" -#include "mongo/db/query/query_planner_params.h" #include "mongo/db/query/query_shape.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { + +/** + * Simplistic redaction strategy for testing which appends the field name to the prefix "REDACT_". + */ +std::string redactFieldNameForTest(StringData sd) { + return "REDACT_" + sd.toString(); +} + +static const SerializationOptions literalAndFieldRedactOpts{redactFieldNameForTest, + query_shape::kLiteralArgString}; + void assertShapeIs(std::string filterJson, BSONObj expectedShape) { ParsedMatchExpressionForTest expr(filterJson); ASSERT_BSONOBJ_EQ(expectedShape, query_shape::predicateShape(expr.get())); @@ -48,12 +58,26 @@ void assertShapeIs(std::string filterJson, std::string expectedShapeJson) { return assertShapeIs(filterJson, fromjson(expectedShapeJson)); } +void assertRedactedShapeIs(std::string filterJson, BSONObj expectedShape) { + ParsedMatchExpressionForTest expr(filterJson); + ASSERT_BSONOBJ_EQ(expectedShape, + query_shape::predicateShape(expr.get(), redactFieldNameForTest)); +} + +void assertRedactedShapeIs(std::string filterJson, std::string expectedShapeJson) { + return assertRedactedShapeIs(filterJson, fromjson(expectedShapeJson)); +} + } // namespace TEST(QueryPredicateShape, Equals) { assertShapeIs("{a: 5}", "{a: {$eq: '?'}}"); // Implicit equals assertShapeIs("{a: {$eq: 5}}", "{a: {$eq: '?'}}"); // Explicit equals assertShapeIs("{a: 5, b: 6}", "{$and: [{a: {$eq: '?'}}, {b: {$eq: '?'}}]}"); // implicit $and + assertRedactedShapeIs("{a: 5}", "{REDACT_a: {$eq: '?'}}"); // Implicit equals + assertRedactedShapeIs("{a: {$eq: 5}}", "{REDACT_a: {$eq: '?'}}"); // Explicit equals + assertRedactedShapeIs("{a: 5, b: 6}", + "{$and: [{REDACT_a: {$eq: '?'}}, {REDACT_b: {$eq: '?'}}]}"); } TEST(QueryPredicateShape, Comparisons) { @@ -68,6 +92,8 @@ TEST(QueryPredicateShape, Regex) { assertShapeIs("{a: /a+/i}", BSON("a" << BSON("$regex" << query_shape::kLiteralArgString << "$options" << query_shape::kLiteralArgString))); + assertRedactedShapeIs("{a: /a+/}", + BSON("REDACT_a" << BSON("$regex" << query_shape::kLiteralArgString))); } TEST(QueryPredicateShape, Mod) { @@ -120,6 +146,55 @@ TEST(QueryPredicateShape, ElemMatch) { // ElemMatchValueMatchExpression assertShapeIs("{a: {$elemMatch: {$gt: 5, $lt: 10}}}", "{a: {$elemMatch: {$gt: '?', $lt:'?'}}}"); + + // Nested + assertRedactedShapeIs("{a: {$elemMatch: {$elemMatch: {$gt: 5, $lt: 10}}}}", + "{REDACT_a: {$elemMatch: {$elemMatch: {$gt: '?', $lt:'?'}}}}"); +} + +TEST(QueryPredicateShape, InternalBucketGeoWithinMatchExpression) { + assertRedactedShapeIs( + "{ $_internalBucketGeoWithin: {withinRegion: {$centerSphere: [[0, 0], 10]}, field: \"a\"} " + "}", + "{ $_internalBucketGeoWithin: { withinRegion: { $centerSphere: \"?\" }, " + "field: \"REDACT_a\" } }"); +} + +TEST(QueryPredicateShape, NorMatchExpression) { + assertRedactedShapeIs("{ $nor: [ { a: {$lt: 5} }, { b: {$gt: 4} } ] }", + "{ $nor: [ { REDACT_a: {$lt: \"?\"} }, { REDACT_b: {$gt: \"?\"} } ] }"); +} + +TEST(QueryPredicateShape, NotMatchExpression) { + assertRedactedShapeIs("{ price: { $not: { $gt: 1.99 } } }", + "{ REDACT_price: { $not: { $gt: \"?\" } } }"); + // Test the special case where NotMatchExpression::serialize() reduces to $alwaysFalse. + auto emptyAnd = std::make_unique<AndMatchExpression>(); + const MatchExpression& notExpr = NotMatchExpression(std::move(emptyAnd)); + auto serialized = notExpr.serialize(literalAndFieldRedactOpts); + ASSERT_BSONOBJ_EQ(fromjson("{$alwaysFalse: '?'}"), serialized); +} + +TEST(QueryPredicateShape, SizeMatchExpression) { + assertRedactedShapeIs("{ price: { $size: 2 } }", "{ REDACT_price: { $size: \"?\" } }"); +} + +TEST(QueryPredicateShape, TextMatchExpression) { + TextMatchExpressionBase::TextParams params = {"coffee"}; + auto expr = ExtensionsCallbackNoop().createText(params); + ASSERT_BSONOBJ_EQ(fromjson("{ $text: { $search: \"?\", $language: \"?\", $caseSensitive: " + "\"?\", $diacriticSensitive: \"?\" } }"), + expr->serialize(literalAndFieldRedactOpts)); +} + +TEST(QueryPredicateShape, TwoDPtInAnnulusExpression) { + const MatchExpression& expr = TwoDPtInAnnulusExpression({}, {}); + ASSERT_BSONOBJ_EQ(fromjson("{ $TwoDPtInAnnulusExpression: true }"), + expr.serialize(literalAndFieldRedactOpts)); +} + +TEST(QueryPredicateShape, WhereMatchExpression) { + assertShapeIs("{$where: \"some_code()\"}", "{$where: \"?\"}"); } BSONObj queryShapeForOptimizedExprExpression(std::string exprPredicateJson) { diff --git a/src/mongo/db/query/serialization_options.h b/src/mongo/db/query/serialization_options.h index 2086a25ae8c..4ac22617ecd 100644 --- a/src/mongo/db/query/serialization_options.h +++ b/src/mongo/db/query/serialization_options.h @@ -46,8 +46,15 @@ std::string defaultRedactionStrategy(StringData s) { */ struct SerializationOptions { SerializationOptions() {} + SerializationOptions(bool explain_) : explain(explain_) {} + SerializationOptions(std::function<std::string(StringData)> redactFieldNamesStrategy_, + boost::optional<StringData> replacementForLiteralArgs_) + : replacementForLiteralArgs(replacementForLiteralArgs_), + redactFieldNames(redactFieldNamesStrategy_), + redactFieldNamesStrategy(redactFieldNamesStrategy_) {} + // '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 // set to '?' then the serialization of {a: {$gt: 2}} will result in {a: {$gt: '?'}}, as |