diff options
-rw-r--r-- | src/mongo/bson/bsonelement.cpp | 1 | ||||
-rw-r--r-- | src/mongo/bson/bsonobj.h | 1 | ||||
-rw-r--r-- | src/mongo/db/matcher/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression.h | 3 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_parser.cpp | 15 | ||||
-rw-r--r-- | src/mongo/db/matcher/expression_serialization_test.cpp | 327 | ||||
-rw-r--r-- | src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp | 74 | ||||
-rw-r--r-- | src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h | 74 | ||||
-rw-r--r-- | src/mongo/db/matcher/schema/expression_internal_schema_unique_items_test.cpp | 111 | ||||
-rw-r--r-- | src/mongo/db/matcher/schema/expression_parser_schema_test.cpp | 81 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_match.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/pipeline/document_source_match_test.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/query/plan_cache.cpp | 4 |
13 files changed, 492 insertions, 204 deletions
diff --git a/src/mongo/bson/bsonelement.cpp b/src/mongo/bson/bsonelement.cpp index f82ba99e8f3..b3f6ec79696 100644 --- a/src/mongo/bson/bsonelement.cpp +++ b/src/mongo/bson/bsonelement.cpp @@ -336,6 +336,7 @@ const StringMap<BSONObj::MatchType> queryOperatorMap{ {"bitsAnyClear", BSONObj::opBITS_ANY_CLEAR}, {"_internalSchemaMinItems", BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS}, {"_internalSchemaMaxItems", BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS}, + {"_internalSchemaUniqueItems", BSONObj::opINTERNAL_SCHEMA_UNIQUE_ITEMS}, }; // Compares two string elements using a simple binary compare. diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h index 49d0edf06bc..4be9c8ccba2 100644 --- a/src/mongo/bson/bsonobj.h +++ b/src/mongo/bson/bsonobj.h @@ -530,6 +530,7 @@ public: opBITS_ANY_CLEAR = 0x1A, opINTERNAL_SCHEMA_MIN_ITEMS = 0x1B, opINTERNAL_SCHEMA_MAX_ITEMS = 0x1C, + opINTERNAL_SCHEMA_UNIQUE_ITEMS = 0x1D, }; /** add all elements of the object to the specified vector */ diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript index 3b865fae670..4d7ee1f1b63 100644 --- a/src/mongo/db/matcher/SConscript +++ b/src/mongo/db/matcher/SConscript @@ -46,6 +46,7 @@ env.Library( 'matchable.cpp', 'matcher.cpp', 'schema/expression_internal_schema_num_array_items.cpp', + 'schema/expression_internal_schema_unique_items.cpp', 'schema/expression_internal_schema_xor.cpp', ], LIBDEPS=[ @@ -68,6 +69,7 @@ env.CppUnitTest( 'expression_tree_test.cpp', 'schema/expression_internal_schema_max_items_test.cpp', 'schema/expression_internal_schema_min_items_test.cpp', + 'schema/expression_internal_schema_unique_items_test.cpp', 'schema/expression_internal_schema_xor_test.cpp', ], LIBDEPS=[ diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h index 2c99218bdf7..f6299aa63c2 100644 --- a/src/mongo/db/matcher/expression.h +++ b/src/mongo/db/matcher/expression.h @@ -98,8 +98,9 @@ public: INTERNAL_2D_POINT_IN_ANNULUS, // JSON Schema expressions. - INTERNAL_SCHEMA_MIN_ITEMS, INTERNAL_SCHEMA_MAX_ITEMS, + INTERNAL_SCHEMA_MIN_ITEMS, + INTERNAL_SCHEMA_UNIQUE_ITEMS, INTERNAL_SCHEMA_XOR, }; diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp index 8b5b9aed1a0..f2c121fa96e 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -38,6 +38,7 @@ #include "mongo/db/matcher/expression_tree.h" #include "mongo/db/matcher/schema/expression_internal_schema_max_items.h" #include "mongo/db/matcher/schema/expression_internal_schema_min_items.h" +#include "mongo/db/matcher/schema/expression_internal_schema_unique_items.h" #include "mongo/db/matcher/schema/expression_internal_schema_xor.h" #include "mongo/db/namespace_string.h" #include "mongo/stdx/memory.h" @@ -293,6 +294,20 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c return _parseInternalSchemaSingleIntegerArgument<InternalSchemaMaxItemsMatchExpression>( name, e); } + case BSONObj::opINTERNAL_SCHEMA_UNIQUE_ITEMS: { + if (!e.isBoolean() || !e.boolean()) { + return {ErrorCodes::FailedToParse, + str::stream() << name << " must be a boolean of value true"}; + } + + + auto expr = stdx::make_unique<InternalSchemaUniqueItemsMatchExpression>(); + auto status = expr->init(name); + if (!status.isOK()) { + return status; + } + return {std::move(expr)}; + } } return {Status(ErrorCodes::BadValue, diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp index b05cbe71bfc..40591d75dcb 100644 --- a/src/mongo/db/matcher/expression_serialization_test.cpp +++ b/src/mongo/db/matcher/expression_serialization_test.cpp @@ -45,6 +45,8 @@ using std::pair; using std::string; using std::unique_ptr; +constexpr CollatorInterface* kSimpleCollator = nullptr; + BSONObj serialize(MatchExpression* match) { BSONObjBuilder bob; match->serialize(&bob); @@ -52,10 +54,9 @@ BSONObj serialize(MatchExpression* match) { } TEST(SerializeBasic, AndExpressionWithOneChildSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{$and: [{x: 0}]}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{$and: [{x: 0}]}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$and: [{x: {$eq: 0}}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -67,10 +68,10 @@ TEST(SerializeBasic, AndExpressionWithOneChildSerializesCorrectly) { } TEST(SerializeBasic, AndExpressionWithTwoChildrenSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{$and: [{x: 1}, {x: 2}]}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{$and: [{x: 1}, {x: 2}]}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$and: [{x: {$eq: 1}}, {x: {$eq: 2}}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -82,10 +83,10 @@ TEST(SerializeBasic, AndExpressionWithTwoChildrenSerializesCorrectly) { } TEST(SerializeBasic, AndExpressionWithTwoIdenticalChildrenSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{$and: [{x: 1}, {x: 1}]}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{$and: [{x: 1}, {x: 1}]}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$and: [{x: {$eq: 1}}, {x: {$eq: 1}}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -97,10 +98,10 @@ TEST(SerializeBasic, AndExpressionWithTwoIdenticalChildrenSerializesCorrectly) { } TEST(SerializeBasic, ExpressionOr) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{$or: [{x: 'A'}, {x: 'B'}]}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{$or: [{x: 'A'}, {x: 'B'}]}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$or: [{x: {$eq: 'A'}}, {x: {$eq: 'B'}}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -113,12 +114,11 @@ TEST(SerializeBasic, ExpressionOr) { } TEST(SerializeBasic, ExpressionElemMatchObjectSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original(fromjson("{x: {$elemMatch: {a: {$gt: 0}, b: {$gt: 0}}}}"), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$elemMatch: {$and: [{a: {$gt: 0}}, {b: {$gt: 0}}]}}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -131,12 +131,11 @@ TEST(SerializeBasic, ExpressionElemMatchObjectSerializesCorrectly) { } TEST(SerializeBasic, ExpressionElemMatchObjectWithEmptyStringSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original(fromjson("{'': {$elemMatch: {a: {$gt: 0}, b: {$gt: 0}}}}"), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{'': {$elemMatch: {$and: [{a: {$gt: 0}}, {b: {$gt: 0}}]}}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -149,11 +148,11 @@ TEST(SerializeBasic, ExpressionElemMatchObjectWithEmptyStringSerializesCorrectly } TEST(SerializeBasic, ExpressionElemMatchValueSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original( - fromjson("{x: {$elemMatch: {$lt: 1, $gt: -1}}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$elemMatch: {$lt: 1, $gt: -1}}}"), + ExtensionsCallbackNoop(), + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$elemMatch: {$lt: 1, $gt: -1}}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -168,14 +167,13 @@ TEST(SerializeBasic, ExpressionElemMatchValueSerializesCorrectly) { } TEST(SerializeBasic, ExpressionElemMatchValueWithRegexSerializesCorrectly) { - const CollatorInterface* collator = nullptr; const auto match = BSON("x" << BSON("$elemMatch" << BSON("$regex" << "abc" << "$options" << "i"))); - Matcher original(match, ExtensionsCallbackNoop(), collator); + Matcher original(match, ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), match); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -190,11 +188,11 @@ TEST(SerializeBasic, ExpressionElemMatchValueWithRegexSerializesCorrectly) { } TEST(SerializeBasic, ExpressionElemMatchValueWithEmptyStringSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original( - fromjson("{x: {$elemMatch: {$lt: 1, $gt: -1}}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$elemMatch: {$lt: 1, $gt: -1}}}"), + ExtensionsCallbackNoop(), + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$elemMatch: {$lt: 1, $gt: -1}}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -209,10 +207,9 @@ TEST(SerializeBasic, ExpressionElemMatchValueWithEmptyStringSerializesCorrectly) } TEST(SerializeBasic, ExpressionSizeSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$size: 2}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$size: 2}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$size: 2}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -224,10 +221,9 @@ TEST(SerializeBasic, ExpressionSizeSerializesCorrectly) { } TEST(SerializeBasic, ExpressionAllSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$all: [1, 2]}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$all: [1, 2]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$and: [{x: {$eq: 1}}, {x: {$eq: 2}}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -239,10 +235,9 @@ TEST(SerializeBasic, ExpressionAllSerializesCorrectly) { } TEST(SerializeBasic, ExpressionAllWithEmptyArraySerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$all: []}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$all: []}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$all: []}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -251,11 +246,10 @@ TEST(SerializeBasic, ExpressionAllWithEmptyArraySerializesCorrectly) { } TEST(SerializeBasic, ExpressionAllWithRegex) { - const CollatorInterface* collator = nullptr; Matcher original( - fromjson("{x: {$all: [/a.b.c/, /.d.e./]}}"), ExtensionsCallbackNoop(), collator); + fromjson("{x: {$all: [/a.b.c/, /.d.e./]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), BSON("$and" << BSON_ARRAY(BSON("x" << BSON("$regex" @@ -272,10 +266,9 @@ TEST(SerializeBasic, ExpressionAllWithRegex) { } TEST(SerializeBasic, ExpressionEqSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$eq: {a: 1}}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$eq: {a: 1}}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$eq: {a: 1}}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -290,10 +283,9 @@ TEST(SerializeBasic, ExpressionEqSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNeSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$ne: {a: 1}}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$ne: {a: 1}}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: {$eq: {a: 1}}}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -305,13 +297,12 @@ TEST(SerializeBasic, ExpressionNeSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNeWithRegexObjectSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original(BSON("x" << BSON("$ne" << BSON("$regex" << "abc"))), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), BSON("$nor" << BSON_ARRAY(BSON("x" << BSON("$eq" << BSON("$regex" << "abc")))))); @@ -325,10 +316,9 @@ TEST(SerializeBasic, ExpressionNeWithRegexObjectSerializesCorrectly) { } TEST(SerializeBasic, ExpressionLtSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$lt: 3}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$lt: 3}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$lt: 3}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -340,10 +330,9 @@ TEST(SerializeBasic, ExpressionLtSerializesCorrectly) { } TEST(SerializeBasic, ExpressionGtSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$gt: 3}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$gt: 3}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$gt: 3}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -355,10 +344,9 @@ TEST(SerializeBasic, ExpressionGtSerializesCorrectly) { } TEST(SerializeBasic, ExpressionGteSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$gte: 3}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$gte: 3}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$gte: 3}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -370,10 +358,9 @@ TEST(SerializeBasic, ExpressionGteSerializesCorrectly) { } TEST(SerializeBasic, ExpressionLteSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$lte: 3}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$lte: 3}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$lte: 3}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -385,10 +372,9 @@ TEST(SerializeBasic, ExpressionLteSerializesCorrectly) { } TEST(SerializeBasic, ExpressionRegexWithObjSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$regex: 'a.b'}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$regex: 'a.b'}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), BSON("x" << BSON("$regex" << "a.b"))); @@ -402,10 +388,9 @@ TEST(SerializeBasic, ExpressionRegexWithObjSerializesCorrectly) { } TEST(SerializeBasic, ExpressionRegexWithValueAndOptionsSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: /a.b/i}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: /a.b/i}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), BSON("x" << BSON("$regex" << "a.b" @@ -421,10 +406,9 @@ TEST(SerializeBasic, ExpressionRegexWithValueAndOptionsSerializesCorrectly) { } TEST(SerializeBasic, ExpressionRegexWithValueSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: /a.b/}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: /a.b/}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), BSON("x" << BSON("$regex" << "a.b"))); @@ -438,10 +422,10 @@ TEST(SerializeBasic, ExpressionRegexWithValueSerializesCorrectly) { } TEST(SerializeBasic, ExpressionRegexWithEqObjSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$eq: {$regex: 'a.b'}}}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{x: {$eq: {$regex: 'a.b'}}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$eq: {$regex: 'a.b'}}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -456,10 +440,9 @@ TEST(SerializeBasic, ExpressionRegexWithEqObjSerializesCorrectly) { } TEST(SerializeBasic, ExpressionModSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$mod: [2, 1]}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$mod: [2, 1]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$mod: [2, 1]}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -471,10 +454,9 @@ TEST(SerializeBasic, ExpressionModSerializesCorrectly) { } TEST(SerializeBasic, ExpressionExistsTrueSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$exists: true}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$exists: true}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$exists: true}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -486,10 +468,9 @@ TEST(SerializeBasic, ExpressionExistsTrueSerializesCorrectly) { } TEST(SerializeBasic, ExpressionExistsFalseSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$exists: false}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$exists: false}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: {$exists: true}}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -501,10 +482,9 @@ TEST(SerializeBasic, ExpressionExistsFalseSerializesCorrectly) { } TEST(SerializeBasic, ExpressionInSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$in: [1, 2, 3]}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$in: [1, 2, 3]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$in: [1, 2, 3]}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -519,10 +499,9 @@ TEST(SerializeBasic, ExpressionInSerializesCorrectly) { } TEST(SerializeBasic, ExpressionInWithEmptyArraySerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$in: []}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$in: []}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$in: []}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -531,10 +510,10 @@ TEST(SerializeBasic, ExpressionInWithEmptyArraySerializesCorrectly) { } TEST(SerializeBasic, ExpressionInWithRegexSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$in: [/\\d+/, /\\w+/]}}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{x: {$in: [/\\d+/, /\\w+/]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$in: [/\\d+/, /\\w+/]}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -549,10 +528,9 @@ TEST(SerializeBasic, ExpressionInWithRegexSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNinSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$nin: [1, 2, 3]}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$nin: [1, 2, 3]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: {$in: [1, 2, 3]}}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -567,11 +545,10 @@ TEST(SerializeBasic, ExpressionNinSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNinWithRegexValueSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original( - fromjson("{x: {$nin: [/abc/, /def/, /xyz/]}}"), ExtensionsCallbackNoop(), collator); + fromjson("{x: {$nin: [/abc/, /def/, /xyz/]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: {$in: [/abc/, /def/, /xyz/]}}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -586,10 +563,10 @@ TEST(SerializeBasic, ExpressionNinWithRegexValueSerializesCorrectly) { } TEST(SerializeBasic, ExpressionBitsAllSetSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$bitsAllSet: [1, 3]}}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{x: {$bitsAllSet: [1, 3]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$bitsAllSet: [1, 3]}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -601,10 +578,10 @@ TEST(SerializeBasic, ExpressionBitsAllSetSerializesCorrectly) { } TEST(SerializeBasic, ExpressionBitsAllClearSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$bitsAllClear: [1, 3]}}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{x: {$bitsAllClear: [1, 3]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$bitsAllClear: [1, 3]}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -616,10 +593,10 @@ TEST(SerializeBasic, ExpressionBitsAllClearSerializesCorrectly) { } TEST(SerializeBasic, ExpressionBitsAnySetSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$bitsAnySet: [1, 3]}}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{x: {$bitsAnySet: [1, 3]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$bitsAnySet: [1, 3]}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -631,10 +608,10 @@ TEST(SerializeBasic, ExpressionBitsAnySetSerializesCorrectly) { } TEST(SerializeBasic, ExpressionBitsAnyClearSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$bitsAnyClear: [1, 3]}}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{x: {$bitsAnyClear: [1, 3]}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$bitsAnyClear: [1, 3]}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -649,10 +626,9 @@ TEST(SerializeBasic, ExpressionBitsAnyClearSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNotSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$not: {$eq: 3}}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$not: {$eq: 3}}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{$and: [{x: {$eq: 3}}]}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -664,10 +640,10 @@ TEST(SerializeBasic, ExpressionNotSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNotWithMultipleChildrenSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$not: {$lt: 1, $gt: 3}}}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{x: {$not: {$lt: 1, $gt: 3}}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{$and: [{x: {$lt: 1}}, {x: {$gt: 3}}]}]}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -680,11 +656,10 @@ TEST(SerializeBasic, ExpressionNotWithMultipleChildrenSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNotWithBitTestSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original( - fromjson("{x: {$not: {$bitsAnySet: [1, 3]}}}"), ExtensionsCallbackNoop(), collator); + fromjson("{x: {$not: {$bitsAnySet: [1, 3]}}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{$and: [{x: {$bitsAnySet: [1, 3]}}]}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -697,10 +672,10 @@ TEST(SerializeBasic, ExpressionNotWithBitTestSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNotWithRegexObjSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$not: {$regex: 'a.b'}}}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{x: {$not: {$regex: 'a.b'}}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), BSON("$nor" << BSON_ARRAY(BSON("x" << BSON("$regex" << "a.b"))))); @@ -714,10 +689,9 @@ TEST(SerializeBasic, ExpressionNotWithRegexObjSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNotWithRegexValueSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$not: /a.b/}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$not: /a.b/}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), BSON("$nor" << BSON_ARRAY(BSON("x" << BSON("$regex" << "a.b"))))); @@ -731,10 +705,9 @@ TEST(SerializeBasic, ExpressionNotWithRegexValueSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNotWithRegexValueAndOptionsSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$not: /a.b/i}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$not: /a.b/i}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), BSON("$nor" << BSON_ARRAY(BSON("x" << BSON("$regex" << "a.b" @@ -750,14 +723,13 @@ TEST(SerializeBasic, ExpressionNotWithRegexValueAndOptionsSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNotWithGeoSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original(fromjson("{x: {$not: {$geoIntersects: {$geometry: {type: 'Polygon', " "coordinates: [[[0,0], [5,0], " "[5, 5], [0, 5], [0, 0]]]}}}}}"), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ( *reserialized.getQuery(), fromjson("{$nor: [{$and: [{x: {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: " @@ -780,11 +752,10 @@ TEST(SerializeBasic, ExpressionNotWithGeoSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNorSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original( - fromjson("{$nor: [{x: 3}, {x: {$lt: 1}}]}"), ExtensionsCallbackNoop(), collator); + fromjson("{$nor: [{x: 3}, {x: {$lt: 1}}]}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$nor: [{x: {$eq: 3}}, {x: {$lt: 1}}]}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -799,10 +770,9 @@ TEST(SerializeBasic, ExpressionNorSerializesCorrectly) { } TEST(SerializeBasic, ExpressionTypeSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$type: 2}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$type: 2}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$type: 2}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -814,10 +784,9 @@ TEST(SerializeBasic, ExpressionTypeSerializesCorrectly) { } TEST(SerializeBasic, ExpressionTypeWithNumberSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{x: {$type: 'number'}}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{x: {$type: 'number'}}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$type: 'number'}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -829,10 +798,9 @@ TEST(SerializeBasic, ExpressionTypeWithNumberSerializesCorrectly) { } TEST(SerializeBasic, ExpressionEmptySerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -841,10 +809,10 @@ TEST(SerializeBasic, ExpressionEmptySerializesCorrectly) { } TEST(SerializeBasic, ExpressionWhereSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{$where: 'this.a == this.b'}"), ExtensionsCallbackNoop(), collator); + Matcher original( + fromjson("{$where: 'this.a == this.b'}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ( *reserialized.getQuery(), BSONObjBuilder().appendCodeWScope("$where", "this.a == this.b", BSONObj()).obj()); @@ -852,22 +820,20 @@ TEST(SerializeBasic, ExpressionWhereSerializesCorrectly) { } TEST(SerializeBasic, ExpressionWhereWithScopeSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original(BSON("$where" << BSONCodeWScope("this.a == this.b", BSON("x" << 3))), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), BSON("$where" << BSONCodeWScope("this.a == this.b", BSON("x" << 3)))); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); } TEST(SerializeBasic, ExpressionCommentSerializesCorrectly) { - const CollatorInterface* collator = nullptr; - Matcher original(fromjson("{$comment: 'Hello'}"), ExtensionsCallbackNoop(), collator); + Matcher original(fromjson("{$comment: 'Hello'}"), ExtensionsCallbackNoop(), kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); @@ -879,15 +845,14 @@ TEST(SerializeBasic, ExpressionCommentSerializesCorrectly) { } TEST(SerializeBasic, ExpressionGeoWithinSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original( fromjson( "{x: {$geoWithin: {$geometry: " "{type: 'Polygon', coordinates: [[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0]]]}}}}"), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ( *reserialized.getQuery(), fromjson("{x: {$geoWithin: {$geometry: {type: 'Polygon', coordinates: [[[0,0], [10,0], " @@ -902,15 +867,14 @@ TEST(SerializeBasic, ExpressionGeoWithinSerializesCorrectly) { } TEST(SerializeBasic, ExpressionGeoIntersectsSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original( fromjson( "{x: {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: [[[0,0], [5,0], [5, " "5], [0, 5], [0, 0]]]}}}}"), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ( *reserialized.getQuery(), fromjson("{x: {$geoIntersects: {$geometry: {type: 'Polygon', coordinates: [[[0,0], [5,0], " @@ -932,14 +896,13 @@ TEST(SerializeBasic, ExpressionGeoIntersectsSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNearSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original( fromjson("{x: {$near: {$geometry: {type: 'Point', coordinates: [0, 0]}, $maxDistance: 10, " "$minDistance: 1}}}"), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ( *reserialized.getQuery(), fromjson("{x: {$near: {$geometry: {type: 'Point', coordinates: [0, 0]}, $maxDistance: 10, " @@ -948,15 +911,14 @@ TEST(SerializeBasic, ExpressionNearSerializesCorrectly) { } TEST(SerializeBasic, ExpressionNearSphereSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original( fromjson( "{x: {$nearSphere: {$geometry: {type: 'Point', coordinates: [0, 0]}, $maxDistance: 10, " "$minDistance: 1}}}"), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ( *reserialized.getQuery(), fromjson("{x: {$nearSphere: {$geometry: {type: 'Point', coordinates: [0, 0]}, " @@ -965,12 +927,11 @@ TEST(SerializeBasic, ExpressionNearSphereSerializesCorrectly) { } TEST(SerializeBasic, ExpressionTextSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original(fromjson("{$text: {$search: 'a', $language: 'en', $caseSensitive: true}}"), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$text: {$search: 'a', $language: 'en', $caseSensitive: true, " "$diacriticSensitive: false}}")); @@ -978,12 +939,11 @@ TEST(SerializeBasic, ExpressionTextSerializesCorrectly) { } TEST(SerializeBasic, ExpressionTextWithDefaultLanguageSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original(fromjson("{$text: {$search: 'a', $caseSensitive: false}}"), ExtensionsCallbackNoop(), - collator); + kSimpleCollator); Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), collator); + serialize(original.getMatchExpression()), ExtensionsCallbackNoop(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$text: {$search: 'a', $language: '', $caseSensitive: false, " "$diacriticSensitive: false}}")); @@ -991,26 +951,37 @@ TEST(SerializeBasic, ExpressionTextWithDefaultLanguageSerializesCorrectly) { } TEST(SerializeInternalSchema, ExpressionInternalSchemaMinItemsSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original(fromjson("{x: {$_internalSchemaMinItems: 1}}"), ExtensionsCallbackDisallowExtensions(), - collator); - Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackDisallowExtensions(), collator); + kSimpleCollator); + Matcher reserialized(serialize(original.getMatchExpression()), + ExtensionsCallbackDisallowExtensions(), + kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$_internalSchemaMinItems: 1}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); } TEST(SerializeInternalSchema, ExpressionInternalSchemaMaxItemsSerializesCorrectly) { - const CollatorInterface* collator = nullptr; Matcher original(fromjson("{x: {$_internalSchemaMaxItems: 1}}"), ExtensionsCallbackDisallowExtensions(), - collator); - Matcher reserialized( - serialize(original.getMatchExpression()), ExtensionsCallbackDisallowExtensions(), collator); + kSimpleCollator); + Matcher reserialized(serialize(original.getMatchExpression()), + ExtensionsCallbackDisallowExtensions(), + kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$_internalSchemaMaxItems: 1}}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); } +TEST(SerializeInternalSchema, ExpressionInternalSchemaUniqueItemsSerializesCorrectly) { + Matcher original(fromjson("{x: {$_internalSchemaUniqueItems: true}}"), + ExtensionsCallbackDisallowExtensions(), + kSimpleCollator); + Matcher reserialized(serialize(original.getMatchExpression()), + ExtensionsCallbackDisallowExtensions(), + kSimpleCollator); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), + fromjson("{x: {$_internalSchemaUniqueItems: true}}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); +} } // namespace } // namespace mongo 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 new file mode 100644 index 00000000000..f35d14eb69b --- /dev/null +++ b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.cpp @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/matcher/schema/expression_internal_schema_unique_items.h" + +namespace mongo { +constexpr StringData InternalSchemaUniqueItemsMatchExpression::kName; + +void InternalSchemaUniqueItemsMatchExpression::debugString(StringBuilder& debug, int level) const { + _debugAddSpace(debug, level); + + BSONObjBuilder builder; + serialize(&builder); + debug << builder.obj().toString() << "\n"; + + const auto* tag = getTag(); + if (tag) { + debug << " "; + tag->debugString(&debug); + } + debug << "\n"; +} + +bool InternalSchemaUniqueItemsMatchExpression::equivalent(const MatchExpression* expr) const { + if (matchType() != expr->matchType()) { + return false; + } + + const auto* other = static_cast<const InternalSchemaUniqueItemsMatchExpression*>(expr); + return path() == other->path(); +} + +void InternalSchemaUniqueItemsMatchExpression::serialize(BSONObjBuilder* builder) const { + BSONObjBuilder subobj(builder->subobjStart(path())); + subobj.append(kName, true); + subobj.doneFast(); +} + +std::unique_ptr<MatchExpression> InternalSchemaUniqueItemsMatchExpression::shallowClone() const { + auto clone = stdx::make_unique<InternalSchemaUniqueItemsMatchExpression>(); + invariantOK(clone->init(path())); + if (getTag()) { + clone->setTag(getTag()->clone()); + } + return std::move(clone); +} +} // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h new file mode 100644 index 00000000000..c1bfc208a91 --- /dev/null +++ b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items.h @@ -0,0 +1,74 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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 + +#include <utility> + +#include "mongo/bson/bsonelement_comparator.h" +#include "mongo/db/matcher/expression_array.h" + +namespace mongo { + +/** + * Matches arrays whose elements are all unique. + */ +class InternalSchemaUniqueItemsMatchExpression final : public ArrayMatchingMatchExpression { +public: + static constexpr StringData kName = "$_internalSchemaUniqueItems"_sd; + + InternalSchemaUniqueItemsMatchExpression() + : ArrayMatchingMatchExpression(MatchExpression::INTERNAL_SCHEMA_UNIQUE_ITEMS) {} + + Status init(StringData path) { + return setPath(path); + } + + bool matchesArray(const BSONObj& array, MatchDetails*) const final { + auto set = _comparator.makeBSONEltSet(); + for (auto&& elem : array) { + if (!std::get<bool>(set.insert(elem))) { + return false; + } + } + return true; + } + + void debugString(StringBuilder& builder, int level) const final; + + bool equivalent(const MatchExpression* other) const final; + + void serialize(BSONObjBuilder* builder) const final; + + std::unique_ptr<MatchExpression> shallowClone() const final; + +private: + // The comparator to use when comparing BSONElements, which will never use a collation. + BSONElementComparator _comparator{BSONElementComparator::FieldNamesMode::kIgnore, nullptr}; +}; +} // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_unique_items_test.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items_test.cpp new file mode 100644 index 00000000000..16628681ae1 --- /dev/null +++ b/src/mongo/db/matcher/schema/expression_internal_schema_unique_items_test.cpp @@ -0,0 +1,111 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/bson/json.h" +#include "mongo/db/matcher/schema/expression_internal_schema_unique_items.h" +#include "mongo/db/query/collation/collator_interface_mock.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { +TEST(InternalSchemaUniqueItemsMatchExpression, RejectsNonArrays) { + InternalSchemaUniqueItemsMatchExpression uniqueItems; + ASSERT_OK(uniqueItems.init("foo")); + ASSERT_FALSE(uniqueItems.matchesBSON(BSON("foo" << 1))); + ASSERT_FALSE(uniqueItems.matchesBSON(BSON("foo" << BSONObj()))); + ASSERT_FALSE(uniqueItems.matchesBSON(BSON("foo" + << "string"))); +} + +TEST(InternalSchemaUniqueItemsMatchExpression, MatchesEmptyArray) { + InternalSchemaUniqueItemsMatchExpression uniqueItems; + ASSERT_OK(uniqueItems.init("foo")); + ASSERT_TRUE(uniqueItems.matchesBSON(BSON("foo" << BSONArray()))); +} + +TEST(InternalSchemaUniqueItemsMatchExpression, MatchesOneElementArray) { + InternalSchemaUniqueItemsMatchExpression uniqueItems; + ASSERT_OK(uniqueItems.init("foo")); + ASSERT_TRUE(uniqueItems.matchesBSON(BSON("foo" << BSON_ARRAY(1)))); + ASSERT_TRUE(uniqueItems.matchesBSON(BSON("foo" << BSON_ARRAY(BSONObj())))); + ASSERT_TRUE(uniqueItems.matchesBSON(BSON("foo" << BSON_ARRAY(BSON_ARRAY(9 << "bar"))))); +} + +TEST(InternalSchemaUniqueItemsMatchExpression, MatchesArrayOfUniqueItems) { + InternalSchemaUniqueItemsMatchExpression uniqueItems; + ASSERT_OK(uniqueItems.init("foo")); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: [1, 'bar', {}, [], null]}"))); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: [{x: 1}, {x: 2}, {x: 2, y: 3}]}"))); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: [[1], [1, 2], 1]}"))); +} + +TEST(InternalSchemaUniqueItemsMatchExpression, MatchesNestedArrayOfUniqueItems) { + InternalSchemaUniqueItemsMatchExpression uniqueItems; + ASSERT_OK(uniqueItems.init("foo.bar")); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: {bar: [1, 'bar', {}, [], null]}}"))); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: {bar: [{x: 1}, {x: 2}, {x: 2, y: 3}]}}"))); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: {bar: [[1], [1, 2], 1]}}"))); +} + +TEST(InternalSchemaUniqueItemsMatchExpression, RejectsArrayWithDuplicates) { + InternalSchemaUniqueItemsMatchExpression uniqueItems; + ASSERT_OK(uniqueItems.init("foo")); + ASSERT_FALSE(uniqueItems.matchesBSON(fromjson("{foo: [1, 1, 1]}"))); + ASSERT_FALSE(uniqueItems.matchesBSON(fromjson("{foo: [['bar'], ['bar']]}"))); +} + +TEST(InternalSchemaUniqueItemsMatchExpression, RejectsNestedArrayWithDuplicates) { + InternalSchemaUniqueItemsMatchExpression uniqueItems; + ASSERT_OK(uniqueItems.init("foo")); + ASSERT_FALSE(uniqueItems.matchesBSON(fromjson("{foo: {bar: [1, 1, 1]}}"))); + ASSERT_FALSE(uniqueItems.matchesBSON(fromjson("{foo: {bar: [['baz'], ['baz']]}}"))); +} + +TEST(InternalSchemaUniqueItemsMatchExpression, FieldNameSignificantWhenComparingNestedObjects) { + InternalSchemaUniqueItemsMatchExpression uniqueItems; + ASSERT_OK(uniqueItems.init("foo")); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: [{x: 7}, {y: 7}]}"))); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: [{a: 'bar'}, {b: 'bar'}]}"))); + ASSERT_FALSE(uniqueItems.matchesBSON(fromjson("{foo: [{a: 'bar'}, {a: 'bar'}]}"))); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: [[1, 2, 3], [1, 3, 2]]}"))); +} + +TEST(InternalSchemaUniqueItemsMatchExpression, AlwaysUsesBinaryComparisonRegardlessOfCollator) { + InternalSchemaUniqueItemsMatchExpression uniqueItems; + ASSERT_OK(uniqueItems.init("foo")); + CollatorInterfaceMock collator(CollatorInterfaceMock::MockType::kAlwaysEqual); + uniqueItems.setCollator(&collator); + + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: ['one', 'OnE', 'ONE']}"))); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: [{x: 'two'}, {y: 'two'}]}"))); + ASSERT_TRUE(uniqueItems.matchesBSON(fromjson("{foo: [{a: 'three'}, {a: 'THREE'}]}"))); +} +} // namespace +} // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp b/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp index 877bc6fad3b..fadd978c6e5 100644 --- a/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp +++ b/src/mongo/db/matcher/schema/expression_parser_schema_test.cpp @@ -28,25 +28,28 @@ #include "mongo/platform/basic.h" +#include "mongo/bson/json.h" #include "mongo/db/matcher/expression.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/matcher/extensions_callback_disallow_extensions.h" #include "mongo/db/matcher/schema/expression_internal_schema_max_items.h" #include "mongo/db/matcher/schema/expression_internal_schema_min_items.h" +#include "mongo/db/matcher/schema/expression_internal_schema_unique_items.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { +constexpr CollatorInterface* kSimpleCollator = nullptr; + // // Tests for parsing the $_internalSchemaMinItems expression. // TEST(MatchExpressionParserInternalSchemaMinItemsTest, CorrectlyParsesIntegerArgument) { BSONObj query = BSON("x" << BSON("$_internalSchemaMinItems" << 2)); - const CollatorInterface* collator = nullptr; - StatusWithMatchExpression result = - MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); + StatusWithMatchExpression result = MatchExpressionParser::parse( + query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); @@ -57,9 +60,8 @@ TEST(MatchExpressionParserInternalSchemaMinItemsTest, CorrectlyParsesIntegerArgu TEST(MatchExpressionParserInternalSchemaMinItemsTest, CorrectlyParsesLongArgument) { BSONObj query = BSON("x" << BSON("$_internalSchemaMinItems" << 2LL)); - const CollatorInterface* collator = nullptr; - StatusWithMatchExpression result = - MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); + StatusWithMatchExpression result = MatchExpressionParser::parse( + query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); @@ -71,9 +73,8 @@ TEST(MatchExpressionParserInternalSchemaMinItemsTest, CorrectlyParsesLongArgumen TEST(MatchExpressionParserInternalSchemaMinItemsTest, CorrectlyParsesDoubleArgumentAsInteger) { BSONObj query = BSON("x" << BSON("$_internalSchemaMinItems" << 2.0)); - const CollatorInterface* collator = nullptr; - StatusWithMatchExpression result = - MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); + StatusWithMatchExpression result = MatchExpressionParser::parse( + query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); @@ -84,9 +85,8 @@ TEST(MatchExpressionParserInternalSchemaMinItemsTest, CorrectlyParsesDoubleArgum TEST(MatchExpressionParserInternalSchemaMinItemsTest, CorrectlyParsesDecimalArgumentAsInteger) { BSONObj query = BSON("x" << BSON("$_internalSchemaMinItems" << Decimal128("2"))); - const CollatorInterface* collator = nullptr; - StatusWithMatchExpression result = - MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); + StatusWithMatchExpression result = MatchExpressionParser::parse( + query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); @@ -100,9 +100,8 @@ TEST(MatchExpressionParserInternalSchemaMinItemsTest, CorrectlyParsesDecimalArgu // TEST(MatchExpressionParserInternalSchemaMaxItemsTest, CorrectlyParsesIntegerArgument) { BSONObj query = BSON("x" << BSON("$_internalSchemaMaxItems" << 2)); - const CollatorInterface* collator = nullptr; - StatusWithMatchExpression result = - MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); + StatusWithMatchExpression result = MatchExpressionParser::parse( + query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); @@ -113,9 +112,8 @@ TEST(MatchExpressionParserInternalSchemaMaxItemsTest, CorrectlyParsesIntegerArgu TEST(MatchExpressionParserInternalSchemaMaxItemsTest, CorrectlyParsesLongArgument) { BSONObj query = BSON("x" << BSON("$_internalSchemaMaxItems" << 2LL)); - const CollatorInterface* collator = nullptr; - StatusWithMatchExpression result = - MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); + StatusWithMatchExpression result = MatchExpressionParser::parse( + query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); @@ -127,9 +125,8 @@ TEST(MatchExpressionParserInternalSchemaMaxItemsTest, CorrectlyParsesLongArgumen TEST(MatchExpressionParserInternalSchemaMaxItemsTest, CorrectlyParsesDoubleArgumentAsInteger) { BSONObj query = BSON("x" << BSON("$_internalSchemaMaxItems" << 2.0)); - const CollatorInterface* collator = nullptr; - StatusWithMatchExpression result = - MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); + StatusWithMatchExpression result = MatchExpressionParser::parse( + query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); @@ -140,9 +137,8 @@ TEST(MatchExpressionParserInternalSchemaMaxItemsTest, CorrectlyParsesDoubleArgum TEST(MatchExpressionParserInternalSchemaMaxItemsTest, CorrectlyParsesDecimalArgumentAsInteger) { BSONObj query = BSON("x" << BSON("$_internalSchemaMaxItems" << Decimal128("2"))); - const CollatorInterface* collator = nullptr; - StatusWithMatchExpression result = - MatchExpressionParser::parse(query, ExtensionsCallbackDisallowExtensions(), collator); + StatusWithMatchExpression result = MatchExpressionParser::parse( + query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(BSON("x" << 1))); @@ -151,5 +147,42 @@ TEST(MatchExpressionParserInternalSchemaMaxItemsTest, CorrectlyParsesDecimalArgu ASSERT(!result.getValue()->matchesBSON(BSON("x" << BSON_ARRAY(1 << 2 << 3)))); } +TEST(MatchExpressionParserInternalSchemaUniqueItemsTest, FailsToParseNonTrueArguments) { + auto queryIntArgument = BSON("x" << BSON("$_internalSchemaUniqueItems" << 0)); + auto expr = MatchExpressionParser::parse( + queryIntArgument, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); + ASSERT_NOT_OK(expr.getStatus()); + + auto queryStringArgument = BSON("x" << BSON("$_internalSchemaUniqueItems" + << "")); + expr = MatchExpressionParser::parse( + queryStringArgument, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); + ASSERT_NOT_OK(expr.getStatus()); + + auto queryDoubleArgument = BSON("x" << BSON("$_internalSchemaUniqueItems" << 1.0)); + expr = MatchExpressionParser::parse( + queryDoubleArgument, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); + ASSERT_NOT_OK(expr.getStatus()); + + auto queryFalseArgument = BSON("x" << BSON("$_internalSchemaUniqueItems" << false)); + expr = MatchExpressionParser::parse( + queryFalseArgument, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); + ASSERT_NOT_OK(expr.getStatus()); +} + +TEST(MatchExpressionParserInternalSchemaUniqueItemsTest, ParsesTrueBooleanArgument) { + auto query = BSON("x" << BSON("$_internalSchemaUniqueItems" << true)); + auto expr = MatchExpressionParser::parse( + query, ExtensionsCallbackDisallowExtensions(), kSimpleCollator); + ASSERT_OK(expr.getStatus()); + + ASSERT_FALSE(expr.getValue()->matchesBSON(fromjson("{x: 1}"))); + ASSERT_FALSE(expr.getValue()->matchesBSON(fromjson("{x: 'blah'}"))); + ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{x: []}"))); + ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{x: [0]}"))); + ASSERT_TRUE(expr.getValue()->matchesBSON(fromjson("{x: ['7', null, [], {}, 7]}"))); + ASSERT_FALSE(expr.getValue()->matchesBSON(fromjson("{x: ['dup', 'dup', 7]}"))); + ASSERT_FALSE(expr.getValue()->matchesBSON(fromjson("{x: [{x: 1}, {x: 1}]}"))); +} } // namespace } // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp index 1ce429bb6be..db34c1f746a 100644 --- a/src/mongo/db/pipeline/document_source_match.cpp +++ b/src/mongo/db/pipeline/document_source_match.cpp @@ -275,6 +275,7 @@ Document redactSafePortionDollarOps(BSONObj expr) { case BSONObj::opGEO_INTERSECTS: case BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS: case BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS: + case BSONObj::opINTERNAL_SCHEMA_UNIQUE_ITEMS: continue; } } diff --git a/src/mongo/db/pipeline/document_source_match_test.cpp b/src/mongo/db/pipeline/document_source_match_test.cpp index b84b472f41e..9ee753078a4 100644 --- a/src/mongo/db/pipeline/document_source_match_test.cpp +++ b/src/mongo/db/pipeline/document_source_match_test.cpp @@ -110,6 +110,8 @@ TEST_F(DocumentSourceMatchTest, RedactSafePortion) { assertExpectedRedactSafePortion("{a: {$_internalSchemaMaxItems: 1}}", "{}"); + assertExpectedRedactSafePortion("{a: {$_internalSchemaUniqueItems: true}}", "{}"); + // Combinations assertExpectedRedactSafePortion("{a:1, b: 'asdf'}", "{a:1, b: 'asdf'}"); diff --git a/src/mongo/db/query/plan_cache.cpp b/src/mongo/db/query/plan_cache.cpp index 124a7d3b998..7cb5210ca0c 100644 --- a/src/mongo/db/query/plan_cache.cpp +++ b/src/mongo/db/query/plan_cache.cpp @@ -177,9 +177,11 @@ const char* encodeMatchType(MatchExpression::MatchType mt) { case MatchExpression::INTERNAL_SCHEMA_MAX_ITEMS: return "internalSchemaMaxItems"; break; + case MatchExpression::INTERNAL_SCHEMA_UNIQUE_ITEMS: + return "internalSchemaUniqueItems"; + break; case MatchExpression::INTERNAL_SCHEMA_XOR: return "internalSchemaXor"; - break; default: MONGO_UNREACHABLE; return ""; |