From 4d1ce1b892eef6f3926548a7b34d020a02eaee8a Mon Sep 17 00:00:00 2001 From: Charlie Swanson Date: Wed, 27 Nov 2019 21:22:40 +0000 Subject: SERVER-43349 Accommodate double $nots during serialization --- src/mongo/db/exec/geo_near.cpp | 2 +- src/mongo/db/matcher/expression.cpp | 2 +- src/mongo/db/matcher/expression.h | 5 +- src/mongo/db/matcher/expression_algo_test.cpp | 54 +++---- src/mongo/db/matcher/expression_always_boolean.h | 2 +- src/mongo/db/matcher/expression_arity.h | 6 +- src/mongo/db/matcher/expression_array.cpp | 9 +- src/mongo/db/matcher/expression_expr.cpp | 2 +- src/mongo/db/matcher/expression_expr.h | 2 +- src/mongo/db/matcher/expression_expr_test.cpp | 2 +- src/mongo/db/matcher/expression_geo.cpp | 2 +- .../matcher/expression_internal_expr_eq_test.cpp | 2 +- src/mongo/db/matcher/expression_optimize_test.cpp | 22 +-- src/mongo/db/matcher/expression_path.h | 8 +- .../db/matcher/expression_serialization_test.cpp | 162 ++++++++++++++++++++- src/mongo/db/matcher/expression_text_base.cpp | 2 +- src/mongo/db/matcher/expression_text_base.h | 2 +- src/mongo/db/matcher/expression_tree.cpp | 119 ++++++--------- src/mongo/db/matcher/expression_tree.h | 16 +- src/mongo/db/matcher/expression_where_base.cpp | 2 +- src/mongo/db/matcher/expression_where_base.h | 2 +- src/mongo/db/matcher/rewrite_expr_test.cpp | 2 +- .../db/matcher/schema/array_keywords_test.cpp | 8 +- src/mongo/db/matcher/schema/assert_serializes_to.h | 44 ++++++ .../db/matcher/schema/encrypt_keyword_test.cpp | 8 +- ...n_internal_schema_all_elem_match_from_index.cpp | 2 +- ...pression_internal_schema_allowed_properties.cpp | 9 +- ...expression_internal_schema_allowed_properties.h | 2 +- ...xpression_internal_schema_match_array_index.cpp | 4 +- .../expression_internal_schema_num_properties.cpp | 5 +- .../expression_internal_schema_num_properties.h | 2 +- .../expression_internal_schema_object_match.cpp | 2 +- .../expression_internal_schema_root_doc_eq.cpp | 3 +- .../expression_internal_schema_root_doc_eq.h | 2 +- .../expression_internal_schema_unique_items.cpp | 2 +- .../schema/expression_internal_schema_xor.cpp | 4 +- .../schema/expression_internal_schema_xor.h | 2 +- .../db/matcher/schema/logical_keywords_test.cpp | 8 +- .../db/matcher/schema/object_keywords_test.cpp | 12 +- .../db/matcher/schema/scalar_keywords_test.cpp | 8 +- src/mongo/db/query/canonical_query.cpp | 2 +- src/mongo/db/query/projection_ast_util.cpp | 2 +- 42 files changed, 354 insertions(+), 204 deletions(-) create mode 100644 src/mongo/db/matcher/schema/assert_serializes_to.h (limited to 'src/mongo') diff --git a/src/mongo/db/exec/geo_near.cpp b/src/mongo/db/exec/geo_near.cpp index b91f1cbfbb3..827439b5a6a 100644 --- a/src/mongo/db/exec/geo_near.cpp +++ b/src/mongo/db/exec/geo_near.cpp @@ -473,7 +473,7 @@ public: TwoDPtInAnnulusExpression(const R2Annulus& annulus, StringData twoDPath) : LeafMatchExpression(INTERNAL_2D_POINT_IN_ANNULUS, twoDPath), _annulus(annulus) {} - void serialize(BSONObjBuilder* out) const final { + void serialize(BSONObjBuilder* out, bool includePath) const final { out->append("TwoDPtInAnnulusExpression", true); } diff --git a/src/mongo/db/matcher/expression.cpp b/src/mongo/db/matcher/expression.cpp index 649eb1a6e77..c035e417848 100644 --- a/src/mongo/db/matcher/expression.cpp +++ b/src/mongo/db/matcher/expression.cpp @@ -44,7 +44,7 @@ MatchExpression::MatchExpression(MatchType type) : _matchType(type) {} std::string MatchExpression::toString() const { BSONObjBuilder bob; - serialize(&bob); + serialize(&bob, true); return bob.obj().toString(); } diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h index a7aa51b7d33..4705c2cdf32 100644 --- a/src/mongo/db/matcher/expression.h +++ b/src/mongo/db/matcher/expression.h @@ -291,9 +291,10 @@ public: /** * Serialize the MatchExpression to BSON, appending to 'out'. Output of this method is expected * to be a valid query object, that, when parsed, produces a logically equivalent - * MatchExpression. + * MatchExpression. If 'includePath' is false then the serialization should assume it's in a + * context where the path has been serialized elsewhere, such as within an $elemMatch value. */ - virtual void serialize(BSONObjBuilder* out) const = 0; + virtual void serialize(BSONObjBuilder* out, bool includePath = true) const = 0; /** * Returns true if this expression will always evaluate to false, such as an $or with no diff --git a/src/mongo/db/matcher/expression_algo_test.cpp b/src/mongo/db/matcher/expression_algo_test.cpp index 78f12d5157e..68075fbb396 100644 --- a/src/mongo/db/matcher/expression_algo_test.cpp +++ b/src/mongo/db/matcher/expression_algo_test.cpp @@ -822,11 +822,11 @@ TEST(SplitMatchExpression, AndWithSplittableChildrenIsSplittable) { ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{a: {$eq: 1}}")); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{b: {$eq: 1}}")); @@ -844,11 +844,11 @@ TEST(SplitMatchExpression, NorWithIndependentChildrenIsSplittable) { ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$nor: [{a: {$eq: 1}}]}")); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$nor: [{b: {$eq: 1}}]}")); @@ -866,7 +866,7 @@ TEST(SplitMatchExpression, NotWithIndependentChildIsSplittable) { ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{x: {$not: {$gt: 4}}}")); ASSERT_FALSE(splitExpr.second); @@ -884,7 +884,7 @@ TEST(SplitMatchExpression, OrWithOnlyIndependentChildrenIsNotSplittable) { ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder bob; - splitExpr.second->serialize(&bob); + splitExpr.second->serialize(&bob, true); ASSERT_FALSE(splitExpr.first); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$or: [{a: {$eq: 1}}, {b: {$eq: 1}}]}")); @@ -905,11 +905,11 @@ TEST(SplitMatchExpression, ComplexMatchExpressionSplitsCorrectly) { ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{'a.b': {$eq: 3}}, {'a.b.c': {$eq: 4}}]}")); ASSERT_BSONOBJ_EQ(secondBob.obj(), @@ -930,11 +930,11 @@ TEST(SplitMatchExpression, ShouldNotExtractPrefixOfDottedPathAsIndependent) { ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{'a.c': {$eq: 1}}")); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{$and: [{a: {$eq: 1}}, {'a.b': {$eq: 1}}]}")); @@ -952,7 +952,7 @@ TEST(SplitMatchExpression, ShouldMoveIndependentLeafPredicateAcrossRename) { ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{b: {$eq: 1}}")); ASSERT_FALSE(splitExpr.second.get()); @@ -970,7 +970,7 @@ TEST(SplitMatchExpression, ShouldMoveIndependentAndPredicateAcrossRename) { ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$and: [{c: {$eq: 1}}, {b: {$eq: 2}}]}")); ASSERT_FALSE(splitExpr.second.get()); @@ -988,12 +988,12 @@ TEST(SplitMatchExpression, ShouldSplitPartiallyDependentAndPredicateAcrossRename ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$eq: 1}}")); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{b: {$eq: 2}}")); } @@ -1009,12 +1009,12 @@ TEST(SplitMatchExpression, ShouldSplitPartiallyDependentComplexPredicateMultiple ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{d: {$eq: 2}}, {e: {$eq: 3}}]}")); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$eq: 1}}")); } @@ -1031,12 +1031,12 @@ TEST(SplitMatchExpression, ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{$or: [{x: {$eq: 2}}, {y: {$eq: 3}}]}")); ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$eq: 1}}")); } @@ -1054,7 +1054,7 @@ TEST(SplitMatchExpression, ShouldNotMoveElemMatchObjectAcrossRename) { ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$elemMatch: {b: {$eq: 3}}}}")); } @@ -1072,7 +1072,7 @@ TEST(SplitMatchExpression, ShouldNotMoveElemMatchValueAcrossRename) { ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$elemMatch: {$eq: 3}}}")); } @@ -1088,7 +1088,7 @@ TEST(SplitMatchExpression, ShouldMoveTypeAcrossRename) { ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$type: [16]}}")); ASSERT_FALSE(splitExpr.second.get()); @@ -1108,7 +1108,7 @@ TEST(SplitMatchExpression, ShouldNotMoveSizeAcrossRename) { ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$size: 3}}")); } @@ -1126,7 +1126,7 @@ TEST(SplitMatchExpression, ShouldNotMoveMinItemsAcrossRename) { ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$_internalSchemaMinItems: 3}}")); } @@ -1144,7 +1144,7 @@ TEST(SplitMatchExpression, ShouldNotMoveMaxItemsAcrossRename) { ASSERT_TRUE(splitExpr.second.get()); BSONObjBuilder secondBob; - splitExpr.second->serialize(&secondBob); + splitExpr.second->serialize(&secondBob, true); ASSERT_BSONOBJ_EQ(secondBob.obj(), fromjson("{a: {$_internalSchemaMaxItems: 3}}")); } @@ -1160,7 +1160,7 @@ TEST(SplitMatchExpression, ShouldMoveMinLengthAcrossRename) { ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$_internalSchemaMinLength: 3}}")); ASSERT_FALSE(splitExpr.second.get()); @@ -1178,7 +1178,7 @@ TEST(SplitMatchExpression, ShouldMoveMaxLengthAcrossRename) { ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{c: {$_internalSchemaMaxLength: 3}}")); ASSERT_FALSE(splitExpr.second.get()); @@ -1197,7 +1197,7 @@ TEST(SplitMatchExpression, ShouldMoveIndependentPredicateWhenThereAreMultipleRen ASSERT_TRUE(splitExpr.first.get()); BSONObjBuilder firstBob; - splitExpr.first->serialize(&firstBob); + splitExpr.first->serialize(&firstBob, true); ASSERT_BSONOBJ_EQ(firstBob.obj(), fromjson("{x: {$eq: 3}}")); ASSERT_FALSE(splitExpr.second.get()); diff --git a/src/mongo/db/matcher/expression_always_boolean.h b/src/mongo/db/matcher/expression_always_boolean.h index 6db02f58bc0..3e6030ae36e 100644 --- a/src/mongo/db/matcher/expression_always_boolean.h +++ b/src/mongo/db/matcher/expression_always_boolean.h @@ -58,7 +58,7 @@ public: debug << name() << ": 1\n"; } - void serialize(BSONObjBuilder* out) const final { + void serialize(BSONObjBuilder* out, bool includePath) const final { out->append(name(), 1); } diff --git a/src/mongo/db/matcher/expression_arity.h b/src/mongo/db/matcher/expression_arity.h index 2d2d93c5875..8ec77663ea7 100644 --- a/src/mongo/db/matcher/expression_arity.h +++ b/src/mongo/db/matcher/expression_arity.h @@ -57,7 +57,7 @@ public: _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder); + serialize(&builder, true); debug << builder.obj().toString(); } @@ -101,11 +101,11 @@ public: /** * Serializes each subexpression sequentially in a BSONArray. */ - void serialize(BSONObjBuilder* builder) const final { + void serialize(BSONObjBuilder* builder, bool includePath) const final { BSONArrayBuilder exprArray(builder->subarrayStart(name())); for (const auto& expr : _expressions) { BSONObjBuilder exprBuilder(exprArray.subobjStart()); - expr->serialize(&exprBuilder); + expr->serialize(&exprBuilder, includePath); exprBuilder.doneFast(); } exprArray.doneFast(); diff --git a/src/mongo/db/matcher/expression_array.cpp b/src/mongo/db/matcher/expression_array.cpp index 8607611b9f7..c54e66600f9 100644 --- a/src/mongo/db/matcher/expression_array.cpp +++ b/src/mongo/db/matcher/expression_array.cpp @@ -102,7 +102,7 @@ void ElemMatchObjectMatchExpression::debugString(StringBuilder& debug, int inden BSONObj ElemMatchObjectMatchExpression::getSerializedRightHandSide() const { BSONObjBuilder subBob; - _sub->serialize(&subBob); + _sub->serialize(&subBob, true); return BSON("$elemMatch" << subBob.obj()); } @@ -178,11 +178,8 @@ void ElemMatchValueMatchExpression::debugString(StringBuilder& debug, int indent BSONObj ElemMatchValueMatchExpression::getSerializedRightHandSide() const { BSONObjBuilder emBob; - for (unsigned i = 0; i < _subs.size(); i++) { - BSONObjBuilder predicate; - _subs[i]->serialize(&predicate); - BSONObj predObj = predicate.obj(); - emBob.appendElements(predObj.firstElement().embeddedObject()); + for (auto&& child : _subs) { + child->serialize(&emBob, false); } return BSON("$elemMatch" << emBob.obj()); diff --git a/src/mongo/db/matcher/expression_expr.cpp b/src/mongo/db/matcher/expression_expr.cpp index bce9461a50f..138ce2d376a 100644 --- a/src/mongo/db/matcher/expression_expr.cpp +++ b/src/mongo/db/matcher/expression_expr.cpp @@ -70,7 +70,7 @@ bool ExprMatchExpression::matches(const MatchableDocument* doc, MatchDetails* de } } -void ExprMatchExpression::serialize(BSONObjBuilder* out) const { +void ExprMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { *out << "$expr" << _expression->serialize(false); } diff --git a/src/mongo/db/matcher/expression_expr.h b/src/mongo/db/matcher/expression_expr.h index 1865025ea30..d3ebae6ec0b 100644 --- a/src/mongo/db/matcher/expression_expr.h +++ b/src/mongo/db/matcher/expression_expr.h @@ -63,7 +63,7 @@ public: debug << "$expr " << _expression->serialize(false).toString(); } - void serialize(BSONObjBuilder* out) const final; + void serialize(BSONObjBuilder* out, bool includePath) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/expression_expr_test.cpp b/src/mongo/db/matcher/expression_expr_test.cpp index 16fdd7b6485..87b9025510c 100644 --- a/src/mongo/db/matcher/expression_expr_test.cpp +++ b/src/mongo/db/matcher/expression_expr_test.cpp @@ -712,7 +712,7 @@ TEST(ExprMatchTest, OptimizingExprAbsorbsAndOfAnd) { BSONObj serialized; { BSONObjBuilder builder; - optimized->serialize(&builder); + optimized->serialize(&builder, true); serialized = builder.obj(); } diff --git a/src/mongo/db/matcher/expression_geo.cpp b/src/mongo/db/matcher/expression_geo.cpp index 9272148b323..68cf0c9d3ef 100644 --- a/src/mongo/db/matcher/expression_geo.cpp +++ b/src/mongo/db/matcher/expression_geo.cpp @@ -369,7 +369,7 @@ void GeoMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder); + serialize(&builder, true); debug << "GEO raw = " << builder.obj().toString(); MatchExpression::TagData* td = getTag(); diff --git a/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp b/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp index a16a7fe6447..833d7c9451d 100644 --- a/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp +++ b/src/mongo/db/matcher/expression_internal_expr_eq_test.cpp @@ -264,7 +264,7 @@ TEST(InternalExprEqMatchExpression, SerializesCorrectly) { operand.firstElement()); BSONObjBuilder bob; - eq.serialize(&bob); + eq.serialize(&bob, true); ASSERT_BSONOBJ_EQ(BSON("x" << BSON("$_internalExprEq" << 5)), bob.obj()); } diff --git a/src/mongo/db/matcher/expression_optimize_test.cpp b/src/mongo/db/matcher/expression_optimize_test.cpp index cec775d9eb8..fb293ed788e 100644 --- a/src/mongo/db/matcher/expression_optimize_test.cpp +++ b/src/mongo/db/matcher/expression_optimize_test.cpp @@ -347,7 +347,7 @@ TEST(ExpressionOptimizeTest, AndWithAlwaysFalseChildOptimizesToAlwaysFalse) { std::unique_ptr matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); } @@ -356,7 +356,7 @@ TEST(ExpressionOptimizeTest, AndRemovesAlwaysTrueChildren) { std::unique_ptr matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{a: {$eq: 1}}")); } @@ -367,7 +367,7 @@ TEST(ExpressionOptimizeTest, AndWithSingleChildAlwaysTrueOptimizesToEmptyAnd) { // TODO SERVER-34759 We want this to optimize to an AlwaysTrueMatchExpression. ASSERT_TRUE(dynamic_cast(optimizedMatchExpression.get())); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{}")); } @@ -378,7 +378,7 @@ TEST(ExpressionOptimizeTest, AndWithEachChildAlwaysTrueOptimizesToEmptyAnd) { // TODO SERVER-34759 We want this to optimize to an AlwaysTrueMatchExpression. ASSERT_TRUE(dynamic_cast(optimizedMatchExpression.get())); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{}")); } @@ -387,7 +387,7 @@ TEST(ExpressionOptimizeTest, NestedAndWithAlwaysFalseOptimizesToAlwaysFalse) { std::unique_ptr matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); } @@ -396,7 +396,7 @@ TEST(ExpressionOptimizeTest, OrWithAlwaysTrueOptimizesToAlwaysTrue) { std::unique_ptr matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysTrue: 1}")); } @@ -405,7 +405,7 @@ TEST(ExpressionOptimizeTest, OrRemovesAlwaysFalseChildren) { std::unique_ptr matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{a: {$eq: 1}}")); } @@ -416,7 +416,7 @@ TEST(ExpressionOptimizeTest, OrPromotesSingleAlwaysFalseAfterOptimize) { auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); ASSERT_TRUE(dynamic_cast(optimizedMatchExpression.get())); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); } @@ -426,7 +426,7 @@ TEST(ExpressionOptimizeTest, OrPromotesSingleAlwaysFalse) { auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); ASSERT_TRUE(dynamic_cast(optimizedMatchExpression.get())); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); } @@ -436,7 +436,7 @@ TEST(ExpressionOptimizeTest, OrPromotesMultipleAlwaysFalse) { auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); ASSERT_TRUE(dynamic_cast(optimizedMatchExpression.get())); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysFalse: 1}")); } @@ -445,7 +445,7 @@ TEST(ExpressionOptimizeTest, NestedOrWithAlwaysTrueOptimizesToAlwaysTrue) { std::unique_ptr matchExpression(parseMatchExpression(obj)); auto optimizedMatchExpression = MatchExpression::optimize(std::move(matchExpression)); BSONObjBuilder bob; - optimizedMatchExpression->serialize(&bob); + optimizedMatchExpression->serialize(&bob, true); ASSERT_BSONOBJ_EQ(bob.obj(), fromjson("{$alwaysTrue: 1}")); } diff --git a/src/mongo/db/matcher/expression_path.h b/src/mongo/db/matcher/expression_path.h index 274aa16d57c..a7f8941b928 100644 --- a/src/mongo/db/matcher/expression_path.h +++ b/src/mongo/db/matcher/expression_path.h @@ -118,8 +118,12 @@ public: } } - void serialize(BSONObjBuilder* out) const override { - out->append(path(), getSerializedRightHandSide()); + void serialize(BSONObjBuilder* out, bool includePath) const override { + if (includePath) { + out->append(path(), getSerializedRightHandSide()); + } else { + out->appendElements(getSerializedRightHandSide()); + } } /** diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp index 862b955423c..5c353734e1d 100644 --- a/src/mongo/db/matcher/expression_serialization_test.cpp +++ b/src/mongo/db/matcher/expression_serialization_test.cpp @@ -37,6 +37,7 @@ #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_max_length.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/unittest/unittest.h" @@ -49,7 +50,7 @@ using std::unique_ptr; BSONObj serialize(MatchExpression* match) { BSONObjBuilder bob; - match->serialize(&bob); + match->serialize(&bob, true); return bob.obj(); } @@ -300,6 +301,61 @@ TEST(SerializeBasic, ExpressionElemMatchValueWithNotLessThanGreaterThanSerialize ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); } +TEST(SerializeBasic, ExpressionElemMatchValueWithDoubleNotSerializesCorrectly) { + boost::intrusive_ptr expCtx(new ExpressionContextForTest()); + Matcher original(fromjson("{x: {$elemMatch: {$not: {$not: {$eq: 10}}}}}"), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + Matcher reserialized(serialize(original.getMatchExpression()), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + fromjson("{x: {$elemMatch: {$not: {$not: {$eq: 10}}}}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); + + auto obj = fromjson("{x: [10]}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); +} + +TEST(SerializeBasic, ExpressionElemMatchValueWithNotNESerializesCorrectly) { + boost::intrusive_ptr expCtx(new ExpressionContextForTest()); + Matcher original(fromjson("{x: {$elemMatch: {$not: {$ne: 10}}}}"), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + Matcher reserialized(serialize(original.getMatchExpression()), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + fromjson("{x: {$elemMatch: {$not: {$not: {$eq: 10}}}}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); + + auto obj = fromjson("{x: [10]}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); +} + +TEST(SerializeBasic, ExpressionElemMatchValueWithTripleNotSerializesCorrectly) { + boost::intrusive_ptr expCtx(new ExpressionContextForTest()); + Matcher original(fromjson("{x: {$elemMatch: {$not: {$not: {$not: {$eq: 10}}}}}}"), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + Matcher reserialized(serialize(original.getMatchExpression()), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + fromjson("{x: {$elemMatch: {$not: {$not: {$not: {$eq: 10}}}}}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); + + auto obj = fromjson("{x: [10]}"); + ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); +} + + TEST(SerializeBasic, ExpressionSizeSerializesCorrectly) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); Matcher original(fromjson("{x: {$size: 2}}"), @@ -901,7 +957,8 @@ TEST(SerializeBasic, ExpressionNotWithMultipleChildrenSerializesCorrectly) { expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures); - ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$not: {$lt: 1, $gt: 3}}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + fromjson("{$nor: [{$and: [{x:{$lt: 1}},{x: {$gt: 3}}]}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); BSONObj obj = fromjson("{x: 2}"); @@ -1030,6 +1087,107 @@ TEST(SerializeBasic, ExpressionNotWithGeoSerializesCorrectly) { ASSERT_EQ(original.matches(obj), reserialized.matches(obj)); } +TEST(SerializeBasic, ExpressionNotWithDirectPathExpSerializesCorrectly) { + boost::intrusive_ptr expCtx(new ExpressionContextForTest()); + // At the time of this writing, the MatchExpression parser does not ever create a NOT with a + // direct path expression child, instead creating a NOT -> AND -> path expression. This test + // manually constructs such an expression in case it ever turns up, since that should still be + // able to serialize. + auto originalBSON = fromjson("{a: {$not: {$eq: 2}}}}"); + auto equalityRHSElem = originalBSON["a"]["$not"]["$eq"]; + auto equalityExpression = std::make_unique("a"_sd, equalityRHSElem); + + auto notExpression = std::make_unique(equalityExpression.release()); + Matcher reserialized(serialize(notExpression.get()), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), originalBSON); + + auto obj = fromjson("{a: 2}"); + ASSERT_EQ(notExpression->matchesBSON(obj), reserialized.matches(obj)); +} + +TEST(SerializeBasic, ExpressionNotNotDirectlySerializesCorrectly) { + boost::intrusive_ptr expCtx(new ExpressionContextForTest()); + // At the time of this writing, the MatchExpression parser does not ever create a NOT with a + // direct NOT child, instead creating a NOT -> AND -> NOT. This test manually constructs such an + // expression in case it ever turns up, since that should still be able to serialize to + // {$not: {$not: ...}}. + auto originalBSON = fromjson("{a: {$not: {$not: {$eq: 2}}}}"); + auto equalityRHSElem = originalBSON["a"]["$not"]["$not"]["$eq"]; + auto equalityExpression = std::make_unique("a"_sd, equalityRHSElem); + + auto nestedNot = std::make_unique(equalityExpression.release()); + auto topNot = std::make_unique(nestedNot.release()); + Matcher reserialized(serialize(topNot.get()), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{a: {$not: {$eq: 2}}}]}")); + + auto obj = fromjson("{a: 2}"); + ASSERT_EQ(topNot->matchesBSON(obj), reserialized.matches(obj)); +} + +TEST(SerializeBasic, ExpressionNotWithoutPathChildrenSerializesCorrectly) { + // The grammar only permits a $not under a given path. For example, {a: {$not: {$eq: 4}}} is OK + // but {$not: {a: 4}} is not OK). However, we sometimes use the NOT MatchExpression to negate + // clauses within a JSONSchema. In such circumstances we need to be able to serialize the tree + // and re-parse it but the parser will reject the NOT in the place it's in. As a result, we need + // to translate the NOT to a $nor. + + // MatchExpression tree expected: + // {$or: [ + // {$and: [ + // {foo: {$_internalSchemaType: [2]}}, + // {foo: {$not: { + // // This whole $or represents the {maxLength: 4}, since the restriction only applies if + // // the element is the right type. + // $or: [ + // {$_internalSchemaMaxLength: 4}, + // {foo: {$not: {$_internalSchemaType: [2]}}} + // ] + // }}} + // ]}, + // {foo: {$not: {$exists: true}}} + // ]} + BSONObj query = + fromjson("{$jsonSchema: {properties: {foo: {type: 'string', not: {maxLength: 4}}}}}"); + boost::intrusive_ptr expCtx(new ExpressionContextForTest()); + auto expression = unittest::assertGet(MatchExpressionParser::parse(query, expCtx)); + + Matcher reserialized(serialize(expression.get()), + expCtx, + ExtensionsCallbackNoop(), + MatchExpressionParser::kAllowAllSpecialFeatures); + + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + fromjson("{$and: [" + " {$and: [" + " {$or: [" + " {foo: {$not: {$exists: true}}}," + " {$and: [" + " {$nor: [" // <-- This is the interesting part of this test. + " {$or: [" + " {foo: {$not: {$_internalSchemaType: [2]}}}," + " {foo: {$_internalSchemaMaxLength: 4}}" + " ]}" + " ]}," + " {foo: {$_internalSchemaType: [2]}}" + " ]}" + " ]}" + " ]}" + "]}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); + + BSONObj obj = fromjson("{foo: 'abc'}"); + ASSERT_EQ(expression->matchesBSON(obj), reserialized.matches(obj)); + + obj = fromjson("{foo: 'acbdf'}"); + ASSERT_EQ(expression->matchesBSON(obj), reserialized.matches(obj)); +} + TEST(SerializeBasic, ExpressionNorSerializesCorrectly) { boost::intrusive_ptr expCtx(new ExpressionContextForTest()); Matcher original(fromjson("{$nor: [{x: 3}, {x: {$lt: 1}}]}"), diff --git a/src/mongo/db/matcher/expression_text_base.cpp b/src/mongo/db/matcher/expression_text_base.cpp index d98efdc684a..56ad05af3d3 100644 --- a/src/mongo/db/matcher/expression_text_base.cpp +++ b/src/mongo/db/matcher/expression_text_base.cpp @@ -56,7 +56,7 @@ void TextMatchExpressionBase::debugString(StringBuilder& debug, int indentationL debug << "\n"; } -void TextMatchExpressionBase::serialize(BSONObjBuilder* out) const { +void TextMatchExpressionBase::serialize(BSONObjBuilder* out, bool includePath) const { const fts::FTSQuery& ftsQuery = getFTSQuery(); out->append("$text", BSON("$search" << ftsQuery.getQuery() << "$language" << ftsQuery.getLanguage() diff --git a/src/mongo/db/matcher/expression_text_base.h b/src/mongo/db/matcher/expression_text_base.h index a5247930ff5..22fc581a2e2 100644 --- a/src/mongo/db/matcher/expression_text_base.h +++ b/src/mongo/db/matcher/expression_text_base.h @@ -70,7 +70,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out) const final; + void serialize(BSONObjBuilder* out, bool includePath) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/expression_tree.cpp b/src/mongo/db/matcher/expression_tree.cpp index f03d49bce2d..ec32d07a233 100644 --- a/src/mongo/db/matcher/expression_tree.cpp +++ b/src/mongo/db/matcher/expression_tree.cpp @@ -56,10 +56,10 @@ void ListOfMatchExpression::_debugList(StringBuilder& debug, int indentationLeve _expressions[i]->debugString(debug, indentationLevel + 1); } -void ListOfMatchExpression::_listToBSON(BSONArrayBuilder* out) const { +void ListOfMatchExpression::_listToBSON(BSONArrayBuilder* out, bool includePath) const { for (unsigned i = 0; i < _expressions.size(); i++) { BSONObjBuilder childBob(out->subobjStart()); - _expressions[i]->serialize(&childBob); + _expressions[i]->serialize(&childBob, includePath); } out->doneFast(); } @@ -227,7 +227,7 @@ void AndMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugList(debug, indentationLevel); } -void AndMatchExpression::serialize(BSONObjBuilder* out) const { +void AndMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { if (!numChildren()) { // It is possible for an AndMatchExpression to have no children, resulting in the serialized // expression {$and: []}, which is not a valid query object. @@ -235,7 +235,7 @@ void AndMatchExpression::serialize(BSONObjBuilder* out) const { } BSONArrayBuilder arrBob(out->subarrayStart("$and")); - _listToBSON(&arrBob); + _listToBSON(&arrBob, includePath); arrBob.doneFast(); } @@ -276,7 +276,7 @@ void OrMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugList(debug, indentationLevel); } -void OrMatchExpression::serialize(BSONObjBuilder* out) const { +void OrMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { if (!numChildren()) { // It is possible for an OrMatchExpression to have no children, resulting in the serialized // expression {$or: []}, which is not a valid query object. An empty $or is logically @@ -285,7 +285,7 @@ void OrMatchExpression::serialize(BSONObjBuilder* out) const { return; } BSONArrayBuilder arrBob(out->subarrayStart("$or")); - _listToBSON(&arrBob); + _listToBSON(&arrBob, includePath); } bool OrMatchExpression::isTriviallyFalse() const { @@ -318,9 +318,9 @@ void NorMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _debugList(debug, indentationLevel); } -void NorMatchExpression::serialize(BSONObjBuilder* out) const { +void NorMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { BSONArrayBuilder arrBob(out->subarrayStart("$nor")); - _listToBSON(&arrBob); + _listToBSON(&arrBob, includePath); } // ------- @@ -331,45 +331,11 @@ void NotMatchExpression::debugString(StringBuilder& debug, int indentationLevel) _exp->debugString(debug, indentationLevel + 1); } -boost::optional NotMatchExpression::getPathIfNotWithSinglePathMatchExpressionTree( - MatchExpression* exp) { - if (auto pathMatch = dynamic_cast(exp)) { - if (dynamic_cast(exp)) { - // While TextMatchExpressionBase derives from PathMatchExpression, text match - // expressions cannot be serialized in the same manner as other PathMatchExpression - // derivatives. This is because the path for a TextMatchExpression is embedded within - // the $text object, whereas for other PathMatchExpressions it is on the left-hand-side, - // for example {x: {$eq: 1}}. - return boost::none; - } - return pathMatch->path(); - } - - if (exp->matchType() == MatchExpression::MatchType::AND && exp->numChildren() > 0) { - boost::optional path; - for (size_t i = 0; i < exp->numChildren(); ++i) { - auto pathMatchChild = dynamic_cast(exp->getChild(i)); - if (!pathMatchChild || dynamic_cast(exp->getChild(i))) { - return boost::none; - } - - if (path && path != pathMatchChild->path()) { - return boost::none; - } else if (!path) { - path = pathMatchChild->path(); - } - } - - invariant(path); - return path; - } - - return boost::none; -} - -void NotMatchExpression::serializeNotExpressionToNor(MatchExpression* exp, BSONObjBuilder* out) { +void NotMatchExpression::serializeNotExpressionToNor(MatchExpression* exp, + BSONObjBuilder* out, + bool includePath) { BSONObjBuilder childBob; - exp->serialize(&childBob); + exp->serialize(&childBob, includePath); BSONObj tempObj = childBob.obj(); BSONArrayBuilder tBob(out->subarrayStart("$nor")); @@ -377,42 +343,49 @@ void NotMatchExpression::serializeNotExpressionToNor(MatchExpression* exp, BSONO tBob.doneFast(); } -void NotMatchExpression::serialize(BSONObjBuilder* out) const { +void NotMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { if (_exp->matchType() == MatchType::AND && _exp->numChildren() == 0) { out->append("$alwaysFalse", 1); return; } - // When a $not contains an expression that is not a PathMatchExpression tree representing a - // single path, we transform to a $nor. - // There are trees constructed to represent JSONSchema that require a nor representation to - // be valid. Here is an example: - // JSONSchema: - // {properties: {foo: {type: "string", not: {maxLength: 4}}}} - // MatchExpression tree generated: - // {foo: {$not: {$or: [{$not: {$_internalSchemaType: [ 2 ]}}, - // {$_internalSchemaMaxLength: 4}]}}} - boost::optional path = getPathIfNotWithSinglePathMatchExpressionTree(_exp.get()); - if (!path) { - return serializeNotExpressionToNor(_exp.get(), out); + if (!includePath) { + BSONObjBuilder notBob(out->subobjStart("$not")); + // Our parser does not accept a $and directly within a $not, instead expecting the direct + // notation like {x: {$not: {$gt: 5, $lt: 0}}}. We represent such an expression with an AND + // internally, so we un-nest it here to be able to re-parse it. + if (_exp->matchType() == MatchType::AND) { + for (size_t x = 0; x < _exp->numChildren(); ++x) { + _exp->getChild(x)->serialize(¬Bob, includePath); + } + } else { + _exp->serialize(¬Bob, includePath); + } + return; } - BSONObjBuilder pathBob(out->subobjStart(*path)); + auto expressionToNegate = _exp.get(); + if (_exp->matchType() == MatchType::AND && _exp->numChildren() == 1) { + expressionToNegate = _exp->getChild(0); + } - if (_exp->matchType() == MatchType::AND) { - BSONObjBuilder notBob(pathBob.subobjStart("$not")); - for (size_t x = 0; x < _exp->numChildren(); ++x) { - auto* pathMatchExpression = dynamic_cast(_exp->getChild(x)); - invariant(pathMatchExpression); - notBob.appendElements(pathMatchExpression->getSerializedRightHandSide()); - } - notBob.doneFast(); - } else { - auto* pathMatchExpression = dynamic_cast(_exp.get()); - invariant(pathMatchExpression); - pathBob.append("$not", pathMatchExpression->getSerializedRightHandSide()); + // It is generally easier to be correct if we just always serialize to a $nor, since this will + // delegate the path serialization to lower in the tree where we have the information on-hand. + // However, for legibility we preserve a $not with a single path-accepting child as a $not. + // + // One exception: while TextMatchExpressionBase derives from PathMatchExpression, text match + // expressions cannot be serialized in the same manner as other PathMatchExpression derivatives. + // This is because the path for a TextMatchExpression is embedded within the $text object, + // whereas for other PathMatchExpressions it is on the left-hand-side, for example {x: {$eq: + // 1}}. + if (auto pathMatch = dynamic_cast(expressionToNegate); + pathMatch && !dynamic_cast(expressionToNegate)) { + const auto path = pathMatch->path(); + BSONObjBuilder pathBob(out->subobjStart(path)); + pathBob.append("$not", pathMatch->getSerializedRightHandSide()); + return; } - pathBob.doneFast(); + return serializeNotExpressionToNor(expressionToNegate, out, includePath); } bool NotMatchExpression::equivalent(const MatchExpression* other) const { diff --git a/src/mongo/db/matcher/expression_tree.h b/src/mongo/db/matcher/expression_tree.h index 78d429fd1e7..5a33cb1e10d 100644 --- a/src/mongo/db/matcher/expression_tree.h +++ b/src/mongo/db/matcher/expression_tree.h @@ -95,7 +95,7 @@ public: protected: void _debugList(StringBuilder& debug, int indentationLevel) const; - void _listToBSON(BSONArrayBuilder* out) const; + void _listToBSON(BSONArrayBuilder* out, bool includePath) const; private: ExpressionOptimizerFunc getOptimizer() const final; @@ -127,7 +127,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out) const; + virtual void serialize(BSONObjBuilder* out, bool includePath) const; bool isTriviallyTrue() const final; }; @@ -156,7 +156,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out) const; + virtual void serialize(BSONObjBuilder* out, bool includePath) const; bool isTriviallyFalse() const final; }; @@ -185,7 +185,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out) const; + virtual void serialize(BSONObjBuilder* out, bool includePath) const; }; class NotMatchExpression final : public MatchExpression { @@ -211,7 +211,7 @@ public: virtual void debugString(StringBuilder& debug, int indentationLevel = 0) const; - virtual void serialize(BSONObjBuilder* out) const; + virtual void serialize(BSONObjBuilder* out, bool includePath) const; bool equivalent(const MatchExpression* other) const; @@ -240,9 +240,9 @@ public: } private: - static boost::optional getPathIfNotWithSinglePathMatchExpressionTree( - MatchExpression* exp); - static void serializeNotExpressionToNor(MatchExpression* exp, BSONObjBuilder* out); + static void serializeNotExpressionToNor(MatchExpression* exp, + BSONObjBuilder* out, + bool includePath); ExpressionOptimizerFunc getOptimizer() const final; diff --git a/src/mongo/db/matcher/expression_where_base.cpp b/src/mongo/db/matcher/expression_where_base.cpp index 1fe88284a24..24168d26028 100644 --- a/src/mongo/db/matcher/expression_where_base.cpp +++ b/src/mongo/db/matcher/expression_where_base.cpp @@ -49,7 +49,7 @@ void WhereMatchExpressionBase::debugString(StringBuilder& debug, int indentation debug << "scope: " << getScope() << "\n"; } -void WhereMatchExpressionBase::serialize(BSONObjBuilder* out) const { +void WhereMatchExpressionBase::serialize(BSONObjBuilder* out, bool includePath) const { out->appendCodeWScope("$where", getCode(), getScope()); } diff --git a/src/mongo/db/matcher/expression_where_base.h b/src/mongo/db/matcher/expression_where_base.h index fa7bc6bb632..78db059c218 100644 --- a/src/mongo/db/matcher/expression_where_base.h +++ b/src/mongo/db/matcher/expression_where_base.h @@ -63,7 +63,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out) const final; + void serialize(BSONObjBuilder* out, bool includePath) const final; bool equivalent(const MatchExpression* other) const final; diff --git a/src/mongo/db/matcher/rewrite_expr_test.cpp b/src/mongo/db/matcher/rewrite_expr_test.cpp index 8516375aa61..6cad57f9c62 100644 --- a/src/mongo/db/matcher/rewrite_expr_test.cpp +++ b/src/mongo/db/matcher/rewrite_expr_test.cpp @@ -57,7 +57,7 @@ void testExprRewrite(BSONObj expr, BSONObj expectedMatch) { if (!expectedMatch.isEmpty()) { ASSERT(result.matchExpression()); BSONObjBuilder bob; - result.matchExpression()->serialize(&bob); + result.matchExpression()->serialize(&bob, true); ASSERT_BSONOBJ_EQ(expectedMatch, bob.obj()); } else { ASSERT_FALSE(result.matchExpression()); diff --git a/src/mongo/db/matcher/schema/array_keywords_test.cpp b/src/mongo/db/matcher/schema/array_keywords_test.cpp index 96edb138560..c652ea9b618 100644 --- a/src/mongo/db/matcher/schema/array_keywords_test.cpp +++ b/src/mongo/db/matcher/schema/array_keywords_test.cpp @@ -31,6 +31,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" +#include "mongo/db/matcher/schema/assert_serializes_to.h" #include "mongo/db/matcher/schema/json_schema_parser.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/unittest/unittest.h" @@ -38,13 +39,6 @@ namespace mongo { namespace { -#define ASSERT_SERIALIZES_TO(match, expected) \ - do { \ - BSONObjBuilder bob; \ - match->serialize(&bob); \ - ASSERT_BSONOBJ_EQ(bob.obj(), expected); \ - } while (false) - TEST(JSONSchemaArrayKeywordTest, FailsToParseIfMinItemsIsNotANumber) { auto schema = BSON("minItems" << BSON_ARRAY(1)); ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(), diff --git a/src/mongo/db/matcher/schema/assert_serializes_to.h b/src/mongo/db/matcher/schema/assert_serializes_to.h new file mode 100644 index 00000000000..e62b5e62e1d --- /dev/null +++ b/src/mongo/db/matcher/schema/assert_serializes_to.h @@ -0,0 +1,44 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +namespace mongo { + +/** + * Asserts that the given MatchExpression 'match' serializes to the BSONObj 'expected'. + */ +#define ASSERT_SERIALIZES_TO(match, expected) \ + do { \ + BSONObjBuilder bob; \ + match->serialize(&bob, true); \ + ASSERT_BSONOBJ_EQ(bob.obj(), expected); \ + } while (false) + +} // namespace mongo diff --git a/src/mongo/db/matcher/schema/encrypt_keyword_test.cpp b/src/mongo/db/matcher/schema/encrypt_keyword_test.cpp index ddc0e301d33..aaac0d897bc 100644 --- a/src/mongo/db/matcher/schema/encrypt_keyword_test.cpp +++ b/src/mongo/db/matcher/schema/encrypt_keyword_test.cpp @@ -32,6 +32,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" #include "mongo/db/matcher/expression_always_boolean.h" +#include "mongo/db/matcher/schema/assert_serializes_to.h" #include "mongo/db/matcher/schema/json_schema_parser.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/unittest/unittest.h" @@ -39,13 +40,6 @@ namespace mongo { namespace { -#define ASSERT_SERIALIZES_TO(match, expected) \ - do { \ - BSONObjBuilder bob; \ - match->serialize(&bob); \ - ASSERT_BSONOBJ_EQ(bob.obj(), expected); \ - } while (false) - TEST(JSONSchemaParserEncryptTest, EncryptTranslatesCorrectly) { BSONObj schema = fromjson("{properties: {foo: {encrypt: {}}}}"); auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema); 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 818054c4af1..356b7f5081d 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 @@ -79,7 +79,7 @@ BSONObj InternalSchemaAllElemMatchFromIndexMatchExpression::getSerializedRightHa subArray.append(_index); { BSONObjBuilder eBuilder(subArray.subobjStart()); - _expression->getFilter()->serialize(&eBuilder); + _expression->getFilter()->serialize(&eBuilder, true); eBuilder.doneFast(); } subArray.doneFast(); 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 e110980de91..1718e2d7844 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 @@ -58,7 +58,7 @@ void InternalSchemaAllowedPropertiesMatchExpression::debugString(StringBuilder& _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder); + serialize(&builder, true); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); @@ -126,7 +126,8 @@ bool InternalSchemaAllowedPropertiesMatchExpression::_matchesBSONObj(const BSONO return true; } -void InternalSchemaAllowedPropertiesMatchExpression::serialize(BSONObjBuilder* builder) const { +void InternalSchemaAllowedPropertiesMatchExpression::serialize(BSONObjBuilder* builder, + bool includePath) const { BSONObjBuilder expressionBuilder( builder->subobjStart(InternalSchemaAllowedPropertiesMatchExpression::kName)); @@ -142,13 +143,13 @@ void InternalSchemaAllowedPropertiesMatchExpression::serialize(BSONObjBuilder* b itemBuilder.appendRegex("regex", item.first.rawRegex); BSONObjBuilder subexpressionBuilder(itemBuilder.subobjStart("expression")); - item.second->getFilter()->serialize(&subexpressionBuilder); + item.second->getFilter()->serialize(&subexpressionBuilder, includePath); subexpressionBuilder.doneFast(); } patternPropertiesBuilder.doneFast(); BSONObjBuilder otherwiseBuilder(expressionBuilder.subobjStart("otherwise")); - _otherwise->getFilter()->serialize(&otherwiseBuilder); + _otherwise->getFilter()->serialize(&otherwiseBuilder, includePath); otherwiseBuilder.doneFast(); expressionBuilder.doneFast(); } diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h b/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h index e45acba010b..c2eaa46bbcc 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h @@ -134,7 +134,7 @@ public: bool matches(const MatchableDocument* doc, MatchDetails* details) const final; bool matchesSingleElement(const BSONElement& element, MatchDetails* details) const final; - void serialize(BSONObjBuilder* builder) const final; + void serialize(BSONObjBuilder* builder, bool includePath) const final; std::unique_ptr shallowClone() 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 6d5d2ed2ed5..991f9240521 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 @@ -47,7 +47,7 @@ void InternalSchemaMatchArrayIndexMatchExpression::debugString(StringBuilder& de _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder); + serialize(&builder, true); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); @@ -76,7 +76,7 @@ BSONObj InternalSchemaMatchArrayIndexMatchExpression::getSerializedRightHandSide matchArrayElemSubobj.append("namePlaceholder", _expression->getPlaceholder().value_or("")); { BSONObjBuilder subexprSubObj(matchArrayElemSubobj.subobjStart("expression")); - _expression->getFilter()->serialize(&subexprSubObj); + _expression->getFilter()->serialize(&subexprSubObj, true); subexprSubObj.doneFast(); } matchArrayElemSubobj.doneFast(); 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 d8895fc7adb..e640a58ec71 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 @@ -37,11 +37,12 @@ void InternalSchemaNumPropertiesMatchExpression::debugString(StringBuilder& debu int indentationLevel) const { _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder); + serialize(&builder, true); debug << builder.obj().toString() << "\n"; } -void InternalSchemaNumPropertiesMatchExpression::serialize(BSONObjBuilder* out) const { +void InternalSchemaNumPropertiesMatchExpression::serialize(BSONObjBuilder* out, + bool includePath) const { out->append(_name, _numProperties); } diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h b/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h index 415ed946145..463420f0dd7 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h @@ -61,7 +61,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel) const final; - void serialize(BSONObjBuilder* out) const final; + void serialize(BSONObjBuilder* out, bool includePath) 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 b6ca5118644..22940d101fa 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 @@ -61,7 +61,7 @@ void InternalSchemaObjectMatchExpression::debugString(StringBuilder& debug, BSONObj InternalSchemaObjectMatchExpression::getSerializedRightHandSide() const { BSONObjBuilder objMatchBob; BSONObjBuilder subBob(objMatchBob.subobjStart(kName)); - _sub->serialize(&subBob); + _sub->serialize(&subBob, true); subBob.doneFast(); return objMatchBob.obj(); } 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 24ba5b22afa..02f1d116825 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 @@ -54,7 +54,8 @@ void InternalSchemaRootDocEqMatchExpression::debugString(StringBuilder& debug, debug << "\n"; } -void InternalSchemaRootDocEqMatchExpression::serialize(BSONObjBuilder* out) const { +void InternalSchemaRootDocEqMatchExpression::serialize(BSONObjBuilder* out, + bool includePath) const { BSONObjBuilder subObj(out->subobjStart(kName)); subObj.appendElements(_rhsObj); subObj.doneFast(); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h index 062d358939d..ebfdca1b136 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h @@ -67,7 +67,7 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out) const final; + void serialize(BSONObjBuilder* out, bool includePath) 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 6d132badf27..bf4c9ebe647 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 @@ -39,7 +39,7 @@ void InternalSchemaUniqueItemsMatchExpression::debugString(StringBuilder& debug, _debugAddSpace(debug, indentationLevel); BSONObjBuilder builder; - serialize(&builder); + serialize(&builder, true); debug << builder.obj().toString() << "\n"; const auto* tag = getTag(); diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp index c920e38f827..ad265266f52 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp +++ b/src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp @@ -73,8 +73,8 @@ void InternalSchemaXorMatchExpression::debugString(StringBuilder& debug, _debugList(debug, indentationLevel); } -void InternalSchemaXorMatchExpression::serialize(BSONObjBuilder* out) const { +void InternalSchemaXorMatchExpression::serialize(BSONObjBuilder* out, bool includePath) const { BSONArrayBuilder arrBob(out->subarrayStart(kName)); - _listToBSON(&arrBob); + _listToBSON(&arrBob, includePath); } } // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_xor.h b/src/mongo/db/matcher/schema/expression_internal_schema_xor.h index 9aebb07d68d..f64e86c822c 100644 --- a/src/mongo/db/matcher/schema/expression_internal_schema_xor.h +++ b/src/mongo/db/matcher/schema/expression_internal_schema_xor.h @@ -60,6 +60,6 @@ public: void debugString(StringBuilder& debug, int indentationLevel = 0) const final; - void serialize(BSONObjBuilder* out) const final; + void serialize(BSONObjBuilder* out, bool includePath) const final; }; } // namespace mongo diff --git a/src/mongo/db/matcher/schema/logical_keywords_test.cpp b/src/mongo/db/matcher/schema/logical_keywords_test.cpp index 1dbb709d4bf..e4c8c73d435 100644 --- a/src/mongo/db/matcher/schema/logical_keywords_test.cpp +++ b/src/mongo/db/matcher/schema/logical_keywords_test.cpp @@ -31,6 +31,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" +#include "mongo/db/matcher/schema/assert_serializes_to.h" #include "mongo/db/matcher/schema/json_schema_parser.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/unittest/unittest.h" @@ -38,13 +39,6 @@ namespace mongo { namespace { -#define ASSERT_SERIALIZES_TO(match, expected) \ - do { \ - BSONObjBuilder bob; \ - match->serialize(&bob); \ - ASSERT_BSONOBJ_EQ(bob.obj(), expected); \ - } while (false) - TEST(JSONSchemaLogicalKeywordTest, FailsToParseIfAllOfIsNotAnArray) { BSONObj schema = fromjson("{properties: {foo: {allOf: 'foo'}}}"); auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema); diff --git a/src/mongo/db/matcher/schema/object_keywords_test.cpp b/src/mongo/db/matcher/schema/object_keywords_test.cpp index 3829f9f68ff..31709e7ab5e 100644 --- a/src/mongo/db/matcher/schema/object_keywords_test.cpp +++ b/src/mongo/db/matcher/schema/object_keywords_test.cpp @@ -32,6 +32,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" #include "mongo/db/matcher/expression_always_boolean.h" +#include "mongo/db/matcher/schema/assert_serializes_to.h" #include "mongo/db/matcher/schema/json_schema_parser.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/unittest/unittest.h" @@ -39,13 +40,6 @@ namespace mongo { namespace { -#define ASSERT_SERIALIZES_TO(match, expected) \ - do { \ - BSONObjBuilder bob; \ - match->serialize(&bob); \ - ASSERT_BSONOBJ_EQ(bob.obj(), expected); \ - } while (false) - TEST(JSONSchemaObjectKeywordTest, FailsToParseIfTypeIsNotAString) { BSONObj schema = fromjson("{type: 1}"); auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema); @@ -262,11 +256,11 @@ TEST(JSONSchemaObjectKeywordTest, SharedJsonAndBsonTypeAliasesTranslateIdentical ASSERT_OK(bsonTypeResult.getStatus()); BSONObjBuilder typeBuilder; - MatchExpression::optimize(std::move(typeResult.getValue()))->serialize(&typeBuilder); + MatchExpression::optimize(std::move(typeResult.getValue()))->serialize(&typeBuilder, true); BSONObjBuilder bsonTypeBuilder; MatchExpression::optimize(std::move(bsonTypeResult.getValue())) - ->serialize(&bsonTypeBuilder); + ->serialize(&bsonTypeBuilder, true); ASSERT_BSONOBJ_EQ(typeBuilder.obj(), bsonTypeBuilder.obj()); } diff --git a/src/mongo/db/matcher/schema/scalar_keywords_test.cpp b/src/mongo/db/matcher/schema/scalar_keywords_test.cpp index 6de666dd3b9..04beec7b853 100644 --- a/src/mongo/db/matcher/schema/scalar_keywords_test.cpp +++ b/src/mongo/db/matcher/schema/scalar_keywords_test.cpp @@ -32,6 +32,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" #include "mongo/db/matcher/expression_always_boolean.h" +#include "mongo/db/matcher/schema/assert_serializes_to.h" #include "mongo/db/matcher/schema/json_schema_parser.h" #include "mongo/db/pipeline/expression_context_for_test.h" #include "mongo/unittest/unittest.h" @@ -39,13 +40,6 @@ namespace mongo { namespace { -#define ASSERT_SERIALIZES_TO(match, expected) \ - do { \ - BSONObjBuilder bob; \ - match->serialize(&bob); \ - ASSERT_BSONOBJ_EQ(bob.obj(), expected); \ - } while (false) - TEST(JSONSchemaParserScalarTest, MaximumTranslatesCorrectlyWithTypeNumber) { BSONObj schema = fromjson("{properties: {num: {type: 'number', maximum: 0}}, type: 'object'}"); auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema); diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp index c7bc11d00a7..15e52c3f0bc 100644 --- a/src/mongo/db/query/canonical_query.cpp +++ b/src/mongo/db/query/canonical_query.cpp @@ -189,7 +189,7 @@ StatusWith> CanonicalQuery::canonicalize( OperationContext* opCtx, const CanonicalQuery& baseQuery, MatchExpression* root) { auto qr = std::make_unique(baseQuery.nss()); BSONObjBuilder builder; - root->serialize(&builder); + root->serialize(&builder, true); qr->setFilter(builder.obj()); qr->setProj(baseQuery.getQueryRequest().getProj()); qr->setSort(baseQuery.getQueryRequest().getSort()); diff --git a/src/mongo/db/query/projection_ast_util.cpp b/src/mongo/db/query/projection_ast_util.cpp index 1d31ddef614..ce346c5b98e 100644 --- a/src/mongo/db/query/projection_ast_util.cpp +++ b/src/mongo/db/query/projection_ast_util.cpp @@ -50,7 +50,7 @@ public: virtual void visit(const MatchExpressionASTNode* node) { static_cast(node)->matchExpression()->serialize( - &_context->builder()); + &_context->builder(), true); _context->fieldNames.top().pop_front(); } -- cgit v1.2.1