summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorAnne Lim <anne.lim@mongodb.com>2017-07-19 10:57:11 -0400
committerAnne Lim <anne.lim@mongodb.com>2017-07-19 18:08:09 -0400
commit634435949c4b855b9cc5bfbf5cf481d8158fd996 (patch)
treef473a7ac7ff49a92bb5447f97c0afad220eede77 /src/mongo
parente034bdc4be7de0326d34e982febf00505cf0d993 (diff)
downloadmongo-634435949c4b855b9cc5bfbf5cf481d8158fd996.tar.gz
SERVER-29583: Create $_internalSchemaMinProperties and $_internalSchemaMaxProperties MatchExpressions
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/bson/bsonelement.cpp2
-rw-r--r--src/mongo/bson/bsonelement.cpp.rej17
-rw-r--r--src/mongo/bson/bsonelement.cpp.rej.rej17
-rw-r--r--src/mongo/bson/bsonobj.h2
-rw-r--r--src/mongo/bson/bsonobj.h.rej17
-rw-r--r--src/mongo/db/matcher/SConscript3
-rw-r--r--src/mongo/db/matcher/SConscript.rej33
-rw-r--r--src/mongo/db/matcher/expression.h2
-rw-r--r--src/mongo/db/matcher/expression.h.rej17
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp30
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp.rej51
-rw-r--r--src/mongo/db/matcher/expression_parser.h12
-rw-r--r--src/mongo/db/matcher/expression_serialization_test.cpp21
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_max_properties.h67
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_max_properties_test.cpp102
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_min_properties.h67
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_min_properties_test.cpp92
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_num_properties.cpp54
-rw-r--r--src/mongo/db/matcher/schema/expression_internal_schema_num_properties.h71
-rw-r--r--src/mongo/db/pipeline/document_source_match.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source_match.cpp.rej17
-rw-r--r--src/mongo/db/query/plan_cache.cpp5
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;