summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher
diff options
context:
space:
mode:
authorCharlie Swanson <charlie.swanson@mongodb.com>2019-11-27 21:22:40 +0000
committerevergreen <evergreen@mongodb.com>2019-11-27 21:22:40 +0000
commit4d1ce1b892eef6f3926548a7b34d020a02eaee8a (patch)
treece9d233ff333c012d845d4c3da16e8a2393871cf /src/mongo/db/matcher
parent05caeff5a904d494c7dc6c994ed67a9b6c30a008 (diff)
downloadmongo-4d1ce1b892eef6f3926548a7b34d020a02eaee8a.tar.gz
SERVER-43349 Accommodate double $nots during serialization
Diffstat (limited to 'src/mongo/db/matcher')
-rw-r--r--src/mongo/db/matcher/expression.cpp2
-rw-r--r--src/mongo/db/matcher/expression.h5
-rw-r--r--src/mongo/db/matcher/expression_algo_test.cpp54
-rw-r--r--src/mongo/db/matcher/expression_always_boolean.h2
-rw-r--r--src/mongo/db/matcher/expression_arity.h6
-rw-r--r--src/mongo/db/matcher/expression_array.cpp9
-rw-r--r--src/mongo/db/matcher/expression_expr.cpp2
-rw-r--r--src/mongo/db/matcher/expression_expr.h2
-rw-r--r--src/mongo/db/matcher/expression_expr_test.cpp2
-rw-r--r--src/mongo/db/matcher/expression_geo.cpp2
-rw-r--r--src/mongo/db/matcher/expression_internal_expr_eq_test.cpp2
-rw-r--r--src/mongo/db/matcher/expression_optimize_test.cpp22
-rw-r--r--src/mongo/db/matcher/expression_path.h8
-rw-r--r--src/mongo/db/matcher/expression_serialization_test.cpp162
-rw-r--r--src/mongo/db/matcher/expression_text_base.cpp2
-rw-r--r--src/mongo/db/matcher/expression_text_base.h2
-rw-r--r--src/mongo/db/matcher/expression_tree.cpp119
-rw-r--r--src/mongo/db/matcher/expression_tree.h16
-rw-r--r--src/mongo/db/matcher/expression_where_base.cpp2
-rw-r--r--src/mongo/db/matcher/expression_where_base.h2
-rw-r--r--src/mongo/db/matcher/rewrite_expr_test.cpp2
-rw-r--r--src/mongo/db/matcher/schema/array_keywords_test.cpp8
-rw-r--r--src/mongo/db/matcher/schema/assert_serializes_to.h44
-rw-r--r--src/mongo/db/matcher/schema/encrypt_keyword_test.cpp8
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_all_elem_match_from_index.cpp2
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.cpp9
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h2
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_match_array_index.cpp4
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp5
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h2
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_object_match.cpp2
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.cpp3
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_root_doc_eq.h2
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp2
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_xor.cpp4
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_xor.h2
-rw-r--r--src/mongo/db/matcher/schema/logical_keywords_test.cpp8
-rw-r--r--src/mongo/db/matcher/schema/object_keywords_test.cpp12
-rw-r--r--src/mongo/db/matcher/schema/scalar_keywords_test.cpp8
39 files changed, 351 insertions, 201 deletions
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> 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> 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<AndMatchExpression*>(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<AndMatchExpression*>(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> 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> 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> 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<AlwaysFalseMatchExpression*>(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<AlwaysFalseMatchExpression*>(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<AlwaysFalseMatchExpression*>(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> 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<ExpressionContextForTest> 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<ExpressionContextForTest> 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<ExpressionContextForTest> 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<ExpressionContextForTest> 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<ExpressionContextForTest> 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<EqualityMatchExpression>("a"_sd, equalityRHSElem);
+
+ auto notExpression = std::make_unique<NotMatchExpression>(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<ExpressionContextForTest> 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<EqualityMatchExpression>("a"_sd, equalityRHSElem);
+
+ auto nestedNot = std::make_unique<NotMatchExpression>(equalityExpression.release());
+ auto topNot = std::make_unique<NotMatchExpression>(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<ExpressionContextForTest> 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<ExpressionContextForTest> 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<StringData> NotMatchExpression::getPathIfNotWithSinglePathMatchExpressionTree(
- MatchExpression* exp) {
- if (auto pathMatch = dynamic_cast<PathMatchExpression*>(exp)) {
- if (dynamic_cast<TextMatchExpressionBase*>(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<StringData> path;
- for (size_t i = 0; i < exp->numChildren(); ++i) {
- auto pathMatchChild = dynamic_cast<PathMatchExpression*>(exp->getChild(i));
- if (!pathMatchChild || dynamic_cast<TextMatchExpressionBase*>(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<StringData> 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(&notBob, includePath);
+ }
+ } else {
+ _exp->serialize(&notBob, 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<PathMatchExpression*>(_exp->getChild(x));
- invariant(pathMatchExpression);
- notBob.appendElements(pathMatchExpression->getSerializedRightHandSide());
- }
- notBob.doneFast();
- } else {
- auto* pathMatchExpression = dynamic_cast<PathMatchExpression*>(_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<PathMatchExpression*>(expressionToNegate);
+ pathMatch && !dynamic_cast<TextMatchExpressionBase*>(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<StringData> 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
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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<MatchExpression> 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);