diff options
author | Anne Lim <anne.lim@mongodb.com> | 2017-07-19 10:57:11 -0400 |
---|---|---|
committer | Anne Lim <anne.lim@mongodb.com> | 2017-07-19 18:08:09 -0400 |
commit | 634435949c4b855b9cc5bfbf5cf481d8158fd996 (patch) | |
tree | f473a7ac7ff49a92bb5447f97c0afad220eede77 /src/mongo | |
parent | e034bdc4be7de0326d34e982febf00505cf0d993 (diff) | |
download | mongo-634435949c4b855b9cc5bfbf5cf481d8158fd996.tar.gz |
SERVER-29583: Create $_internalSchemaMinProperties and $_internalSchemaMaxProperties MatchExpressions
Diffstat (limited to 'src/mongo')
22 files changed, 697 insertions, 4 deletions
diff --git a/src/mongo/bson/bsonelement.cpp b/src/mongo/bson/bsonelement.cpp index ecb1f932eef..b73e9f941a7 100644 --- a/src/mongo/bson/bsonelement.cpp +++ b/src/mongo/bson/bsonelement.cpp @@ -340,6 +340,8 @@ const StringMap<BSONObj::MatchType> queryOperatorMap{ {"_internalSchemaObjectMatch", BSONObj::opINTERNAL_SCHEMA_OBJECT_MATCH}, {"_internalSchemaMinLength", BSONObj::opINTERNAL_SCHEMA_MIN_LENGTH}, {"_internalSchemaMaxLength", BSONObj::opINTERNAL_SCHEMA_MAX_LENGTH}, + {"_internalSchemaMinProperties", BSONObj::opINTERNAL_SCHEMA_MIN_PROPERTIES}, + {"_internalSchemaMaxProperties", BSONObj::opINTERNAL_SCHEMA_MAX_PROPERTIES}, }; // Compares two string elements using a simple binary compare. diff --git a/src/mongo/bson/bsonelement.cpp.rej b/src/mongo/bson/bsonelement.cpp.rej new file mode 100644 index 00000000000..d644f71ebeb --- /dev/null +++ b/src/mongo/bson/bsonelement.cpp.rej @@ -0,0 +1,17 @@ +*************** +*** 336,341 **** + {"bitsAnyClear", BSONObj::opBITS_ANY_CLEAR}, + {"_internalSchemaMinItems", BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS}, + {"_internalSchemaMaxItems", BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS}, + }; + + // Compares two string elements using a simple binary compare. +--- 336,343 ---- + {"bitsAnyClear", BSONObj::opBITS_ANY_CLEAR}, + {"_internalSchemaMinItems", BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS}, + {"_internalSchemaMaxItems", BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS}, ++ {"_internalSchemaMinProperties", BSONObj::opINTERNAL_SCHEMA_MIN_PROPERTIES}, ++ {"_internalSchemaMaxProperties", BSONObj::opINTERNAL_SCHEMA_MAX_PROPERTIES}, + }; + + // Compares two string elements using a simple binary compare. diff --git a/src/mongo/bson/bsonelement.cpp.rej.rej b/src/mongo/bson/bsonelement.cpp.rej.rej new file mode 100644 index 00000000000..d644f71ebeb --- /dev/null +++ b/src/mongo/bson/bsonelement.cpp.rej.rej @@ -0,0 +1,17 @@ +*************** +*** 336,341 **** + {"bitsAnyClear", BSONObj::opBITS_ANY_CLEAR}, + {"_internalSchemaMinItems", BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS}, + {"_internalSchemaMaxItems", BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS}, + }; + + // Compares two string elements using a simple binary compare. +--- 336,343 ---- + {"bitsAnyClear", BSONObj::opBITS_ANY_CLEAR}, + {"_internalSchemaMinItems", BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS}, + {"_internalSchemaMaxItems", BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS}, ++ {"_internalSchemaMinProperties", BSONObj::opINTERNAL_SCHEMA_MIN_PROPERTIES}, ++ {"_internalSchemaMaxProperties", BSONObj::opINTERNAL_SCHEMA_MAX_PROPERTIES}, + }; + + // Compares two string elements using a simple binary compare. diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h index 8a6e41f88f9..1498cd3a1c2 100644 --- a/src/mongo/bson/bsonobj.h +++ b/src/mongo/bson/bsonobj.h @@ -542,6 +542,8 @@ public: opINTERNAL_SCHEMA_OBJECT_MATCH = 0x1E, opINTERNAL_SCHEMA_MIN_LENGTH = 0x1F, opINTERNAL_SCHEMA_MAX_LENGTH = 0x20, + opINTERNAL_SCHEMA_MIN_PROPERTIES = 0x21, + opINTERNAL_SCHEMA_MAX_PROPERTIES = 0x22, }; /** add all elements of the object to the specified vector */ diff --git a/src/mongo/bson/bsonobj.h.rej b/src/mongo/bson/bsonobj.h.rej new file mode 100644 index 00000000000..41eb1d84c17 --- /dev/null +++ b/src/mongo/bson/bsonobj.h.rej @@ -0,0 +1,17 @@ +*************** +*** 530,535 **** + opBITS_ANY_CLEAR = 0x1A, + opINTERNAL_SCHEMA_MIN_ITEMS = 0x1B, + opINTERNAL_SCHEMA_MAX_ITEMS = 0x1C, + }; + + /** add all elements of the object to the specified vector */ +--- 530,537 ---- + opBITS_ANY_CLEAR = 0x1A, + opINTERNAL_SCHEMA_MIN_ITEMS = 0x1B, + opINTERNAL_SCHEMA_MAX_ITEMS = 0x1C, ++ opINTERNAL_SCHEMA_MIN_PROPERTIES = 0x1D, ++ opINTERNAL_SCHEMA_MAX_PROPERTIES = 0x1E, + }; + + /** 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 edece7bd350..088f5dc1729 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_num_properties.cpp', 'schema/expression_internal_schema_object_match.cpp', 'schema/expression_internal_schema_str_length.cpp', 'schema/expression_internal_schema_unique_items.cpp', @@ -72,8 +73,10 @@ env.CppUnitTest( 'expression_tree_test.cpp', 'schema/expression_internal_schema_max_items_test.cpp', 'schema/expression_internal_schema_max_length_test.cpp', + 'schema/expression_internal_schema_max_properties_test.cpp', 'schema/expression_internal_schema_min_items_test.cpp', 'schema/expression_internal_schema_min_length_test.cpp', + 'schema/expression_internal_schema_min_properties_test.cpp', 'schema/expression_internal_schema_object_match_test.cpp', 'schema/expression_internal_schema_unique_items_test.cpp', 'schema/expression_internal_schema_xor_test.cpp', diff --git a/src/mongo/db/matcher/SConscript.rej b/src/mongo/db/matcher/SConscript.rej new file mode 100644 index 00000000000..78394abb15f --- /dev/null +++ b/src/mongo/db/matcher/SConscript.rej @@ -0,0 +1,33 @@ +*************** +*** 47,52 **** + 'matcher.cpp', + 'schema/expression_internal_schema_num_array_items.cpp', + 'schema/expression_internal_schema_xor.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', +--- 47,53 ---- + 'matcher.cpp', + 'schema/expression_internal_schema_num_array_items.cpp', + 'schema/expression_internal_schema_xor.cpp', ++ 'schema/expression_internal_schema_num_properties.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', +*************** +*** 68,73 **** + 'expression_tree_test.cpp', + 'schema/expression_internal_schema_max_items_test.cpp', + 'schema/expression_internal_schema_min_items_test.cpp', + 'schema/expression_internal_schema_xor_test.cpp', + ], + LIBDEPS=[ +--- 69,76 ---- + 'expression_tree_test.cpp', + 'schema/expression_internal_schema_max_items_test.cpp', + 'schema/expression_internal_schema_min_items_test.cpp', ++ 'schema/expression_internal_schema_min_properties_test.cpp', ++ 'schema/expression_internal_schema_max_properties_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 7dba2d16dc6..5159be3fefb 100644 --- a/src/mongo/db/matcher/expression.h +++ b/src/mongo/db/matcher/expression.h @@ -102,6 +102,8 @@ public: INTERNAL_SCHEMA_MIN_ITEMS, INTERNAL_SCHEMA_OBJECT_MATCH, INTERNAL_SCHEMA_UNIQUE_ITEMS, + INTERNAL_SCHEMA_MIN_PROPERTIES, + INTERNAL_SCHEMA_MAX_PROPERTIES, INTERNAL_SCHEMA_XOR, INTERNAL_SCHEMA_MIN_LENGTH, INTERNAL_SCHEMA_MAX_LENGTH, diff --git a/src/mongo/db/matcher/expression.h.rej b/src/mongo/db/matcher/expression.h.rej new file mode 100644 index 00000000000..9b644b63e10 --- /dev/null +++ b/src/mongo/db/matcher/expression.h.rej @@ -0,0 +1,17 @@ +*************** +*** 100,105 **** + // JSON Schema expressions. + INTERNAL_SCHEMA_MIN_ITEMS, + INTERNAL_SCHEMA_MAX_ITEMS, + INTERNAL_SCHEMA_XOR, + }; + +--- 100,107 ---- + // JSON Schema expressions. + INTERNAL_SCHEMA_MIN_ITEMS, + INTERNAL_SCHEMA_MAX_ITEMS, ++ INTERNAL_SCHEMA_MIN_PROPERTIES, ++ INTERNAL_SCHEMA_MAX_PROPERTIES, + INTERNAL_SCHEMA_XOR, + }; + diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp index 502305ab995..b436e0d02f7 100644 --- a/src/mongo/db/matcher/expression_parser.cpp +++ b/src/mongo/db/matcher/expression_parser.cpp @@ -38,8 +38,10 @@ #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_max_length.h" +#include "mongo/db/matcher/schema/expression_internal_schema_max_properties.h" #include "mongo/db/matcher/schema/expression_internal_schema_min_items.h" #include "mongo/db/matcher/schema/expression_internal_schema_min_length.h" +#include "mongo/db/matcher/schema/expression_internal_schema_min_properties.h" #include "mongo/db/matcher/schema/expression_internal_schema_object_match.h" #include "mongo/db/matcher/schema/expression_internal_schema_unique_items.h" #include "mongo/db/matcher/schema/expression_internal_schema_xor.h" @@ -341,7 +343,6 @@ StatusWithMatchExpression MatchExpressionParser::_parseSubField(const BSONObj& c InternalSchemaMaxLengthMatchExpression>(name, e); } } - return {Status(ErrorCodes::BadValue, mongoutils::str::stream() << "not handled: " << e.fieldName())}; } @@ -429,12 +430,17 @@ StatusWithMatchExpression MatchExpressionParser::_parse(const BSONObj& obj, } return JSONSchemaParser::parse(e.Obj()); + } else if (mongoutils::str::equals("_internalSchemaMinProperties", rest)) { + return _parseTopLevelInternalSchemaSingleIntegerArgument< + InternalSchemaMinPropertiesMatchExpression>(e); + } else if (mongoutils::str::equals("_internalSchemaMaxProperties", rest)) { + return _parseTopLevelInternalSchemaSingleIntegerArgument< + InternalSchemaMaxPropertiesMatchExpression>(e); } else { return {Status(ErrorCodes::BadValue, mongoutils::str::stream() << "unknown top level operator: " << e.fieldName())}; } - continue; } @@ -783,7 +789,9 @@ StatusWithMatchExpression MatchExpressionParser::_parseElemMatch(const char* nam !mongoutils::str::equals("$nor", elt.fieldName()) && !mongoutils::str::equals("$_internalSchemaXor", elt.fieldName()) && !mongoutils::str::equals("$or", elt.fieldName()) && - !mongoutils::str::equals("$where", elt.fieldName()); + !mongoutils::str::equals("$where", elt.fieldName()) && + !mongoutils::str::equals("$_internalSchemaMinProperties", elt.fieldName()) && + !mongoutils::str::equals("$_internalSchemaMaxProperties", elt.fieldName()); } if (isElemMatchValue) { @@ -1089,7 +1097,6 @@ StatusWith<long long> MatchExpressionParser::parseIntegerElementToLong(BSONEleme template <class T> StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaSingleIntegerArgument( const char* name, const BSONElement& elem) const { - auto parsedInt = parseIntegerElementToNonNegativeLong(elem); if (!parsedInt.isOK()) { return parsedInt.getStatus(); @@ -1103,4 +1110,19 @@ StatusWithMatchExpression MatchExpressionParser::_parseInternalSchemaSingleInteg return {std::move(matchExpression)}; } + +template <class T> +StatusWithMatchExpression MatchExpressionParser::_parseTopLevelInternalSchemaSingleIntegerArgument( + const BSONElement& elem) const { + auto parsedInt = parseIntegerElementToNonNegativeLong(elem); + if (!parsedInt.isOK()) { + return parsedInt.getStatus(); + } + auto matchExpression = stdx::make_unique<T>(); + auto status = matchExpression->init(parsedInt.getValue()); + if (!status.isOK()) { + return status; + } + return {std::move(matchExpression)}; +} } // namespace mongo diff --git a/src/mongo/db/matcher/expression_parser.cpp.rej b/src/mongo/db/matcher/expression_parser.cpp.rej new file mode 100644 index 00000000000..fcad3c02288 --- /dev/null +++ b/src/mongo/db/matcher/expression_parser.cpp.rej @@ -0,0 +1,51 @@ +*************** +*** 37,43 **** + #include "mongo/db/matcher/expression_leaf.h" + #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_xor.h" + #include "mongo/db/namespace_string.h" + #include "mongo/stdx/memory.h" +--- 37,45 ---- + #include "mongo/db/matcher/expression_leaf.h" + #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_max_properties.h" + #include "mongo/db/matcher/schema/expression_internal_schema_min_items.h" ++ #include "mongo/db/matcher/schema/expression_internal_schema_min_properties.h" + #include "mongo/db/matcher/schema/expression_internal_schema_xor.h" + #include "mongo/db/namespace_string.h" + #include "mongo/stdx/memory.h" +*************** +*** 423,434 **** + if (!s.isOK()) + return s; + root->add(xorExpr.release()); + } else { + return {Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "unknown top level operator: " + << e.fieldName())}; + } +- + continue; + } + +--- 424,440 ---- + if (!s.isOK()) + return s; + root->add(xorExpr.release()); ++ } else if (mongoutils::str::equals("_internalSchemaMinProperties", rest)) { ++ return _parseTopLevelInternalSchemaSingleIntegerArgument< ++ InternalSchemaMinPropertiesMatchExpression>(e); ++ } else if (mongoutils::str::equals("_internalSchemaMaxProperties", rest)) { ++ return _parseTopLevelInternalSchemaSingleIntegerArgument< ++ InternalSchemaMaxPropertiesMatchExpression>(e); + } else { + return {Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "unknown top level operator: " + << e.fieldName())}; + } + continue; + } + diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h index b6a7f9c21c6..c3ec2a14c0c 100644 --- a/src/mongo/db/matcher/expression_parser.h +++ b/src/mongo/db/matcher/expression_parser.h @@ -210,6 +210,18 @@ private: StatusWithMatchExpression _parseInternalSchemaSingleIntegerArgument( const char* name, const BSONElement& elem) const; + /** + * Same as the _parseInternalSchemaSingleIntegerArgument function, but for top-level + * operators which don't have paths. + */ + template <class T> + StatusWithMatchExpression _parseTopLevelInternalSchemaSingleIntegerArgument( + const BSONElement& elem) const; + + + // The maximum allowed depth of a query tree. Just to guard against stack overflow. + static const int kMaximumTreeDepth; + // Performs parsing for the match extensions. We do not own this pointer - it has to live // as long as the parser is active. const ExtensionsCallback* _extensionsCallback; diff --git a/src/mongo/db/matcher/expression_serialization_test.cpp b/src/mongo/db/matcher/expression_serialization_test.cpp index fd1714d1435..25bce603a17 100644 --- a/src/mongo/db/matcher/expression_serialization_test.cpp +++ b/src/mongo/db/matcher/expression_serialization_test.cpp @@ -1014,6 +1014,27 @@ TEST(SerializeInternalSchema, ExpressionInternalSchemaMaxLengthSerializesCorrect ExtensionsCallbackDisallowExtensions(), kSimpleCollator); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{x: {$_internalSchemaMaxLength: 1}}")); +} + +TEST(SerializeInternalSchema, ExpressionInternalSchemaMinPropertiesSerializesCorrectly) { + const CollatorInterface* collator = nullptr; + Matcher original(fromjson("{$_internalSchemaMinProperties: 1}"), + ExtensionsCallbackDisallowExtensions(), + collator); + Matcher reserialized( + serialize(original.getMatchExpression()), ExtensionsCallbackDisallowExtensions(), collator); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$_internalSchemaMinProperties: 1}")); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); +} + +TEST(SerializeInternalSchema, ExpressionInternalSchemaMaxPropertiesSerializesCorrectly) { + const CollatorInterface* collator = nullptr; + Matcher original(fromjson("{$_internalSchemaMaxProperties: 1}"), + ExtensionsCallbackDisallowExtensions(), + collator); + Matcher reserialized( + serialize(original.getMatchExpression()), ExtensionsCallbackDisallowExtensions(), collator); + ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), fromjson("{$_internalSchemaMaxProperties: 1}")); ASSERT_BSONOBJ_EQ(*reserialized.getQuery(), serialize(reserialized.getMatchExpression())); } diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_max_properties.h b/src/mongo/db/matcher/schema/expression_internal_schema_max_properties.h new file mode 100644 index 00000000000..e4ef7d72c9b --- /dev/null +++ b/src/mongo/db/matcher/schema/expression_internal_schema_max_properties.h @@ -0,0 +1,67 @@ +/** + * 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 "mongo/db/matcher/schema/expression_internal_schema_num_properties.h" + +namespace mongo { + +/** + * MatchExpression for $_internalSchemaMaxProperties keyword. Takes an integer + * argument that indicates the maximum amount of properties in an object. + */ +class InternalSchemaMaxPropertiesMatchExpression final + : public InternalSchemaNumPropertiesMatchExpression { +public: + InternalSchemaMaxPropertiesMatchExpression() + : InternalSchemaNumPropertiesMatchExpression(MatchType::INTERNAL_SCHEMA_MAX_PROPERTIES, + "$_internalSchemaMaxProperties") {} + + bool matches(const MatchableDocument* doc, MatchDetails* details) const final { + BSONObj obj = doc->toBSON(); + return (obj.nFields() <= numProperties()); + } + + bool matchesSingleElement(const BSONElement& elem) const final { + if (elem.type() != BSONType::Object) { + return false; + } + return (elem.embeddedObject().nFields() <= numProperties()); + } + + virtual std::unique_ptr<MatchExpression> shallowClone() const final { + auto maxProperties = stdx::make_unique<InternalSchemaMaxPropertiesMatchExpression>(); + invariantOK(maxProperties->init(numProperties())); + if (getTag()) { + maxProperties->setTag(getTag()->clone()); + } + return std::move(maxProperties); + } +}; +} // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_max_properties_test.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_max_properties_test.cpp new file mode 100644 index 00000000000..c626bd81844 --- /dev/null +++ b/src/mongo/db/matcher/schema/expression_internal_schema_max_properties_test.cpp @@ -0,0 +1,102 @@ +/** + * 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/expression.h" +#include "mongo/db/matcher/schema/expression_internal_schema_max_properties.h" +#include "mongo/db/matcher/schema/expression_internal_schema_min_properties.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { + +namespace { + +TEST(InternalSchemaMaxPropertiesMatchExpression, RejectsObjectsWithTooManyElements) { + InternalSchemaMaxPropertiesMatchExpression maxProperties; + ASSERT_OK(maxProperties.init(0)); + + ASSERT_FALSE(maxProperties.matchesBSON(BSON("b" << 21))); + ASSERT_FALSE(maxProperties.matchesBSON(BSON("b" << 21 << "c" << 3))); +} + +TEST(InternalSchemaMaxPropertiesMatchExpression, AcceptsObjectWithLessThanOrEqualToMaxElements) { + InternalSchemaMaxPropertiesMatchExpression maxProperties; + ASSERT_OK(maxProperties.init(2)); + + ASSERT_TRUE(maxProperties.matchesBSON(BSONObj())); + ASSERT_TRUE(maxProperties.matchesBSON(BSON("b" << BSONNULL))); + ASSERT_TRUE(maxProperties.matchesBSON(BSON("b" << 21))); + ASSERT_TRUE(maxProperties.matchesBSON(BSON("b" << 21 << "c" << 3))); +} + +TEST(InternalSchemaMaxPropertiesMatchExpression, MaxPropertiesZeroAllowsEmptyObjects) { + InternalSchemaMaxPropertiesMatchExpression maxProperties; + ASSERT_OK(maxProperties.init(0)); + + ASSERT_TRUE(maxProperties.matchesBSON(BSONObj())); +} + +TEST(InternalSchemaMaxPropertiesMatchExpression, NestedObjectsAreNotUnwound) { + InternalSchemaMaxPropertiesMatchExpression maxProperties; + ASSERT_OK(maxProperties.init(1)); + + ASSERT_TRUE(maxProperties.matchesBSON(BSON("b" << BSON("c" << 2 << "d" << 3)))); +} + +TEST(InternalSchemaMaxPropertiesMatchExpression, EquivalentFunctionIsAccurate) { + InternalSchemaMaxPropertiesMatchExpression maxProperties1; + InternalSchemaMaxPropertiesMatchExpression maxProperties2; + InternalSchemaMaxPropertiesMatchExpression maxProperties3; + ASSERT_OK(maxProperties1.init(1)); + ASSERT_OK(maxProperties2.init(1)); + ASSERT_OK(maxProperties3.init(2)); + + ASSERT_TRUE(maxProperties1.equivalent(&maxProperties1)); + ASSERT_TRUE(maxProperties1.equivalent(&maxProperties2)); + ASSERT_FALSE(maxProperties1.equivalent(&maxProperties3)); +} + +TEST(InternalSchemaMaxPropertiesMatchExpression, NestedArraysAreNotUnwound) { + InternalSchemaMaxPropertiesMatchExpression maxProperties; + ASSERT_OK(maxProperties.init(2)); + + ASSERT_TRUE(maxProperties.matchesBSON(BSON("a" << (BSON("b" << 2 << "c" << 3 << "d" << 4))))); +} + +TEST(InternalSchemaMaxPropertiesMatchExpression, MinPropertiesNotEquivalentToMaxProperties) { + InternalSchemaMaxPropertiesMatchExpression maxProperties; + InternalSchemaMinPropertiesMatchExpression minProperties; + ASSERT_OK(maxProperties.init(5)); + ASSERT_OK(minProperties.init(5)); + + ASSERT_FALSE(maxProperties.equivalent(&minProperties)); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_min_properties.h b/src/mongo/db/matcher/schema/expression_internal_schema_min_properties.h new file mode 100644 index 00000000000..7267964eba1 --- /dev/null +++ b/src/mongo/db/matcher/schema/expression_internal_schema_min_properties.h @@ -0,0 +1,67 @@ +/** + * 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 "mongo/db/matcher/schema/expression_internal_schema_num_properties.h" + +namespace mongo { + +/** + * MatchExpression for $_internalSchemaMinProperties keyword. Takes an integer + * argument that indicates the minimum amount of properties in an object. + */ +class InternalSchemaMinPropertiesMatchExpression final + : public InternalSchemaNumPropertiesMatchExpression { +public: + InternalSchemaMinPropertiesMatchExpression() + : InternalSchemaNumPropertiesMatchExpression(MatchType::INTERNAL_SCHEMA_MIN_PROPERTIES, + "$_internalSchemaMinProperties") {} + + bool matches(const MatchableDocument* doc, MatchDetails* details) const { + BSONObj obj = doc->toBSON(); + return (obj.nFields() >= numProperties()); + } + + bool matchesSingleElement(const BSONElement& elem) const { + if (elem.type() != BSONType::Object) { + return false; + } + return (elem.Obj().nFields() >= numProperties()); + } + + virtual std::unique_ptr<MatchExpression> shallowClone() const { + auto minProperties = stdx::make_unique<InternalSchemaMinPropertiesMatchExpression>(); + invariantOK(minProperties->init(numProperties())); + if (getTag()) { + minProperties->setTag(getTag()->clone()); + } + return std::move(minProperties); + } +}; +} // namespace mongo diff --git a/src/mongo/db/matcher/schema/expression_internal_schema_min_properties_test.cpp b/src/mongo/db/matcher/schema/expression_internal_schema_min_properties_test.cpp new file mode 100644 index 00000000000..e364180a7f9 --- /dev/null +++ b/src/mongo/db/matcher/schema/expression_internal_schema_min_properties_test.cpp @@ -0,0 +1,92 @@ +/** + * 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/expression.h" +#include "mongo/db/matcher/schema/expression_internal_schema_max_properties.h" +#include "mongo/db/matcher/schema/expression_internal_schema_min_properties.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { + +namespace { + +TEST(InternalSchemaMinPropertiesMatchExpression, RejectsObjectsWithTooFewElements) { + InternalSchemaMinPropertiesMatchExpression minProperties; + ASSERT_OK(minProperties.init(2)); + + ASSERT_FALSE(minProperties.matchesBSON(BSONObj())); + ASSERT_FALSE(minProperties.matchesBSON(BSON("b" << 21))); +} + +TEST(InternalSchemaMinPropertiesMatchExpression, AcceptsObjectWithAtLeastMinElements) { + InternalSchemaMinPropertiesMatchExpression minProperties; + ASSERT_OK(minProperties.init(2)); + + ASSERT_TRUE(minProperties.matchesBSON(BSON("b" << 21 << "c" << BSONNULL))); + ASSERT_TRUE(minProperties.matchesBSON(BSON("b" << 21 << "c" << 3))); + ASSERT_TRUE(minProperties.matchesBSON(BSON("b" << 21 << "c" << 3 << "d" << 43))); +} + +TEST(InternalSchemaMinPropertiesMatchExpression, MinPropertiesZeroAllowsEmptyObjects) { + InternalSchemaMinPropertiesMatchExpression minProperties; + ASSERT_OK(minProperties.init(0)); + + ASSERT_TRUE(minProperties.matchesBSON(BSONObj())); +} + +TEST(InternalSchemaMinPropertiesMatchExpression, NestedObjectsAreNotUnwound) { + InternalSchemaMinPropertiesMatchExpression minProperties; + ASSERT_OK(minProperties.init(2)); + + ASSERT_FALSE(minProperties.matchesBSON(BSON("b" << BSON("c" << 2 << "d" << 3)))); +} + +TEST(InternalSchemaMinPropertiesMatchExpression, NestedArraysAreNotUnwound) { + InternalSchemaMinPropertiesMatchExpression minProperties; + ASSERT_OK(minProperties.init(2)); + + ASSERT_FALSE(minProperties.matchesBSON(BSON("a" << (BSON("b" << 2 << "c" << 3 << "d" << 4))))); +} + +TEST(InternalSchemaMinPropertiesMatchExpression, EquivalentFunctionIsAccurate) { + InternalSchemaMinPropertiesMatchExpression minProperties1; + InternalSchemaMinPropertiesMatchExpression minProperties2; + InternalSchemaMinPropertiesMatchExpression minProperties3; + ASSERT_OK(minProperties1.init(1)); + ASSERT_OK(minProperties2.init(1)); + ASSERT_OK(minProperties3.init(2)); + + ASSERT_TRUE(minProperties1.equivalent(&minProperties1)); + ASSERT_TRUE(minProperties1.equivalent(&minProperties2)); + ASSERT_FALSE(minProperties1.equivalent(&minProperties3)); +} + +} // namespace +} // namespace mongo 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 new file mode 100644 index 00000000000..0ff962779eb --- /dev/null +++ b/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp @@ -0,0 +1,54 @@ +/** + * 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_num_properties.h" + +namespace mongo { + +void InternalSchemaNumPropertiesMatchExpression::debugString(StringBuilder& debug, + int level) const { + _debugAddSpace(debug, level); + BSONObjBuilder builder; + serialize(&builder); + debug << builder.obj().toString() << "\n"; +} + +void InternalSchemaNumPropertiesMatchExpression::serialize(BSONObjBuilder* out) const { + out->append(_name, _numProperties); +} + +bool InternalSchemaNumPropertiesMatchExpression::equivalent(const MatchExpression* other) const { + if (matchType() != other->matchType()) + return false; + const InternalSchemaNumPropertiesMatchExpression* otherMaxProperties = + static_cast<const InternalSchemaNumPropertiesMatchExpression*>(other); + return _numProperties == otherMaxProperties->_numProperties; +} +} // namespace mongo 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 new file mode 100644 index 00000000000..6013eafb5be --- /dev/null +++ b/src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h @@ -0,0 +1,71 @@ +/** + * 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 "mongo/base/string_data.h" +#include "mongo/db/matcher/expression.h" + +namespace mongo { + +/** + * MatchExpression for internal JSON Schema keywords that validate the number of properties in an + * object. + */ +class InternalSchemaNumPropertiesMatchExpression : public MatchExpression { +public: + InternalSchemaNumPropertiesMatchExpression(MatchType type, std::string name) + : MatchExpression(type), _name(name) {} + + virtual ~InternalSchemaNumPropertiesMatchExpression() {} + + Status init(long long numProperties) { + _numProperties = numProperties; + return Status::OK(); + } + + void debugString(StringBuilder& debug, int level) const final; + + void serialize(BSONObjBuilder* out) const final; + + bool equivalent(const MatchExpression* other) const final; + + MatchCategory getCategory() const final { + return MatchCategory::kArrayMatching; + } + +protected: + long long numProperties() const { + return _numProperties; + } + +private: + long long _numProperties; + std::string _name; +}; +} // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp index 8cef470c453..956d1f6e460 100644 --- a/src/mongo/db/pipeline/document_source_match.cpp +++ b/src/mongo/db/pipeline/document_source_match.cpp @@ -279,6 +279,8 @@ Document redactSafePortionDollarOps(BSONObj expr) { case BSONObj::opINTERNAL_SCHEMA_OBJECT_MATCH: case BSONObj::opINTERNAL_SCHEMA_MIN_LENGTH: case BSONObj::opINTERNAL_SCHEMA_MAX_LENGTH: + case BSONObj::opINTERNAL_SCHEMA_MIN_PROPERTIES: + case BSONObj::opINTERNAL_SCHEMA_MAX_PROPERTIES: continue; } } diff --git a/src/mongo/db/pipeline/document_source_match.cpp.rej b/src/mongo/db/pipeline/document_source_match.cpp.rej new file mode 100644 index 00000000000..a9ee2d89128 --- /dev/null +++ b/src/mongo/db/pipeline/document_source_match.cpp.rej @@ -0,0 +1,17 @@ +*************** +*** 275,280 **** + case BSONObj::opGEO_INTERSECTS: + case BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS: + case BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS: + continue; + } + } +--- 275,282 ---- + case BSONObj::opGEO_INTERSECTS: + case BSONObj::opINTERNAL_SCHEMA_MIN_ITEMS: + case BSONObj::opINTERNAL_SCHEMA_MAX_ITEMS: ++ case BSONObj::opINTERNAL_SCHEMA_MIN_PROPERTIES: ++ case BSONObj::opINTERNAL_SCHEMA_MAX_PROPERTIES: + continue; + } + } diff --git a/src/mongo/db/query/plan_cache.cpp b/src/mongo/db/query/plan_cache.cpp index ca52b1475ee..bfcedf74307 100644 --- a/src/mongo/db/query/plan_cache.cpp +++ b/src/mongo/db/query/plan_cache.cpp @@ -190,6 +190,11 @@ const char* encodeMatchType(MatchExpression::MatchType mt) { break; case MatchExpression::INTERNAL_SCHEMA_MAX_LENGTH: return "internalSchemaMaxLength"; + case MatchExpression::INTERNAL_SCHEMA_MIN_PROPERTIES: + return "internalSchemaMinProperties"; + break; + case MatchExpression::INTERNAL_SCHEMA_MAX_PROPERTIES: + return "internalSchemaMaxProperties"; break; default: MONGO_UNREACHABLE; |