diff options
author | Ted Tuckman <ted.tuckman@mongodb.com> | 2023-03-28 16:20:15 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-03-28 20:30:16 +0000 |
commit | df7e8314403e4e23c80a384e2413d32d66d6f4d2 (patch) | |
tree | 65c43eb80406eff0e3ef570299cac152f8a0c411 /src/mongo/db | |
parent | 3e44409d23b9924ceca60bb2429382fbcc8fc0c3 (diff) | |
download | mongo-df7e8314403e4e23c80a384e2413d32d66d6f4d2.tar.gz |
SERVER-73678 Implement and verify redaction for JsonSchema MatchExpressions
Diffstat (limited to 'src/mongo/db')
19 files changed, 414 insertions, 41 deletions
diff --git a/src/mongo/db/matcher/expression_arity.h b/src/mongo/db/matcher/expression_arity.h index ebe68bd9255..fce1508c224 100644 --- a/src/mongo/db/matcher/expression_arity.h +++ b/src/mongo/db/matcher/expression_arity.h @@ -108,7 +108,6 @@ public: * Serializes each subexpression sequentially in a BSONArray. */ void serialize(BSONObjBuilder* builder, SerializationOptions opts) const final { - // TODO SERVER-73678 respect 'opts'. BSONArrayBuilder exprArray(builder->subarrayStart(name())); for (const auto& expr : _expressions) { BSONObjBuilder exprBuilder(exprArray.subobjStart()); diff --git a/src/mongo/db/matcher/expression_path.h b/src/mongo/db/matcher/expression_path.h index 07db4a167ed..33f4cb751a2 100644 --- a/src/mongo/db/matcher/expression_path.h +++ b/src/mongo/db/matcher/expression_path.h @@ -159,12 +159,6 @@ public: } void serialize(BSONObjBuilder* out, SerializationOptions opts) const override { - // TODO SERVER-73678 do we need to pass 'includePath' or other options to - // `getSerializedRightHandSide()` here? I don't think we need 'includePath' for - // LeafMatchExpression subclasses, but the class comment on PathMatchExpression leaves me a - // 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) { if (opts.redactFieldNames) { diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp index 214c0d31ffb..1551039194b 100644 --- a/src/mongo/db/matcher/expression_serialization_test.cpp +++ b/src/mongo/db/matcher/expression_serialization_test.cpp @@ -37,7 +37,16 @@ #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/matcher/extensions_callback_noop.h" #include "mongo/db/matcher/matcher.h" +#include "mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.h" +#include "mongo/db/matcher/schema/expression_internal_schema_cond.h" +#include "mongo/db/matcher/schema/expression_internal_schema_eq.h" +#include "mongo/db/matcher/schema/expression_internal_schema_fmod.h" +#include "mongo/db/matcher/schema/expression_internal_schema_max_items.h" #include "mongo/db/matcher/schema/expression_internal_schema_max_length.h" +#include "mongo/db/matcher/schema/expression_internal_schema_max_properties.h" +#include "mongo/db/matcher/schema/expression_internal_schema_min_items.h" +#include "mongo/db/matcher/schema/expression_internal_schema_min_length.h" +#include "mongo/db/matcher/schema/expression_internal_schema_min_properties.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/unittest/unittest.h" @@ -1846,5 +1855,275 @@ TEST(SerializeInternalBinDataSubType, ExpressionBinDataSubTypeSerializesCorrectl ASSERT_TRUE(original.matches(obj)); } +std::string redactFieldNameForTest(StringData s) { + return str::stream() << "HASH(" << s << ")"; +} +TEST(SerializeInternalSchema, AllowedPropertiesRedactsCorrectly) { + + auto query = fromjson( + "{$_internalSchemaAllowedProperties: {properties: ['a', 'b']," + "namePlaceholder: 'i', patternProperties: [], otherwise: {i: 0}}}"); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + auto objMatch = MatchExpressionParser::parse(query, expCtx); + ASSERT_OK(objMatch.getStatus()); + + SerializationOptions opts; + opts.redactFieldNames = true; + opts.redactFieldNamesStrategy = redactFieldNameForTest; + opts.replacementForLiteralArgs = "?"; + + ASSERT_BSONOBJ_EQ( + fromjson( + "{ $_internalSchemaAllowedProperties: { properties: \"?\", namePlaceholder: \"?\", " + "patternProperties: [], otherwise: { \"HASH(i)\": { $eq: \"?\" } } } }"), + objMatch.getValue()->serialize(opts)); +} + +/** + * Helper function for parsing and creating MatchExpressions. + */ +std::unique_ptr<InternalSchemaCondMatchExpression> createCondMatchExpression(BSONObj condition, + BSONObj thenBranch, + BSONObj elseBranch) { + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + auto conditionExpr = MatchExpressionParser::parse(condition, expCtx); + ASSERT_OK(conditionExpr.getStatus()); + auto thenBranchExpr = MatchExpressionParser::parse(thenBranch, expCtx); + ASSERT_OK(thenBranchExpr.getStatus()); + auto elseBranchExpr = MatchExpressionParser::parse(elseBranch, expCtx); + + std::array<std::unique_ptr<MatchExpression>, 3> expressions = { + {std::move(conditionExpr.getValue()), + std::move(thenBranchExpr.getValue()), + std::move(elseBranchExpr.getValue())}}; + + auto cond = std::make_unique<InternalSchemaCondMatchExpression>(std::move(expressions)); + + return cond; +} + +TEST(SerializeInternalSchema, CondMatchRedactsCorrectly) { + SerializationOptions opts; + opts.redactFieldNames = true; + opts.replacementForLiteralArgs = "?"; + opts.redactFieldNamesStrategy = redactFieldNameForTest; + auto conditionQuery = BSON("age" << BSON("$lt" << 18)); + auto thenQuery = BSON("job" + << "student"); + auto elseQuery = BSON("job" + << "engineer"); + auto cond = createCondMatchExpression(conditionQuery, thenQuery, elseQuery); + BSONObjBuilder bob; + cond->serialize(&bob, opts); + auto expectedResult = + BSON("$_internalSchemaCond" << BSON_ARRAY(BSON("HASH(age)" << BSON("$lt" + << "?")) + << BSON("HASH(job)" << BSON("$eq" + << "?")) + << BSON("HASH(job)" << BSON("$eq" + << "?")))); + ASSERT_BSONOBJ_EQ(expectedResult, bob.done()); +} + +TEST(SerializeInternalSchema, FmodMatchRedactsCorrectly) { + InternalSchemaFmodMatchExpression m("a"_sd, Decimal128(1.7), Decimal128(2)); + SerializationOptions opts; + opts.replacementForLiteralArgs = "?"; + BSONObjBuilder bob; + m.serialize(&bob, opts); + ASSERT_BSONOBJ_EQ(BSON("a" << BSON("$_internalSchemaFmod" << BSON_ARRAY("?" + << "?"))), + bob.done()); +} + +TEST(SerializeInternalSchema, MatchArrayIndexRedactsCorrectly) { + auto query = fromjson( + "{foo: {$_internalSchemaMatchArrayIndex:" + "{index: 0, namePlaceholder: 'i', expression: {i: {$type: 'number'}}}}}"); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + auto objMatch = MatchExpressionParser::parse(query, expCtx); + ASSERT_OK(objMatch.getStatus()); + + BSONObjBuilder bob; + SerializationOptions opts; + opts.redactFieldNames = true; + opts.redactFieldNamesStrategy = redactFieldNameForTest; + opts.replacementForLiteralArgs = "?"; + objMatch.getValue()->serialize(&bob, opts); + + ASSERT_BSONOBJ_EQ(bob.done(), + BSON("HASH(foo)" << BSON("$_internalSchemaMatchArrayIndex" + << BSON("index" + << "?" + << "namePlaceholder" + << "HASH(i)" + << "expression" + << BSON("HASH(i)" << BSON("$type" + << "?")))))); +} + +TEST(SerializeInternalSchema, MaxItemsRedactsCorrectly) { + InternalSchemaMaxItemsMatchExpression maxItems("a.b"_sd, 2); + SerializationOptions opts; + opts.redactFieldNames = true; + opts.replacementForLiteralArgs = "?"; + opts.redactFieldNamesStrategy = redactFieldNameForTest; + + ASSERT_BSONOBJ_EQ(maxItems.getSerializedRightHandSide(opts), + BSON("$_internalSchemaMaxItems" + << "?")); +} + +TEST(SerializeInternalSchema, MaxLengthRedactsCorrectly) { + InternalSchemaMaxLengthMatchExpression maxLength("a"_sd, 2); + SerializationOptions opts; + opts.replacementForLiteralArgs = "?"; + ASSERT_BSONOBJ_EQ(maxLength.getSerializedRightHandSide(opts), + BSON("$_internalSchemaMaxLength" + << "?")); +} + +TEST(SerializeInternalSchema, MinItemsRedactsCorrectly) { + InternalSchemaMinItemsMatchExpression minItems("a.b"_sd, 2); + SerializationOptions opts; + opts.redactFieldNames = true; + opts.replacementForLiteralArgs = "?"; + opts.redactFieldNamesStrategy = redactFieldNameForTest; + + ASSERT_BSONOBJ_EQ(minItems.getSerializedRightHandSide(opts), + BSON("$_internalSchemaMinItems" + << "?")); +} + +TEST(SerializeInternalSchema, MinLengthRedactsCorrectly) { + InternalSchemaMinLengthMatchExpression minLength("a"_sd, 2); + SerializationOptions opts; + opts.replacementForLiteralArgs = "?"; + ASSERT_BSONOBJ_EQ(minLength.getSerializedRightHandSide(opts), + BSON("$_internalSchemaMinLength" + << "?")); +} + +TEST(SerializeInternalSchema, MinPropertiesRedactsCorrectly) { + InternalSchemaMinPropertiesMatchExpression minProperties(5); + SerializationOptions opts; + opts.replacementForLiteralArgs = "?"; + + BSONObjBuilder bob; + minProperties.serialize(&bob, opts); + ASSERT_BSONOBJ_EQ(bob.done(), + BSON("$_internalSchemaMinProperties" + << "?")); +} + +TEST(SerializeInternalSchema, ObjectMatchRedactsCorrectly) { + SerializationOptions opts; + opts.redactFieldNames = true; + opts.redactFieldNamesStrategy = redactFieldNameForTest; + opts.replacementForLiteralArgs = "?"; + auto query = fromjson( + " {a: {$_internalSchemaObjectMatch: {" + " c: {$eq: 3}" + " }}}"); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + auto objMatch = MatchExpressionParser::parse(query, expCtx); + ASSERT_OK(objMatch.getStatus()); + + ASSERT_BSONOBJ_EQ( + objMatch.getValue()->serialize(opts), + BSON("HASH(a)" << BSON("$_internalSchemaObjectMatch" << BSON("HASH(c)" << BSON("$eq" + << "?"))))); +} + +TEST(SerializeInternalSchema, RootDocEqRedactsCorrectly) { + auto query = fromjson("{$_internalSchemaRootDocEq: {a:1, b: {c: 1, d: [1]}}}"); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + SerializationOptions opts; + opts.redactFieldNames = true; + opts.redactFieldNamesStrategy = redactFieldNameForTest; + opts.replacementForLiteralArgs = "?"; + auto objMatch = MatchExpressionParser::parse(query, expCtx); + ASSERT_OK(objMatch.getStatus()); + + ASSERT_BSONOBJ_EQ( + objMatch.getValue()->serialize(opts), + BSON("$_internalSchemaRootDocEq" << BSON("HASH(a)" + << "?" + << "HASH(b)" + << BSON("HASH(c)" + << "?" + << "HASH(d)" << BSON_ARRAY("?"))))); +} + +TEST(SerializeInternalSchema, BinDataEncryptedTypeRedactsCorrectly) { + MatcherTypeSet typeSet; + typeSet.bsonTypes.insert(BSONType::String); + typeSet.bsonTypes.insert(BSONType::Date); + InternalSchemaBinDataEncryptedTypeExpression e("a"_sd, std::move(typeSet)); + SerializationOptions opts; + opts.replacementForLiteralArgs = "?"; + ASSERT_BSONOBJ_EQ(BSON("$_internalSchemaBinDataEncryptedType" + << "?"), + e.getSerializedRightHandSide(opts)); +} + +TEST(SerializeInternalSchema, BinDataFLE2EncryptedTypeRedactsCorrectly) { + InternalSchemaBinDataFLE2EncryptedTypeExpression e("ssn"_sd, BSONType::String); + SerializationOptions opts; + opts.replacementForLiteralArgs = "?"; + ASSERT_BSONOBJ_EQ(BSON("$_internalSchemaBinDataFLE2EncryptedType" + << "?"), + e.getSerializedRightHandSide(opts)); +} + +TEST(SerializesInternalSchema, MaxPropertiesRedactsCorrectly) { + InternalSchemaMaxPropertiesMatchExpression maxProperties(5); + SerializationOptions opts; + opts.replacementForLiteralArgs = "?"; + + BSONObjBuilder bob; + maxProperties.serialize(&bob, opts); + ASSERT_BSONOBJ_EQ(bob.done(), + BSON("$_internalSchemaMaxProperties" + << "?")); +} + +TEST(SerializesInternalSchema, EqRedactsCorrectly) { + SerializationOptions opts; + opts.redactFieldNamesStrategy = redactFieldNameForTest; + opts.redactFieldNames = true; + opts.replacementForLiteralArgs = "?"; + auto query = fromjson("{$_internalSchemaEq: {a:1, b: {c: 1, d: [1]}}}"); + BSONObjBuilder bob; + InternalSchemaEqMatchExpression e("a"_sd, query.firstElement()); + e.serialize(&bob, opts); + ASSERT_BSONOBJ_EQ(bob.done(), + BSON("HASH(a)" << BSON("$_internalSchemaEq" + << BSON("HASH(a)" + << "?" + << "HASH(b)" + << BSON("HASH(c)" + << "?" + << "HASH(d)" << BSON_ARRAY("?")))))); +} + +TEST(InternalSchemaAllElemMatchFromIndexMatchExpression, RedactsExpressionCorrectly) { + auto query = fromjson("{a: {$_internalSchemaAllElemMatchFromIndex: [2, {a: {$lt: 5}}]}}"); + boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); + auto expr = MatchExpressionParser::parse(query, expCtx); + ASSERT_OK(expr.getStatus()); + auto elemMatchExpr = dynamic_cast<const InternalSchemaAllElemMatchFromIndexMatchExpression*>( + expr.getValue().get()); + + SerializationOptions opts; + opts.redactFieldNames = true; + opts.redactFieldNamesStrategy = redactFieldNameForTest; + opts.replacementForLiteralArgs = "?"; + + ASSERT_BSONOBJ_EQ(BSON("$_internalSchemaAllElemMatchFromIndex" + << BSON_ARRAY("?" << BSON("HASH(a)" << BSON("$lt" + << "?")))), + elemMatchExpr->getSerializedRightHandSide(opts)); +} } // namespace } // namespace mongo diff --git a/src/mongo/db/matcher/expression_type.h b/src/mongo/db/matcher/expression_type.h index 75f6155f7fa..bc2a758caf7 100644 --- a/src/mongo/db/matcher/expression_type.h +++ b/src/mongo/db/matcher/expression_type.h @@ -77,8 +77,11 @@ public: } BSONObj getSerializedRightHandSide(SerializationOptions opts) const final { - // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder subBuilder; + if (opts.replacementForLiteralArgs) { + subBuilder.append(name(), opts.replacementForLiteralArgs.get()); + return subBuilder.obj(); + } BSONArrayBuilder arrBuilder(subBuilder.subarrayStart(name())); _typeSet.toBSONArray(&arrBuilder); arrBuilder.doneFast(); @@ -244,9 +247,12 @@ public: } BSONObj getSerializedRightHandSide(SerializationOptions opts) const final { - // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder bob; - bob.append(name(), _binDataSubType); + if (opts.replacementForLiteralArgs) { + bob.append(name(), opts.replacementForLiteralArgs.get()); + } else { + bob.append(name(), _binDataSubType); + } return bob.obj(); } diff --git a/src/mongo/db/matcher/expression_type_test.cpp b/src/mongo/db/matcher/expression_type_test.cpp index 0ddee9a24b5..d934f8a402c 100644 --- a/src/mongo/db/matcher/expression_type_test.cpp +++ b/src/mongo/db/matcher/expression_type_test.cpp @@ -217,6 +217,15 @@ TEST(ExpressionTypeTest, InternalSchemaTypeExprWithMultipleTypesMatchesAllSuchTy ASSERT_FALSE(expr.matchesBSON(fromjson("{a: ['str']}"))); } +TEST(ExpressionTypeTest, RedactsTypesCorrectly) { + TypeMatchExpression type(""_sd, String); + SerializationOptions opts; + opts.replacementForLiteralArgs = "?"; + ASSERT_BSONOBJ_EQ(BSON("$type" + << "?"), + type.getSerializedRightHandSide(opts)); +} + TEST(ExpressionBinDataSubTypeTest, MatchesBinDataGeneral) { BSONObj match = BSON("a" << BSONBinData(nullptr, 0, BinDataType::BinDataGeneral)); BSONObj notMatch = BSON("a" << BSONBinData(nullptr, 0, BinDataType::bdtCustom)); @@ -300,6 +309,15 @@ TEST(ExpressionBinDataSubTypeTest, Equivalent) { ASSERT(!e1.equivalent(&e3)); } +TEST(ExpressionBinDataSubTypeTest, RedactsCorrectly) { + InternalSchemaBinDataSubTypeExpression e("b"_sd, BinDataType::newUUID); + SerializationOptions opts; + opts.replacementForLiteralArgs = "?"; + ASSERT_BSONOBJ_EQ(BSON("$_internalSchemaBinDataSubType" + << "?"), + e.getSerializedRightHandSide(opts)); +} + TEST(InternalSchemaBinDataEncryptedTypeTest, DoesNotTraverseLeafArrays) { MatcherTypeSet typeSet; typeSet.bsonTypes.insert(BSONType::String); 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 8c533d8cc95..97ceacf1e01 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 @@ -78,10 +78,13 @@ void InternalSchemaAllElemMatchFromIndexMatchExpression::debugString(StringBuild BSONObj InternalSchemaAllElemMatchFromIndexMatchExpression::getSerializedRightHandSide( SerializationOptions opts) const { - // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder allElemMatchBob; BSONArrayBuilder subArray(allElemMatchBob.subarrayStart(kName)); - subArray.append(_index); + if (opts.replacementForLiteralArgs) { + subArray.append(opts.replacementForLiteralArgs.get()); + } else { + subArray.append(_index); + } { BSONObjBuilder eBuilder(subArray.subobjStart()); _expression->getFilter()->serialize(&eBuilder, opts); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp index dd1be3e030f..46e498fb67c 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index_test.cpp @@ -136,6 +136,5 @@ DEATH_TEST_REGEX(InternalSchemaAllElemMatchFromIndexMatchExpression, ASSERT_EQ(objMatch.getValue()->numChildren(), 1); ASSERT_THROWS_CODE(objMatch.getValue()->getChild(1), AssertionException, 6400200); } - } // namespace } // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.cpp index 2a656b15269..c9a76a5d61f 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.cpp @@ -123,20 +123,27 @@ bool InternalSchemaAllowedPropertiesMatchExpression::_matchesBSONObj(const BSONO void InternalSchemaAllowedPropertiesMatchExpression::serialize(BSONObjBuilder* builder, SerializationOptions opts) const { - // TODO SERVER-73678 respect 'opts'. BSONObjBuilder expressionBuilder( builder->subobjStart(InternalSchemaAllowedPropertiesMatchExpression::kName)); std::vector<StringData> sortedProperties(_properties.begin(), _properties.end()); std::sort(sortedProperties.begin(), sortedProperties.end()); - expressionBuilder.append("properties", sortedProperties); - - expressionBuilder.append("namePlaceholder", _namePlaceholder); + if (opts.replacementForLiteralArgs) { + expressionBuilder.append("properties", opts.replacementForLiteralArgs.get()); + expressionBuilder.append("namePlaceholder", opts.replacementForLiteralArgs.get()); + } else { + expressionBuilder.append("properties", sortedProperties); + expressionBuilder.append("namePlaceholder", _namePlaceholder); + } BSONArrayBuilder patternPropertiesBuilder(expressionBuilder.subarrayStart("patternProperties")); for (auto&& item : _patternProperties) { BSONObjBuilder itemBuilder(patternPropertiesBuilder.subobjStart()); - itemBuilder.appendRegex("regex", item.first.rawRegex); + if (opts.replacementForLiteralArgs) { + itemBuilder.appendRegex("regex", opts.replacementForLiteralArgs.get()); + } else { + itemBuilder.appendRegex("regex", item.first.rawRegex); + } BSONObjBuilder subexpressionBuilder(itemBuilder.subobjStart("expression")); item.second->getFilter()->serialize(&subexpressionBuilder, opts); 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 d28ad725d00..d3e57b11bb2 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_eq.cpp @@ -64,8 +64,18 @@ void InternalSchemaEqMatchExpression::debugString(StringBuilder& debug, BSONObj InternalSchemaEqMatchExpression::getSerializedRightHandSide( SerializationOptions opts) const { - // TODO SERVER-73678 respect 'replacementForLiteralArgs.' BSONObjBuilder eqObj; + if (opts.redactFieldNames || opts.replacementForLiteralArgs) { + if (_rhsElem.isABSONObj()) { + BSONObjBuilder exprSpec(eqObj.subobjStart(kName)); + opts.redactObjToBuilder(&exprSpec, _rhsElem.Obj()); + exprSpec.done(); + return eqObj.obj(); + } else if (opts.replacementForLiteralArgs) { + // If the element is not an object it must be a literal. + return BSON(kName << opts.replacementForLiteralArgs.get()); + } + } eqObj.appendAs(_rhsElem, kName); return eqObj.obj(); } 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 df257512525..93280d7cb5a 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_fmod.cpp @@ -73,11 +73,16 @@ void InternalSchemaFmodMatchExpression::debugString(StringBuilder& debug, BSONObj InternalSchemaFmodMatchExpression::getSerializedRightHandSide( SerializationOptions opts) const { - // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objMatchBob; BSONArrayBuilder arrBuilder(objMatchBob.subarrayStart("$_internalSchemaFmod")); - arrBuilder.append(_divisor); - arrBuilder.append(_remainder); + // Divisor and Remainder are always literals. + if (opts.replacementForLiteralArgs) { + arrBuilder.append(opts.replacementForLiteralArgs.get()); + arrBuilder.append(opts.replacementForLiteralArgs.get()); + } else { + arrBuilder.append(_divisor); + arrBuilder.append(_remainder); + } arrBuilder.doneFast(); return objMatchBob.obj(); } 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 115c95eafb8..2f000c524e9 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 @@ -68,12 +68,20 @@ bool InternalSchemaMatchArrayIndexMatchExpression::equivalent(const MatchExpress BSONObj InternalSchemaMatchArrayIndexMatchExpression::getSerializedRightHandSide( SerializationOptions opts) const { - // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objBuilder; { BSONObjBuilder matchArrayElemSubobj(objBuilder.subobjStart(kName)); - matchArrayElemSubobj.append("index", _index); - matchArrayElemSubobj.append("namePlaceholder", _expression->getPlaceholder().value_or("")); + if (opts.replacementForLiteralArgs) { + matchArrayElemSubobj.append("index", opts.replacementForLiteralArgs.get()); + } else { + matchArrayElemSubobj.append("index", _index); + } + if (auto placeHolder = _expression->getPlaceholder()) { + matchArrayElemSubobj.append("namePlaceholder", + opts.serializeFieldName(placeHolder.get())); + } else { + matchArrayElemSubobj.append("namePlaceholder", ""); + } { BSONObjBuilder subexprSubObj(matchArrayElemSubobj.subobjStart("expression")); _expression->getFilter()->serialize(&subexprSubObj, opts); 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 9871c44a1c9..3ef7921faf3 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 @@ -52,9 +52,12 @@ void InternalSchemaNumArrayItemsMatchExpression::debugString(StringBuilder& debu BSONObj InternalSchemaNumArrayItemsMatchExpression::getSerializedRightHandSide( SerializationOptions opts) const { - // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objBuilder; - objBuilder.append(_name, _numItems); + if (opts.replacementForLiteralArgs) { + objBuilder.append(_name, opts.replacementForLiteralArgs.get()); + } else { + objBuilder.append(_name, _numItems); + } return objBuilder.obj(); } diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp index e086cc7f34b..94fa8206616 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp @@ -44,8 +44,11 @@ void InternalSchemaNumPropertiesMatchExpression::debugString(StringBuilder& debu void InternalSchemaNumPropertiesMatchExpression::serialize(BSONObjBuilder* out, SerializationOptions opts) const { - // TODO SERVER-73678 respect 'opts'. - out->append(_name, _numProperties); + if (opts.replacementForLiteralArgs) { + out->append(_name, opts.replacementForLiteralArgs.get()); + } else { + out->append(_name, _numProperties); + } } bool InternalSchemaNumPropertiesMatchExpression::equivalent(const MatchExpression* other) const { 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 1bf30c432c0..8e8bf6c6f7a 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 @@ -64,7 +64,6 @@ void InternalSchemaObjectMatchExpression::debugString(StringBuilder& debug, BSONObj InternalSchemaObjectMatchExpression::getSerializedRightHandSide( SerializationOptions opts) const { - // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objMatchBob; BSONObjBuilder subBob(objMatchBob.subobjStart(kName)); _sub->serialize(&subBob, opts); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp index 18d1fe747e1..f9e40a3856d 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp @@ -49,9 +49,8 @@ void InternalSchemaRootDocEqMatchExpression::debugString(StringBuilder& debug, void InternalSchemaRootDocEqMatchExpression::serialize(BSONObjBuilder* out, SerializationOptions opts) const { - // TODO SERVER-73678 respect 'opts.' BSONObjBuilder subObj(out->subobjStart(kName)); - subObj.appendElements(_rhsObj); + opts.redactObjToBuilder(&subObj, _rhsObj); subObj.doneFast(); } 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 97031addb3c..bb8d3351e87 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 @@ -52,9 +52,12 @@ void InternalSchemaStrLengthMatchExpression::debugString(StringBuilder& debug, BSONObj InternalSchemaStrLengthMatchExpression::getSerializedRightHandSide( SerializationOptions opts) const { - // TODO SERVER-73678 respect 'replacementForLiteralArgs'. BSONObjBuilder objBuilder; - objBuilder.append(_name, _strLen); + if (opts.replacementForLiteralArgs) { + objBuilder.append(opts.serializeFieldName(_name), opts.replacementForLiteralArgs.get()); + } else { + objBuilder.append(opts.serializeFieldName(_name), _strLen); + } return objBuilder.obj(); } 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 05ea89e39b4..4915d1ba2ab 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 @@ -55,7 +55,6 @@ bool InternalSchemaUniqueItemsMatchExpression::equivalent(const MatchExpression* BSONObj InternalSchemaUniqueItemsMatchExpression::getSerializedRightHandSide( SerializationOptions opts) const { - // TODO SERVER-73678 respect 'replacementForLiteralArgs.' BSONObjBuilder bob; bob.append(kName, true); return bob.obj(); diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 5672003d034..c239ab05d1e 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -2390,12 +2390,7 @@ Value ExpressionObject::serialize(SerializationOptions options) const { } MutableDocument outputDoc; for (auto&& pair : _expressions) { - if (options.redactFieldNames) { - outputDoc.addField(options.redactFieldNamesStrategy(pair.first), - pair.second->serialize(options)); - } else { - outputDoc.addField(pair.first, pair.second->serialize(options)); - } + outputDoc.addField(options.serializeFieldName(pair.first), pair.second->serialize(options)); } return outputDoc.freezeToValue(); } diff --git a/src/mongo/db/query/serialization_options.h b/src/mongo/db/query/serialization_options.h index 30efc9294bf..6858ddd0c30 100644 --- a/src/mongo/db/query/serialization_options.h +++ b/src/mongo/db/query/serialization_options.h @@ -29,6 +29,8 @@ #pragma once #include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/exec/document_value/value.h" #include "mongo/db/query/explain_options.h" #include "mongo/util/assert_util.h" @@ -73,6 +75,48 @@ struct SerializationOptions { return str.toString(); } + // Helper functions for redacting BSONObj. Does not take into account anything to do with MQL + // semantics, redacts all field names and literals in the passed in obj. + void redactArrayToBuilder(BSONArrayBuilder* bab, std::vector<BSONElement> array) { + for (const auto& elem : array) { + if (elem.type() == BSONType::Object) { + BSONObjBuilder subObj(bab->subobjStart()); + redactObjToBuilder(&subObj, elem.Obj()); + subObj.done(); + } else if (elem.type() == BSONType::Array) { + BSONArrayBuilder subArr(bab->subarrayStart()); + redactArrayToBuilder(&subArr, elem.Array()); + subArr.done(); + } else { + if (replacementForLiteralArgs) { + bab->append(replacementForLiteralArgs.get()); + } else { + bab->append(elem); + } + } + } + } + void redactObjToBuilder(BSONObjBuilder* bob, BSONObj objToRedact) { + for (const auto& elem : objToRedact) { + auto fieldName = serializeFieldName(elem.fieldName()); + if (elem.type() == BSONType::Object) { + BSONObjBuilder subObj(bob->subobjStart(fieldName)); + redactObjToBuilder(&subObj, elem.Obj()); + subObj.done(); + } else if (elem.type() == BSONType::Array) { + BSONArrayBuilder subArr(bob->subarrayStart(fieldName)); + redactArrayToBuilder(&subArr, elem.Array()); + subArr.done(); + } else { + if (replacementForLiteralArgs) { + bob->append(fieldName, replacementForLiteralArgs.get()); + } else { + bob->appendAs(elem, fieldName); + } + } + } + } + template <class T> Value serializeLiteralValue(T n) { if (replacementForLiteralArgs) { |