summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNicholas Zolnierz <nicholas.zolnierz@mongodb.com>2019-10-07 14:14:46 +0000
committerevergreen <evergreen@mongodb.com>2019-10-07 14:14:46 +0000
commitf6b19b784598fb1143ecbe29bd7970ce645183f6 (patch)
tree4bc4b84ebeec2454e698b00b7d081633f4f5d0ba /src
parent27409fcff6f2ee860cda3341f37302e66334f716 (diff)
downloadmongo-f6b19b784598fb1143ecbe29bd7970ce645183f6.tar.gz
SERVER-43842 Break up json_schema_parser_test.cpp to fix -fvar-tracking-assignments note
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/matcher/SConscript6
-rw-r--r--src/mongo/db/matcher/schema/array_keywords_test.cpp456
-rw-r--r--src/mongo/db/matcher/schema/encrypt_keyword_test.cpp316
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser.cpp1
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser_test.cpp2167
-rw-r--r--src/mongo/db/matcher/schema/logical_keywords_test.cpp271
-rw-r--r--src/mongo/db/matcher/schema/object_keywords_test.cpp939
-rw-r--r--src/mongo/db/matcher/schema/scalar_keywords_test.cpp383
8 files changed, 2371 insertions, 2168 deletions
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript
index 0bdd51b8c07..8aad3efe9db 100644
--- a/src/mongo/db/matcher/SConscript
+++ b/src/mongo/db/matcher/SConscript
@@ -118,6 +118,8 @@ env.CppUnitTest(
'path_accepting_keyword_test.cpp',
'path_test.cpp',
'rewrite_expr_test.cpp',
+ 'schema/array_keywords_test.cpp',
+ 'schema/encrypt_keyword_test.cpp',
'schema/encrypt_schema_types_test.cpp',
'schema/expression_internal_schema_all_elem_match_from_index_test.cpp',
'schema/expression_internal_schema_allowed_properties_test.cpp',
@@ -137,7 +139,9 @@ env.CppUnitTest(
'schema/expression_internal_schema_xor_test.cpp',
'schema/expression_parser_schema_test.cpp',
'schema/json_pointer_test.cpp',
- 'schema/json_schema_parser_test.cpp',
+ 'schema/logical_keywords_test.cpp',
+ 'schema/object_keywords_test.cpp',
+ 'schema/scalar_keywords_test.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/db/query/collation/collator_interface_mock',
diff --git a/src/mongo/db/matcher/schema/array_keywords_test.cpp b/src/mongo/db/matcher/schema/array_keywords_test.cpp
new file mode 100644
index 00000000000..96edb138560
--- /dev/null
+++ b/src/mongo/db/matcher/schema/array_keywords_test.cpp
@@ -0,0 +1,456 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/json.h"
+#include "mongo/db/matcher/schema/json_schema_parser.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+#define ASSERT_SERIALIZES_TO(match, expected) \
+ do { \
+ BSONObjBuilder bob; \
+ match->serialize(&bob); \
+ ASSERT_BSONOBJ_EQ(bob.obj(), expected); \
+ } while (false)
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfMinItemsIsNotANumber) {
+ auto schema = BSON("minItems" << BSON_ARRAY(1));
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfMinItemsIsNotANonNegativeInteger) {
+ auto schema = BSON("minItems" << -1);
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::FailedToParse);
+
+ schema = BSON("minItems" << 3.14);
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaArrayKeywordTest, MinItemsTranslatesCorrectlyWithNoType) {
+ auto schema = BSON("minItems" << 1);
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
+
+ schema = fromjson("{properties: {a: {minItems: 1}}}");
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {a: {$not: {$_internalSchemaType: [4]}}},
+ {a: {$_internalSchemaMinItems: 1}}]
+ })"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, MinItemsTranslatesCorrectlyWithArrayType) {
+ auto schema = fromjson("{properties: {a: {minItems: 1, type: 'array'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {$and: [ {a: {$_internalSchemaMinItems: 1}}, {a: {$_internalSchemaType: [4]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, MinItemsTranslatesCorrectlyWithNonArrayType) {
+ auto schema = fromjson("{properties: {a: {minItems: 1, type: 'number'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}}, {a: {$_internalSchemaType: ["number"]}}]
+ })"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfMaxItemsIsNotANumber) {
+ auto schema = BSON("maxItems" << BSON_ARRAY(1));
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfMaxItemsIsNotANonNegativeInteger) {
+ auto schema = BSON("maxItems" << -1);
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::FailedToParse);
+
+ schema = BSON("maxItems" << 1.60217);
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaArrayKeywordTest, MaxItemsTranslatesCorrectlyWithNoType) {
+ auto schema = BSON("maxItems" << 1);
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
+
+ schema = fromjson("{properties: {a: {maxItems: 1}}}");
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {a: {$not: {$_internalSchemaType: [4]}}},
+ {a: {$_internalSchemaMaxItems: 1}}]
+ })"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, MaxItemsTranslatesCorrectlyWithArrayType) {
+ auto schema = fromjson("{properties: {a: {maxItems: 1, type: 'array'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {$and: [ {a: {$_internalSchemaMaxItems: 1}}, {a: {$_internalSchemaType: [4]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, MaxItemsTranslatesCorrectlyWithNonArrayType) {
+ auto schema = fromjson("{properties: {a: {maxItems: 1, type: 'string'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists : true}}}, {a: {$_internalSchemaType: [2]}}]
+ })"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfUniqueItemsIsNotABoolean) {
+ auto schema = BSON("uniqueItems" << 1);
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaArrayKeywordTest, UniqueItemsFalseGeneratesAlwaysTrueExpression) {
+ auto schema = fromjson("{properties: {a: {uniqueItems: false}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, UniqueItemsTranslatesCorrectlyWithNoType) {
+ auto schema = BSON("uniqueItems" << true);
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
+
+ schema = fromjson("{properties: {a: {uniqueItems: true}}}");
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {a: {$not: {$_internalSchemaType: [4]}}},
+ {a: {$_internalSchemaUniqueItems: true}}]
+ })"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, UniqueItemsTranslatesCorrectlyWithTypeArray) {
+ auto schema = fromjson("{properties: {a: {type: 'array', uniqueItems: true}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {$and: [ {a: {$_internalSchemaUniqueItems: true}}, {a: {$_internalSchemaType: [4]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfItemsIsNotAnArrayOrObject) {
+ auto schema = BSON("items" << 1);
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfItemsIsAnArrayWithANonObject) {
+ auto schema = fromjson("{items: [{type: 'string'}, 'blah']}");
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfItemsIsAnInvalidSchema) {
+ auto schema = BSON("items" << BSON("invalid" << 1));
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfItemsIsAnArrayThatContainsAnInvalidSchema) {
+ auto schema = fromjson("{items: [{type: 'string'}, {invalid: 1}]}");
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaArrayKeywordTest, ItemsParsesSuccessfullyAsArrayAtTopLevel) {
+ auto schema = fromjson("{items: [{type: 'string'}]}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, ItemsParsesSuccessfullyAsObjectAtTopLevel) {
+ auto schema = fromjson("{items: {type: 'string'}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, ItemsParsesSuccessfullyAsArrayInNestedSchema) {
+ auto schema = fromjson("{properties: {a: {items: [{maxLength: 4}, {minimum: 0}]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ auto expectedResult = fromjson(R"(
+ {
+ $or:
+ [{a: {$not: {$exists: true}}}, {a: {$not: {$_internalSchemaType: [4]}}}, {
+ $and: [
+ {
+ a: {
+ $_internalSchemaMatchArrayIndex: {
+ index: 0,
+ namePlaceholder: "i",
+ expression: {
+ $or: [
+ {i: {$not: {$_internalSchemaType: [2]}}},
+ {i: {$_internalSchemaMaxLength: 4}}
+ ]
+ }
+ }
+ }
+ },
+ {
+ a: {
+ $_internalSchemaMatchArrayIndex : {
+ index: 1,
+ namePlaceholder: "i",
+ expression: {
+ $or: [
+ {i : {$not: {$_internalSchemaType: ["number"]}}},
+ {i: {$gte: 0}}
+ ]
+ }
+ }
+ }
+ }
+ ]
+ }]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
+}
+
+TEST(JSONSchemaArrayKeywordTest, ItemsParsesSuccessfullyAsObjectInNestedSchema) {
+ auto schema = fromjson("{properties: {a: {items: {type: 'string'}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {a: {$not: {$_internalSchemaType: [4]}}},
+ {a: {$_internalSchemaAllElemMatchFromIndex : [ 0, {i: {$_internalSchemaType: [2]}}]}}]
+ })"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfAdditionalItemsIsNotAnObjectOrBoolean) {
+ auto schema = BSON("items" << BSONObj() << "additionalItems" << 1);
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::TypeMismatch);
+
+ schema = BSON("additionalItems" << 1);
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaArrayKeywordTest, FailsToParseIfAdditionalItemsIsAnInvalidSchema) {
+ auto schema = BSON("items" << BSONObj() << "additionalItems" << BSON("invalid" << 1));
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::FailedToParse);
+
+ schema = BSON("additionalItems" << BSON("invalid" << 1));
+ ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
+ ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaArrayKeywordTest, AdditionalItemsTranslatesSucessfullyAsBooleanAtTopLevel) {
+ auto schema = fromjson("{items: [], additionalItems: true}");
+ auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
+
+ schema = fromjson("{items: [], additionalItems: false}");
+ expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, AdditionalItemsTranslatesSucessfullyAsObjectAtTopLevel) {
+ auto schema = fromjson("{items: [], additionalItems: {multipleOf: 7}}");
+ auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, AdditionalItemsTranslatesSucessfullyAsBooleanInNestedSchema) {
+ auto schema = fromjson("{properties: {a: {items: [], additionalItems: true}}}");
+ auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ auto expectedResult = fromjson(R"(
+ {
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {a: {$not: {$_internalSchemaType: [4]}}},
+ {a: {$_internalSchemaAllElemMatchFromIndex: [0, {$alwaysTrue: 1}]}}]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedExpr, expectedResult);
+
+ schema = fromjson("{properties: {a: {items: [], additionalItems: false}}}");
+ expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ expectedResult = fromjson(R"(
+ {
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {a: {$not: {$_internalSchemaType: [4]}}},
+ {a: {$_internalSchemaAllElemMatchFromIndex: [0, {$alwaysFalse: 1}]}}]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedExpr, expectedResult);
+}
+
+TEST(JSONSchemaArrayKeywordTest,
+ AdditionalItemsGeneratesEmptyExpressionAtTopLevelIfItemsNotPresent) {
+ auto schema = BSON("additionalItems" << true);
+ auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
+
+ schema = BSON("additionalItems" << false);
+ expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
+
+ schema = BSON("additionalItems" << BSON("minLength" << 1));
+ expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
+}
+
+TEST(JSONSchemaArrayKeywordTest,
+ AdditionalItemsGeneratesEmptyExpressionInNestedSchemaIfItemsNotPresent) {
+ auto schema = fromjson("{properties: {foo: {additionalItems: true}}}");
+ auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
+
+
+ schema = fromjson("{properties: {foo: {additionalItems: false}}}");
+ expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
+}
+
+TEST(JSONSchemaArrayKeywordTest, AdditionalItemsGeneratesEmptyExpressionIfItemsAnObject) {
+ auto schema = fromjson("{properties: {a: {items: {minimum: 7}, additionalItems: false}}}");
+ auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ auto expectedResult = fromjson(R"(
+ {
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {a: {$not: {$_internalSchemaType: [4]}}},
+ {
+ a: {
+ $_internalSchemaAllElemMatchFromIndex: [
+ 0,
+ {$or: [ {i: {$not: {$_internalSchemaType: ["number"]}}}, {i: {$gte: 7}}]}
+ ]
+ }
+ }]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedExpr, expectedResult);
+
+ schema = fromjson("{properties: {a: {items: {minimum: 7}, additionalItems: {minLength: 7}}}}");
+ expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(expr.getStatus());
+ optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
+ expectedResult = fromjson(R"(
+ {
+ $or:
+ [{a: {$not: {$exists: true}}},
+ {a: {$not: {$_internalSchemaType: [4]}}},
+ {
+ a: {
+ $_internalSchemaAllElemMatchFromIndex : [
+ 0,
+ {$or: [ {i: {$not: {$_internalSchemaType: ["number"]}}}, {i: {$gte: 7}}]}
+ ]
+ }
+ }]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedExpr, expectedResult);
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/encrypt_keyword_test.cpp b/src/mongo/db/matcher/schema/encrypt_keyword_test.cpp
new file mode 100644
index 00000000000..ddc0e301d33
--- /dev/null
+++ b/src/mongo/db/matcher/schema/encrypt_keyword_test.cpp
@@ -0,0 +1,316 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/json.h"
+#include "mongo/db/matcher/expression_always_boolean.h"
+#include "mongo/db/matcher/schema/json_schema_parser.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+#define ASSERT_SERIALIZES_TO(match, expected) \
+ do { \
+ BSONObjBuilder bob; \
+ match->serialize(&bob); \
+ ASSERT_BSONOBJ_EQ(bob.obj(), expected); \
+ } while (false)
+
+TEST(JSONSchemaParserEncryptTest, EncryptTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {encrypt: {}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
+ {
+ $or:
+ [{foo: {$not: {$exists: true}}}, {
+ $and:
+ [ {foo: {$_internalSchemaBinDataSubType: 6}}, {foo: {$_internalSchemaType: [5]}} ]
+ }]
+ })"));
+}
+
+TEST(JSONSchemaParserEncryptTest, EncryptWithSingleBsonTypeTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {encrypt: {bsonType: \"string\"}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
+ {
+ $or:
+ [{foo: {$not: {$exists: true}}}, {
+ $and:
+ [ {foo: {$_internalSchemaBinDataSubType: 6}},
+ {foo: {$_internalSchemaBinDataEncryptedType: [2]}},
+ {foo: {$_internalSchemaType: [5]}}]
+ }]
+ })"));
+}
+
+TEST(JSONSchemaParserEncryptTest, EncryptWithArrayOfMultipleTypesTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {encrypt: {bsonType: [\"string\",\"date\"]}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
+ {
+ $or:
+ [{foo: {$not: {$exists: true}}}, {
+ $and:
+ [ {foo: {$_internalSchemaBinDataSubType: 6}},
+ {foo: {$_internalSchemaBinDataEncryptedType: [2, 9]}},
+ {foo: {$_internalSchemaType: [5]}}]
+ }]
+ })"));
+}
+
+TEST(JSONSchemaParserEncryptTest, TopLevelEncryptTranslatesCorrectly) {
+ BSONObj schema = fromjson("{encrypt: {}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, BSON(AlwaysFalseMatchExpression::kName << 1));
+}
+
+TEST(JSONSchemaParserEncryptTest, NestedEncryptTranslatesCorrectly) {
+ BSONObj schema =
+ fromjson("{properties: {a: {type: 'object', properties: {b: {encrypt: {}}}}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
+ {
+ $or:
+ [{a: {$not: {$exists: true}}}, {
+ $and: [
+ {
+ a: {
+ $_internalSchemaObjectMatch: {
+ $or: [
+ {b: {$not : {$exists: true}}},
+ {
+ $and: [
+ {b: {$_internalSchemaBinDataSubType: 6}},
+ {b: {$_internalSchemaType: [5]}}
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {a: {$_internalSchemaType: [3]}}
+ ]
+ }]
+ })"));
+}
+
+TEST(JSONSchemaParserEncryptTest, NestedEncryptInArrayTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {a: {type: 'array', items: {encrypt: {}}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
+ {
+ $or:
+ [{a: {$not: {$exists: true}}}, {
+ $and: [
+ {
+ a: {
+ $_internalSchemaAllElemMatchFromIndex : [
+ 0,
+ {
+ $and: [
+ {i: {$_internalSchemaBinDataSubType: 6}},
+ {i: {$_internalSchemaType: [5]}}
+ ]
+ }
+ ]
+ }
+ },
+ {a: {$_internalSchemaType: [4]}}
+ ]
+ }]
+ })"));
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseIfBothEncryptAndTypeArePresent) {
+ BSONObj schema = fromjson("{encrypt: {}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseIfBothEncryptAndBSONTypeArePresent) {
+ BSONObj schema = fromjson("{encrypt: {}, bsonType: 'binData'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseIfEncryptValueIsNotObject) {
+ BSONObj schema = fromjson("{properties: {foo: {encrypt: 12}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserEncryptTest, ParseSucceedsWithEmptyEncryptObject) {
+ BSONObj schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSONObj())));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaParserEncryptTest, ParseSucceedsWithBsonType) {
+ BSONObj schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("bsonType"
+ << "int"))));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaParserEncryptTest, ParseFailsWithBsonTypeGivenByCode) {
+ BSONObj schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("bsonType" << 5))));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserEncryptTest, ParseSucceedsWithArrayOfBsonTypes) {
+ BSONObj schema =
+ BSON("properties" << BSON(
+ "foo" << BSON("encrypt" << BSON("bsonType" << BSON_ARRAY("int"
+ << "date"
+ << "string")))));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaParserEncryptTest, ParseSucceedsIfEncryptFieldsAreValid) {
+ auto schema = BSON(
+ "properties" << BSON(
+ "foo" << BSON("encrypt" << BSON("bsonType"
+ << "string"
+ << "keyId"
+ << "/pointer"
+ << "algorithm"
+ << "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"))));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseIfEncryptHasBadFieldName) {
+ BSONObj schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("keyIdx"
+ << "/pointer"))));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus().code(), 40415);
+ schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("bsonType"
+ << "bool"
+ << "keyIdx"
+ << "/pointer"))));
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus().code(), 40415);
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseWithBadKeyIdArray) {
+ auto schema = BSON(
+ "properties" << BSON("foo" << BSON("encrypt" << BSON("keyId" << BSON_ARRAY("nonsense"
+ << "again")))));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus().code(), 51088);
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseWithBadBSONType) {
+ auto schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("bsonType"
+ << "Stringx"))));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::BadValue);
+
+ schema = BSON("properties" << BSON(
+ "foo" << BSON("encrypt" << BSON("bsonType" << (BSONType::JSTypeMax + 1)))));
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseWithBadAlgorithm) {
+ auto schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("algorithm"
+ << "Stringx"))));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::BadValue);
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseWithBadPointer) {
+ auto schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("keyId"
+ << "invalidPointer"))));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus().code(), 51065);
+}
+
+TEST(JSONSchemaParserEncryptTest, TopLevelEncryptMetadataValidatedCorrectly) {
+ BSONObj schema = fromjson(
+ "{encryptMetadata: {algorithm: \"AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic\","
+ " keyId: [{$binary: \"ASNFZ4mrze/ty6mHZUMhAQ==\", $type: \"04\"}]}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaParserEncryptTest, NestedEncryptMetadataValidatedCorrectly) {
+ BSONObj schema = fromjson(
+ "{properties: {a: {encryptMetadata: {algorithm: \"AEAD_AES_256_CBC_HMAC_SHA_512-Random\", "
+ "keyId: [{$binary: \"ASNFZ4mrze/ty6mHZUMhAQ==\", $type: \"04\"}]}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseIfEncryptMetadataValueIsEmptyObject) {
+ BSONObj schema = fromjson("{encryptMetadata: {}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseIfBothEncryptAndEncryptMetadataAreSiblings) {
+ BSONObj schema = fromjson(
+ "{encrypt: {}, encryptMetadata: {algorithm: "
+ "\"AEAD_AES_256_CBC_HMAC_SHA_512-Random\"}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserEncryptTest, FailsToParseWithNonUUIDArrayElement) {
+ BSONArrayBuilder builder;
+ UUID::gen().appendToArrayBuilder(&builder);
+ UUID::gen().appendToArrayBuilder(&builder);
+ builder.appendBinData(16, BinDataType::Encrypt, "16charactershere");
+ auto schema =
+ BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("keyId" << builder.arr()))));
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus().code(), 51084);
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/json_schema_parser.cpp b/src/mongo/db/matcher/schema/json_schema_parser.cpp
index 0608c2389b4..3d345aebf12 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser.cpp
@@ -1207,6 +1207,7 @@ Status translateObjectKeywords(StringMap<BSONElement>& keywordMap,
* - minLength
* - maxLength
* - pattern
+ * - multipleOf
*/
Status translateScalarKeywords(StringMap<BSONElement>& keywordMap,
StringData path,
diff --git a/src/mongo/db/matcher/schema/json_schema_parser_test.cpp b/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
deleted file mode 100644
index 6a47dbe567a..00000000000
--- a/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
+++ /dev/null
@@ -1,2167 +0,0 @@
-/**
- * Copyright (C) 2018-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * As a special exception, the copyright holders give permission to link the
- * code of portions of this program with the OpenSSL library under certain
- * conditions as described in each individual source file and distribute
- * linked combinations including the program with the OpenSSL library. You
- * must comply with the Server Side Public License in all respects for
- * all of the code used other than as permitted herein. If you modify file(s)
- * with this exception, you may extend this exception to your version of the
- * file(s), but you are not obligated to do so. If you do not wish to do so,
- * delete this exception statement from your version. If you delete this
- * exception statement from all source files in the program, then also delete
- * it in the license file.
- */
-
-#include "mongo/platform/basic.h"
-
-#include "mongo/bson/bsonobjbuilder.h"
-#include "mongo/bson/json.h"
-#include "mongo/db/bson/bson_helper.h"
-#include "mongo/db/matcher/expression_always_boolean.h"
-#include "mongo/db/matcher/schema/json_schema_parser.h"
-#include "mongo/db/pipeline/expression_context_for_test.h"
-#include "mongo/db/query/query_knobs_gen.h"
-#include "mongo/unittest/death_test.h"
-#include "mongo/unittest/unittest.h"
-
-namespace mongo {
-namespace {
-
-#define ASSERT_SERIALIZES_TO(match, expected) \
- do { \
- BSONObjBuilder bob; \
- match->serialize(&bob); \
- ASSERT_BSONOBJ_EQ(bob.obj(), expected); \
- } while (false)
-
-TEST(JSONSchemaParserTest, FailsToParseIfTypeIsNotAString) {
- BSONObj schema = fromjson("{type: 1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseNicelyIfTypeIsKnownUnsupportedAlias) {
- BSONObj schema = fromjson("{type: 'integer'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema type 'integer' is not currently supported");
-}
-
-TEST(JSONSchemaParserTest, FailsToParseUnknownKeyword) {
- BSONObj schema = fromjson("{unknown: 1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfPropertiesIsNotAnObject) {
- BSONObj schema = fromjson("{properties: 1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfPropertiesIsNotAnObjectWithType) {
- BSONObj schema = fromjson("{type: 'string', properties: 1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfParticularPropertyIsNotAnObject) {
- BSONObj schema = fromjson("{properties: {foo: 1}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfKeywordIsDuplicated) {
- BSONObj schema = BSON("type"
- << "object"
- << "type"
- << "object");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, EmptySchemaTranslatesCorrectly) {
- BSONObj schema = fromjson("{}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, TypeObjectTranslatesCorrectly) {
- BSONObj schema = fromjson("{type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, NestedTypeObjectTranslatesCorrectly) {
- BSONObj schema =
- fromjson("{properties: {a: {type: 'object', properties: {b: {type: 'string'}}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(
- optimizedResult,
- fromjson("{$or: [{a: {$not: {$exists: true }}}, {$and: [{a: {$_internalSchemaObjectMatch: "
- "{$or: [{b: {$not: {$exists: true}}}, {b: {$_internalSchemaType: [2]}}]}}}, {a: "
- "{$_internalSchemaType: [3]}}]}]}"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelNonObjectTypeTranslatesCorrectly) {
- BSONObj schema = fromjson("{type: 'string'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, BSON(AlwaysFalseMatchExpression::kName << 1));
-}
-
-TEST(JSONSchemaParserTest, TypeNumberTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {num: {type: 'number'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult,
- fromjson("{$or: [{num: {$not: {$exists: true }}}, {num: "
- "{ $_internalSchemaType: [ 'number' ]}}]}"));
-}
-
-TEST(JSONSchemaParserTest, MaximumTranslatesCorrectlyWithTypeNumber) {
- BSONObj schema = fromjson("{properties: {num: {type: 'number', maximum: 0}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult,
- fromjson("{$or: [{num: {$not: {$exists: true}}}, {$and: [{num: {$lte: "
- "0}}, {num: {$_internalSchemaType: ['number']}}]}]}"));
-}
-
-TEST(JSONSchemaParserTest, MaximumTranslatesCorrectlyWithBsonTypeLong) {
- BSONObj schema =
- fromjson("{properties: {num: {bsonType: 'long', maximum: 0}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult,
- fromjson("{$or: [{num: {$not: {$exists: true}}}, {$and: [{num: {$lte: "
- "0}}, {num: {$_internalSchemaType: [18]}}]}]}"));
-}
-
-TEST(JSONSchemaParserTest, MaximumTranslatesCorrectlyWithTypeString) {
- BSONObj schema = fromjson("{properties: {num: {type: 'string', maximum: 0}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(
- optimizedResult,
- fromjson("{$or: [{num: {$not: {$exists: true }}}, {num: {$_internalSchemaType: [2]}}]}"));
-}
-
-TEST(JSONSchemaParserTest, MaximumTranslatesCorrectlyWithNoType) {
- BSONObj schema = fromjson("{properties: {num: {maximum: 0}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{num: {$not: {$exists : true}}},
- {num: {$not: {$_internalSchemaType: ["number"]}}},
- {num: {$lte: 0}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMaximumIsNotANumber) {
- BSONObj schema = fromjson("{maximum: 'foo'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMaxLengthIsNotANumber) {
- BSONObj schema = fromjson("{maxLength: 'foo'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMaxLengthIsLessThanZero) {
- BSONObj schema = fromjson("{maxLength: -1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, MinimumTranslatesCorrectlyWithTypeNumber) {
- BSONObj schema = fromjson("{properties: {num: {type: 'number', minimum: 0}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{num: {$not: {$exists : true}}},
- {$and: [ {num: {$gte: 0}}, {num: {$_internalSchemaType: ["number"]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMaxLengthIsNonIntegralDouble) {
- BSONObj schema =
- fromjson("{properties: {foo: {type: 'string', maxLength: 5.5}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, MaxLengthTranslatesCorrectlyWithIntegralDouble) {
- BSONObj schema =
- fromjson("{properties: {foo: {type: 'string', maxLength: 5.0}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}},
- {$and: [ {foo: {$_internalSchemaMaxLength: 5}}, {foo: {$_internalSchemaType: [2]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MaxLengthTranslatesCorrectlyWithTypeString) {
- BSONObj schema =
- fromjson("{properties: {foo: {type: 'string', maxLength: 5}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists : true}}},
- {$and: [ {foo: {$_internalSchemaMaxLength: 5}}, {foo: {$_internalSchemaType: [2]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MinimumTranslatesCorrectlyWithBsonTypeLong) {
- BSONObj schema =
- fromjson("{properties: {num: {bsonType: 'long', minimum: 0}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{num: {$not: {$exists: true}}},
- {$and: [ {num: {$gte: 0}}, {num: {$_internalSchemaType: [18]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MinimumTranslatesCorrectlyWithTypeString) {
- BSONObj schema = fromjson("{properties: {num: {type: 'string', minimum: 0}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{num: {$not: {$exists: true}}}, {num: {$_internalSchemaType: [2]}}]
- })"));
-}
-
-
-TEST(JSONSchemaParserTest, MinimumTranslatesCorrectlyWithNoType) {
- BSONObj schema = fromjson("{properties: {num: {minimum: 0}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{num: {$not: {$exists: true}}},
- {num: {$not: {$_internalSchemaType: ["number"]}}},
- {num: {$gte: 0}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MaximumTranslatesCorrectlyWithExclusiveMaximumTrue) {
- BSONObj schema = fromjson(
- "{properties: {num: {bsonType: 'long', maximum: 0, exclusiveMaximum: true}},"
- "type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{num: {$not: {$exists: true}}},
- {$and: [ {num: {$lt: 0}}, {num: {$_internalSchemaType: [18]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MaximumTranslatesCorrectlyWithExclusiveMaximumFalse) {
- BSONObj schema = fromjson(
- "{properties: {num: {bsonType: 'long', maximum: 0, exclusiveMaximum: false}},"
- "type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{num: {$not: {$exists: true}}},
- {$and: [ {num: {$lte: 0}}, {num: {$_internalSchemaType: [18]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfExclusiveMaximumIsPresentButMaximumIsNot) {
- BSONObj schema = fromjson("{exclusiveMaximum: true}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfExclusiveMaximumIsNotABoolean) {
- BSONObj schema = fromjson("{maximum: 5, exclusiveMaximum: 'foo'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, MinimumTranslatesCorrectlyWithExclusiveMinimumTrue) {
- BSONObj schema = fromjson(
- "{properties: {num: {bsonType: 'long', minimum: 0, exclusiveMinimum: true}},"
- "type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{num: {$not: {$exists: true}}},
- {$and: [ {num: {$gt: 0}}, {num: {$_internalSchemaType: [18]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MinimumTranslatesCorrectlyWithExclusiveMinimumFalse) {
- BSONObj schema = fromjson(
- "{properties: {num: {bsonType: 'long', minimum: 0, exclusiveMinimum: false}},"
- "type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{num: {$not: {$exists: true}}},
- {$and: [ {num: {$gte: 0}}, {num: {$_internalSchemaType: [18]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfExclusiveMinimumIsPresentButMinimumIsNot) {
- BSONObj schema = fromjson("{exclusiveMinimum: true}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfExclusiveMinimumIsNotABoolean) {
- BSONObj schema = fromjson("{minimum: 5, exclusiveMinimum: 'foo'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMinLengthIsNotANumber) {
- BSONObj schema = fromjson("{minLength: 'foo'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMinLengthIsLessThanZero) {
- BSONObj schema = fromjson("{minLength: -1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMinLengthIsNonIntegralDouble) {
- BSONObj schema =
- fromjson("{properties: {foo: {type: 'string', minLength: 5.5}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, MinLengthTranslatesCorrectlyWithTypeString) {
- BSONObj schema =
- fromjson("{properties: {foo: {type: 'string', minLength: 5}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}},
- {$and: [ {foo: {$_internalSchemaMinLength: 5}}, {foo: {$_internalSchemaType: [2]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MinLengthTranslatesCorrectlyWithIntegralDouble) {
- BSONObj schema =
- fromjson("{properties: {foo: {type: 'string', minLength: 5.0}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}},
- {$and: [ {foo: {$_internalSchemaMinLength: 5}}, {foo: {$_internalSchemaType: [2]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMinimumIsNotANumber) {
- BSONObj schema = fromjson("{minimum: 'foo'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfPatternIsNotString) {
- BSONObj schema = fromjson("{pattern: 6}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, PatternTranslatesCorrectlyWithString) {
- BSONObj schema =
- fromjson("{properties: {foo: {type: 'string', pattern: 'abc'}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- auto expected =
- BSON("$or" << BSON_ARRAY(
- BSON("foo" << BSON("$not" << BSON("$exists" << true)))
- << BSON("$and" << BSON_ARRAY(
- BSON("foo" << BSON("$regex"
- << "abc"))
- << BSON("foo" << BSON("$_internalSchemaType" << BSON_ARRAY(2)))))));
- ASSERT_SERIALIZES_TO(optimizedResult, expected);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMultipleOfIsNotANumber) {
- BSONObj schema = fromjson("{multipleOf: 'foo'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMultipleOfIsLessThanZero) {
- BSONObj schema = fromjson("{multipleOf: -1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMultipleOfIsZero) {
- BSONObj schema = fromjson("{multipleOf: 0}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, MultipleOfTranslatesCorrectlyWithTypeNumber) {
- BSONObj schema = fromjson(
- "{properties: {foo: {type: 'number', multipleOf: NumberDecimal('5.3')}}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}}, {
- $and: [
- {foo: {$_internalSchemaFmod: [ NumberDecimal('5.3'), 0]}},
- {foo: {$_internalSchemaType: ["number"]}}
- ]
- }]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfAllOfIsNotAnArray) {
- BSONObj schema = fromjson("{properties: {foo: {allOf: 'foo'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseAllOfIfArrayContainsInvalidSchema) {
- BSONObj schema = fromjson("{properties: {foo: {allOf: [{type: {}}]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseAllOfIfArrayIsEmpty) {
- BSONObj schema = fromjson("{properties: {foo: {allOf: []}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::BadValue);
-}
-
-TEST(JSONSchemaParserTest, AllOfTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {foo: {allOf: [{minimum: 0}, {maximum: 10}]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- auto expectedResult = fromjson(
- R"({
- $or:
- [{foo: {$not: {$exists: true}}},
- {
- $and : [
- {$or: [ {foo: {$not: {$_internalSchemaType: ["number"]}}}, {foo: {$gte: 0}}]},
- {$or: [ {foo: {$not: {$_internalSchemaType: ["number"]}}}, {foo: {$lte: 10}}]}
- ]
- }]
- })");
- ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
-}
-
-TEST(JSONSchemaParserTest, TopLevelAllOfTranslatesCorrectly) {
- BSONObj schema = fromjson("{allOf: [{properties: {foo: {type: 'string'}}}]}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}}, {foo: {$_internalSchemaType: [2]}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfAnyOfIsNotAnArray) {
- BSONObj schema = fromjson("{properties: {foo: {anyOf: 'foo'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseAnyOfIfArrayContainsInvalidSchema) {
- BSONObj schema = fromjson("{properties: {foo: {anyOf: [{type: {}}]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseAnyOfIfArrayIsEmpty) {
- BSONObj schema = fromjson("{properties: {foo: {anyOf: []}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::BadValue);
-}
-
-TEST(JSONSchemaParserTest, AnyOfTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {foo: {anyOf: [{type: 'number'}, {type: 'string'}]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}},
- {foo: {$_internalSchemaType: ["number"]}},
- {foo: {$_internalSchemaType: [2]}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelAnyOfTranslatesCorrectly) {
- BSONObj schema = fromjson("{anyOf: [{properties: {foo: {type: 'string'}}}]}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}}, {foo: {$_internalSchemaType: [2]}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfOneOfIsNotAnArray) {
- BSONObj schema = fromjson("{properties: {foo: {oneOf: 'foo'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseOneOfIfArrayContainsInvalidSchema) {
- BSONObj schema = fromjson("{properties: {foo: {oneOf: [{type: {}}]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseOneOfIfArrayIsEmpty) {
- BSONObj schema = fromjson("{properties: {foo: {oneOf: []}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::BadValue);
-}
-
-TEST(JSONSchemaParserTest, OneOfTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {foo: {oneOf: [{minimum: 0}, {maximum: 10}]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}},
- {
- $_internalSchemaXor : [
- {$or: [ {foo: {$not: {$_internalSchemaType: ["number"]}}}, {foo: {$gte : 0}}]},
- {$or: [ {foo: {$not: {$_internalSchemaType: ["number"]}}}, {foo: {$lte : 10}}]}
- ]
- }]
- })"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelOneOfTranslatesCorrectly) {
- BSONObj schema = fromjson("{oneOf: [{properties: {foo: {type: 'string'}}}]}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}}, {foo: {$_internalSchemaType: [2]}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfNotIsNotAnObject) {
- BSONObj schema = fromjson("{properties: {foo: {not: 'foo'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseNotIfObjectContainsInvalidSchema) {
- BSONObj schema = fromjson("{properties: {foo: {not: {type: {}}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, NotTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {foo: {not: {type: 'number'}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}}, {foo: {$not: {$_internalSchemaType: ['number']}}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelNotTranslatesCorrectly) {
- BSONObj schema = fromjson("{not: {properties: {foo: {type: 'string'}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $nor:
- [{$or: [ {foo: {$not: {$exists: true}}}, {foo: {$_internalSchemaType: [2]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMinItemsIsNotANumber) {
- auto schema = BSON("minItems" << BSON_ARRAY(1));
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMinItemsIsNotANonNegativeInteger) {
- auto schema = BSON("minItems" << -1);
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::FailedToParse);
-
- schema = BSON("minItems" << 3.14);
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, MinItemsTranslatesCorrectlyWithNoType) {
- auto schema = BSON("minItems" << 1);
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
-
- schema = fromjson("{properties: {a: {minItems: 1}}}");
- result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}},
- {a: {$not: {$_internalSchemaType: [4]}}},
- {a: {$_internalSchemaMinItems: 1}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MinItemsTranslatesCorrectlyWithArrayType) {
- auto schema = fromjson("{properties: {a: {minItems: 1, type: 'array'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}},
- {$and: [ {a: {$_internalSchemaMinItems: 1}}, {a: {$_internalSchemaType: [4]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MinItemsTranslatesCorrectlyWithNonArrayType) {
- auto schema = fromjson("{properties: {a: {minItems: 1, type: 'number'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}}, {a: {$_internalSchemaType: ["number"]}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMaxItemsIsNotANumber) {
- auto schema = BSON("maxItems" << BSON_ARRAY(1));
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfMaxItemsIsNotANonNegativeInteger) {
- auto schema = BSON("maxItems" << -1);
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::FailedToParse);
-
- schema = BSON("maxItems" << 1.60217);
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, MaxItemsTranslatesCorrectlyWithNoType) {
- auto schema = BSON("maxItems" << 1);
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
-
- schema = fromjson("{properties: {a: {maxItems: 1}}}");
- result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}},
- {a: {$not: {$_internalSchemaType: [4]}}},
- {a: {$_internalSchemaMaxItems: 1}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MaxItemsTranslatesCorrectlyWithArrayType) {
- auto schema = fromjson("{properties: {a: {maxItems: 1, type: 'array'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}},
- {$and: [ {a: {$_internalSchemaMaxItems: 1}}, {a: {$_internalSchemaType: [4]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, MaxItemsTranslatesCorrectlyWithNonArrayType) {
- auto schema = fromjson("{properties: {a: {maxItems: 1, type: 'string'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists : true}}}, {a: {$_internalSchemaType: [2]}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, RequiredFailsToParseIfNotAnArray) {
- BSONObj schema = fromjson("{required: 'field'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, RequiredFailsToParseArrayIsEmpty) {
- BSONObj schema = fromjson("{required: []}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, RequiredFailsToParseIfArrayContainsNonString) {
- BSONObj schema = fromjson("{required: ['foo', 1]}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, RequiredFailsToParseIfArrayContainsDuplicates) {
- BSONObj schema = fromjson("{required: ['foo', 'bar', 'foo']}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, TopLevelRequiredTranslatesCorrectly) {
- BSONObj schema = fromjson("{required: ['foo', 'bar']}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult,
- fromjson("{$and: [{bar: {$exists: true}}, {foo: {$exists: true}}]}"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelRequiredTranslatesCorrectlyWithProperties) {
- BSONObj schema = fromjson("{required: ['foo'], properties: {foo: {type: 'number'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $and: [
- {foo: {$_internalSchemaType: ['number']}},
- {foo: {$exists: true}}
- ]
- })"));
-}
-
-TEST(JSONSchemaParserTest, RequiredTranslatesCorrectlyWithMultipleElements) {
- BSONObj schema = fromjson("{properties: {x: {required: ['y', 'z']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{x: {$not: {$exists: true}}}, {x: {$not: {$_internalSchemaType: [3]}}}, {
- x: {
- $_internalSchemaObjectMatch:
- {$and: [ {y: {$exists: true}}, {z: {$exists: true}}]}
- }
- }]
- })"));
-}
-
-TEST(JSONSchemaParserTest, RequiredTranslatesCorrectlyInsideProperties) {
- BSONObj schema = fromjson("{properties: {x: {required: ['y']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{x: {$not: {$exists: true}}},
- {x: {$not: {$_internalSchemaType: [3]}}},
- {x: {$_internalSchemaObjectMatch: {y: {$exists: true}}}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, RequiredTranslatesCorrectlyInsidePropertiesWithSiblingProperties) {
- BSONObj schema =
- fromjson("{properties: {x:{required: ['y'], properties: {y: {type: 'number'}}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- auto expectedResult = fromjson(
- R"({
- $or:
- [{x: {$not: {$exists: true}}},
- {
- $and: [
- {
- $or: [
- {x: {$not: {$_internalSchemaType: [3]}}},
- {x: {$_internalSchemaObjectMatch: {y : {$_internalSchemaType: ["number"]}}}}
- ]
- },
- {
- $or: [
- {x: {$not: {$_internalSchemaType: [3]}}},
- {x: {$_internalSchemaObjectMatch: {y: {$exists: true}}}}
- ]
- }
- ]
- }]
- })");
- ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
-}
-
-TEST(JSONSchemaParserTest, SharedJsonAndBsonTypeAliasesTranslateIdentically) {
- for (auto&& mapEntry : MatcherTypeSet::kJsonSchemaTypeAliasMap) {
- auto typeAlias = mapEntry.first;
- // JSON Schema spells its bool type as "boolean", whereas MongoDB calls it "bool".
- auto bsonTypeAlias =
- (typeAlias == JSONSchemaParser::kSchemaTypeBoolean) ? "bool" : typeAlias;
-
- BSONObj typeSchema = BSON("properties" << BSON("f" << BSON("type" << typeAlias)));
- BSONObj bsonTypeSchema =
- BSON("properties" << BSON("f" << BSON("bsonType" << bsonTypeAlias)));
- auto typeResult = JSONSchemaParser::parse(new ExpressionContextForTest(), typeSchema);
- ASSERT_OK(typeResult.getStatus());
- auto bsonTypeResult =
- JSONSchemaParser::parse(new ExpressionContextForTest(), bsonTypeSchema);
- ASSERT_OK(bsonTypeResult.getStatus());
-
- BSONObjBuilder typeBuilder;
- MatchExpression::optimize(std::move(typeResult.getValue()))->serialize(&typeBuilder);
-
- BSONObjBuilder bsonTypeBuilder;
- MatchExpression::optimize(std::move(bsonTypeResult.getValue()))
- ->serialize(&bsonTypeBuilder);
-
- ASSERT_BSONOBJ_EQ(typeBuilder.obj(), bsonTypeBuilder.obj());
- }
-}
-
-TEST(JSONSchemaParserTest, MinPropertiesFailsToParseIfNotNumber) {
- BSONObj schema = fromjson("{minProperties: null}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, MaxPropertiesFailsToParseIfNotNumber) {
- BSONObj schema = fromjson("{maxProperties: null}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, MinPropertiesFailsToParseIfNegative) {
- BSONObj schema = fromjson("{minProperties: -2}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, MaxPropertiesFailsToParseIfNegative) {
- BSONObj schema = fromjson("{maxProperties: -2}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, MinPropertiesFailsToParseIfNotAnInteger) {
- BSONObj schema = fromjson("{minProperties: 1.1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, MaxPropertiesFailsToParseIfNotAnInteger) {
- BSONObj schema = fromjson("{maxProperties: 1.1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, TopLevelMinPropertiesTranslatesCorrectly) {
- BSONObj schema = fromjson("{minProperties: 0}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{$_internalSchemaMinProperties: 0}"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelMaxPropertiesTranslatesCorrectly) {
- BSONObj schema = fromjson("{maxProperties: 0}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{$_internalSchemaMaxProperties: 0}"));
-}
-
-TEST(JSONSchemaParserTest, NestedMinPropertiesTranslatesCorrectly) {
- BSONObj schema =
- fromjson("{properties: {obj: {type: 'object', minProperties: 2}}, required: ['obj']}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- auto expectedResult = fromjson(
- R"({
- $and: [
- {obj: {$exists: true}},
- {obj: {$_internalSchemaObjectMatch: {$_internalSchemaMinProperties: 2}}},
- {obj: {$_internalSchemaType: [3]}}
- ]
- })");
- ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
-}
-
-TEST(JSONSchemaParserTest, NestedMaxPropertiesTranslatesCorrectly) {
- BSONObj schema =
- fromjson("{properties: {obj: {type: 'object', maxProperties: 2}}, required: ['obj']}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- auto expectedResult = fromjson(
- R"({
- $and: [
- {obj: {$exists: true}},
- {obj: {$_internalSchemaObjectMatch: {$_internalSchemaMaxProperties: 2}}},
- {obj: {$_internalSchemaType: [3]}}
- ]
- })");
- ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
-}
-
-TEST(JSONSchemaParserTest, NestedMinPropertiesTranslatesCorrectlyWithoutRequired) {
- BSONObj schema = fromjson("{properties: {obj: {type: 'object', minProperties: 2}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- auto expectedResult = fromjson(R"(
- {
- $or:
- [{obj: {$not: {$exists: true}}}, {
- $and: [
- {obj: {$_internalSchemaObjectMatch: {$_internalSchemaMinProperties: 2}}},
- {obj: {$_internalSchemaType: [3]}}
- ]
- }]
- })");
- ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
-}
-
-TEST(JSONSchemaParserTest, NestedMaxPropertiesTranslatesCorrectlyWithoutRequired) {
- BSONObj schema = fromjson("{properties: {obj: {type: 'object', maxProperties: 2}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- auto expectedResult = fromjson(R"(
- {
- $or:
- [{obj: {$not: {$exists: true}}}, {
- $and: [
- {obj: {$_internalSchemaObjectMatch: {$_internalSchemaMaxProperties: 2}}},
- {obj: {$_internalSchemaType: [3]}}
- ]
- }]
- })");
- ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfTypeArrayHasRepeatedAlias) {
- BSONObj schema = fromjson("{properties: {obj: {type: ['object', 'string', 'object']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfBsonTypeArrayHasRepeatedAlias) {
- BSONObj schema = fromjson("{properties: {obj: {bsonType: ['object', 'string', 'object']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfTypeArrayIsEmpty) {
- BSONObj schema = fromjson("{properties: {obj: {type: []}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfBsonTypeArrayIsEmpty) {
- BSONObj schema = fromjson("{properties: {obj: {bsonType: []}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfTypeArrayContainsNonString) {
- BSONObj schema = fromjson("{properties: {obj: {type: [1]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfBsonTypeArrayContainsNonString) {
- BSONObj schema = fromjson("{properties: {obj: {bsonType: [1]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfTypeArrayContainsUnknownAlias) {
- BSONObj schema = fromjson("{properties: {obj: {type: ['objectId']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseNicelyIfTypeArrayContainsKnownUnsupportedAlias) {
- BSONObj schema = fromjson("{properties: {obj: {type: ['number', 'integer']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema type 'integer' is not currently supported");
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfBsonTypeArrayContainsUnknownAlias) {
- BSONObj schema = fromjson("{properties: {obj: {bsonType: ['unknown']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, CanTranslateTopLevelTypeArrayWithoutObject) {
- BSONObj schema = fromjson("{type: ['number', 'string']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_SERIALIZES_TO(result.getValue(), BSON(AlwaysFalseMatchExpression::kName << 1));
-}
-
-TEST(JSONSchemaParserTest, CanTranslateTopLevelBsonTypeArrayWithoutObject) {
- BSONObj schema = fromjson("{bsonType: ['number', 'string']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_SERIALIZES_TO(result.getValue(), BSON(AlwaysFalseMatchExpression::kName << 1));
-}
-
-TEST(JSONSchemaParserTest, CanTranslateTopLevelTypeArrayWithObject) {
- BSONObj schema = fromjson("{type: ['number', 'object']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_SERIALIZES_TO(result.getValue(), fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, CanTranslateTopLevelBsonTypeArrayWithObject) {
- BSONObj schema = fromjson("{bsonType: ['number', 'object']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_SERIALIZES_TO(result.getValue(), fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, CanTranslateNestedTypeArray) {
- BSONObj schema = fromjson("{properties: {a: {type: ['number', 'object']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}}, {a: {$_internalSchemaType: [ "number", 3 ]}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, CanTranslateNestedBsonTypeArray) {
- BSONObj schema = fromjson("{properties: {a: {bsonType: ['number', 'objectId']}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}}, {a: {$_internalSchemaType: [ "number", 7 ]}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, DependenciesFailsToParseIfNotAnObject) {
- BSONObj schema = fromjson("{dependencies: []}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, DependenciesFailsToParseIfDependencyIsNotObjectOrArray) {
- BSONObj schema = fromjson("{dependencies: {a: ['b'], bad: 1}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, DependenciesFailsToParseIfNestedSchemaIsInvalid) {
- BSONObj schema = fromjson("{dependencies: {a: {invalid: 1}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, PropertyDependencyFailsToParseIfEmptyArray) {
- BSONObj schema = fromjson("{dependencies: {a: []}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, PropertyDependencyFailsToParseIfArrayContainsNonStringElement) {
- BSONObj schema = fromjson("{dependencies: {a: ['b', 1]}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, PropertyDependencyFailsToParseIfRepeatedArrayElement) {
- BSONObj schema = fromjson("{dependencies: {a: ['b', 'b']}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, TopLevelSchemaDependencyTranslatesCorrectly) {
- BSONObj schema = fromjson("{dependencies: {a: {properties: {b: {type: 'string'}}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $_internalSchemaCond:
- [{a: {$exists: true}},
- {$or: [ {b: {$not: {$exists: true}}}, {b: {$_internalSchemaType: [2]}}]},
- {$alwaysTrue: 1}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelPropertyDependencyTranslatesCorrectly) {
- BSONObj schema = fromjson("{dependencies: {a: ['b', 'c']}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $_internalSchemaCond: [
- {a: {$exists: true}},
- {
- $and: [
- {b: {$exists: true}},
- {c: {$exists: true}}
- ]
- },
- {$alwaysTrue: 1}
- ]
- })"));
-}
-
-TEST(JSONSchemaParserTest, NestedSchemaDependencyTranslatesCorrectly) {
- BSONObj schema =
- fromjson("{properties: {a: {dependencies: {b: {properties: {c: {type: 'object'}}}}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}}, {
- $_internalSchemaCond: [
- {a: {$_internalSchemaObjectMatch : {b: {$exists: true}}}},
- {
- $or : [
- {a: {$not: {$_internalSchemaType: [3]}}},
- {
- a: {
- $_internalSchemaObjectMatch : {
- $or : [
- {c: {$not: {$exists: true}}},
- {c: {$_internalSchemaType: [3]}}
- ]
- }
- }
- }
- ]
- },
- {$alwaysTrue : 1}
- ]
- }]
- })"));
-}
-
-TEST(JSONSchemaParserTest, NestedPropertyDependencyTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {a: {dependencies: {b: ['c', 'd']}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- auto expectedResult = fromjson(R"(
- {
- $or:
- [{a: {$not: {$exists: true}}}, {
- $_internalSchemaCond: [
- {a: {$_internalSchemaObjectMatch : {b : {$exists : true}}}},
- {
- $and: [
- {a: {$_internalSchemaObjectMatch: {c: {$exists: true}}}},
- {a: {$_internalSchemaObjectMatch: {d: {$exists: true}}}}
- ]
- },
- {$alwaysTrue: 1}
- ]
- }]
- })");
- ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
-}
-
-TEST(JSONSchemaParserTest, EmptyDependenciesTranslatesCorrectly) {
- BSONObj schema = fromjson("{dependencies: {}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_SERIALIZES_TO(result.getValue(), fromjson("{$and: [{}]}"));
-}
-
-TEST(JSONSchemaParserTest, UnsupportedKeywordsFailNicely) {
- auto result =
- JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{default: {}}"));
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword 'default' is not currently supported");
-
- result = JSONSchemaParser::parse(new ExpressionContextForTest(),
- fromjson("{definitions: {numberField: {type: 'number'}}}"));
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword 'definitions' is not currently supported");
-
- result = JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{format: 'email'}"));
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword 'format' is not currently supported");
-
- result = JSONSchemaParser::parse(new ExpressionContextForTest(),
- fromjson("{id: 'someschema.json'}"));
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword 'id' is not currently supported");
-
- result = JSONSchemaParser::parse(new ExpressionContextForTest(),
- BSON("$ref"
- << "#/definitions/positiveInt"));
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword '$ref' is not currently supported");
-
- result = JSONSchemaParser::parse(new ExpressionContextForTest(),
- fromjson("{$schema: 'hyper-schema'}"));
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword '$schema' is not currently supported");
-
- result =
- JSONSchemaParser::parse(new ExpressionContextForTest(),
- fromjson("{$schema: 'http://json-schema.org/draft-04/schema#'}"));
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword '$schema' is not currently supported");
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfDescriptionIsNotAString) {
- auto result =
- JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{description: {}}"));
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, CorrectlyParsesDescriptionAsString) {
- auto result =
- JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{description: 'str'}"));
- ASSERT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, CorrectlyParsesNestedDescriptionAsString) {
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(),
- fromjson("{properties: {a: {description: 'str'}}}"));
- ASSERT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfTitleIsNotAString) {
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{title: {}}"));
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, CorrectlyParsesTitleAsString) {
- auto result =
- JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{title: 'str'}"));
- ASSERT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, CorrectlyParsesNestedTitleAsString) {
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(),
- fromjson("{properties: {a: {title: 'str'}}}"));
- ASSERT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, PatternPropertiesFailsToParseIfNotObject) {
- BSONObj schema = fromjson("{patternProperties: 1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, PatternPropertiesFailsToParseIfOnePropertyIsNotObject) {
- BSONObj schema = fromjson("{patternProperties: {a: {}, b: 1}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, PatternPropertiesFailsToParseIfNestedSchemaIsInvalid) {
- BSONObj schema = fromjson("{patternProperties: {a: {invalid: 1}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, PatternPropertiesFailsToParseIfPropertyNameIsAnInvalidRegex) {
- BSONObj schema = fromjson("{patternProperties: {'[': {}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, AdditionalPropertiesFailsToParseIfNotBoolOrString) {
- BSONObj schema = fromjson("{additionalProperties: 1}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, AdditionalPropertiesFailsToParseIfNestedSchemaIsInvalid) {
- BSONObj schema = fromjson("{additionalProperties: {invalid: 1}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_NOT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, TopLevelPatternPropertiesTranslatesCorrectly) {
- BSONObj schema =
- fromjson("{patternProperties: {'^a': {type: 'number'}, '^b': {type: 'string'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $_internalSchemaAllowedProperties: {
- properties: [],
- namePlaceholder: "i",
- patternProperties: [
- {
- regex: /^a/,
- expression: {
- i: {$_internalSchemaType: ['number']}
- }
- },
- {
- regex: /^b/,
- expression: {
- i: {$_internalSchemaType: [2]}
- }
- }
- ],
- otherwise: {$alwaysTrue: 1}
- }
- })"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelAdditionalPropertiesFalseTranslatesCorrectly) {
- BSONObj schema = fromjson("{additionalProperties: false}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $_internalSchemaAllowedProperties: {
- properties: [],
- namePlaceholder: "i",
- patternProperties: [],
- otherwise: {$alwaysFalse: 1}
- }
- })"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelAdditionalPropertiesTrueTranslatesCorrectly) {
- BSONObj schema = fromjson("{additionalProperties: true}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $_internalSchemaAllowedProperties: {
- properties: [],
- namePlaceholder: "i",
- patternProperties: [],
- otherwise: {$alwaysTrue: 1}
- }
- })"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelAdditionalPropertiesTypeNumberTranslatesCorrectly) {
- BSONObj schema = fromjson("{additionalProperties: {type: 'number'}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $_internalSchemaAllowedProperties: {
- properties: [],
- namePlaceholder: "i",
- patternProperties: [],
- otherwise: {i: {$_internalSchemaType: ['number']}}
- }
- })"));
-}
-
-TEST(JSONSchemaParserTest, NestedAdditionalPropertiesTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {obj: {additionalProperties: {type: 'number'}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{obj: {$not: {$exists: true}}}, {obj: {$not: {$_internalSchemaType: [3]}}}, {
- obj: {
- $_internalSchemaObjectMatch: {
- $_internalSchemaAllowedProperties: {
- properties: [],
- namePlaceholder: "i",
- patternProperties: [],
- otherwise: {i: {$_internalSchemaType: ["number"]}}
- }
- }
- }
- }]
- })"));
-}
-
-TEST(JSONSchemaParserTest,
- PropertiesPatternPropertiesAndAdditionalPropertiesTranslateCorrectlyTogether) {
- BSONObj schema = fromjson(
- "{properties: {a: {}, b: {}}, patternProperties: {'^c': {}}, additionalProperties: false}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $_internalSchemaAllowedProperties: {
- properties: ["a", "b"],
- namePlaceholder: "i",
- patternProperties: [
- {regex: /^c/, expression: {}}
- ],
- otherwise: {$alwaysFalse: 1}
- }
- })"));
-}
-
-TEST(JSONSchemaParserTest,
- PropertiesPatternPropertiesAdditionalPropertiesAndRequiredTranslateCorrectlyTogether) {
- BSONObj schema = fromjson(
- "{properties: {a: {}, b: {}}, required: ['a'], patternProperties: {'^c': {}}, "
- "additionalProperties: false}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $and: [
- {
- $_internalSchemaAllowedProperties: {
- properties: ["a", "b"],
- namePlaceholder: "i",
- patternProperties: [
- {regex: /^c/, expression: {}}
- ],
- otherwise: {$alwaysFalse: 1}
- }
- },
- {a: {$exists: true}}
- ]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfUniqueItemsIsNotABoolean) {
- auto schema = BSON("uniqueItems" << 1);
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, UniqueItemsFalseGeneratesAlwaysTrueExpression) {
- auto schema = fromjson("{properties: {a: {uniqueItems: false}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, UniqueItemsTranslatesCorrectlyWithNoType) {
- auto schema = BSON("uniqueItems" << true);
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
-
- schema = fromjson("{properties: {a: {uniqueItems: true}}}");
- result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}},
- {a: {$not: {$_internalSchemaType: [4]}}},
- {a: {$_internalSchemaUniqueItems: true}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, UniqueItemsTranslatesCorrectlyWithTypeArray) {
- auto schema = fromjson("{properties: {a: {type: 'array', uniqueItems: true}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}},
- {$and: [ {a: {$_internalSchemaUniqueItems: true}}, {a: {$_internalSchemaType: [4]}}]}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, CorrectlyIgnoresUnknownKeywordsParameterIsSet) {
- const auto ignoreUnknownKeywords = true;
-
- auto schema = fromjson("{ignored_keyword: 1}");
- ASSERT_OK(JSONSchemaParser::parse(new ExpressionContextForTest(), schema, ignoreUnknownKeywords)
- .getStatus());
-
- schema = fromjson("{properties: {a: {ignored_keyword: 1}}}");
- ASSERT_OK(JSONSchemaParser::parse(new ExpressionContextForTest(), schema, ignoreUnknownKeywords)
- .getStatus());
-
- schema = fromjson("{properties: {a: {oneOf: [{ignored_keyword: {}}]}}}");
- ASSERT_OK(JSONSchemaParser::parse(new ExpressionContextForTest(), schema, ignoreUnknownKeywords)
- .getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseUnsupportedKeywordsWhenIgnoreUnknownParameterIsSet) {
- const auto ignoreUnknownKeywords = true;
-
- auto result = JSONSchemaParser::parse(
- new ExpressionContextForTest(), fromjson("{default: {}}"), ignoreUnknownKeywords);
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword 'default' is not currently supported");
-
- result = JSONSchemaParser::parse(new ExpressionContextForTest(),
- fromjson("{definitions: {numberField: {type: 'number'}}}"),
- ignoreUnknownKeywords);
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword 'definitions' is not currently supported");
-
- result = JSONSchemaParser::parse(
- new ExpressionContextForTest(), fromjson("{format: 'email'}"), ignoreUnknownKeywords);
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword 'format' is not currently supported");
-
- result = JSONSchemaParser::parse(
- new ExpressionContextForTest(), fromjson("{id: 'someschema.json'}"), ignoreUnknownKeywords);
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword 'id' is not currently supported");
-
- result = JSONSchemaParser::parse(new ExpressionContextForTest(),
- BSON("$ref"
- << "#/definitions/positiveInt"),
- ignoreUnknownKeywords);
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword '$ref' is not currently supported");
-
- result = JSONSchemaParser::parse(new ExpressionContextForTest(),
- fromjson("{$schema: 'hyper-schema'}"),
- ignoreUnknownKeywords);
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword '$schema' is not currently supported");
-
- result =
- JSONSchemaParser::parse(new ExpressionContextForTest(),
- fromjson("{$schema: 'http://json-schema.org/draft-04/schema#'}"),
- ignoreUnknownKeywords);
- ASSERT_STRING_CONTAINS(result.getStatus().reason(),
- "$jsonSchema keyword '$schema' is not currently supported");
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfItemsIsNotAnArrayOrObject) {
- auto schema = BSON("items" << 1);
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfItemsIsAnArrayWithANonObject) {
- auto schema = fromjson("{items: [{type: 'string'}, 'blah']}");
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfItemsIsAnInvalidSchema) {
- auto schema = BSON("items" << BSON("invalid" << 1));
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfItemsIsAnArrayThatContainsAnInvalidSchema) {
- auto schema = fromjson("{items: [{type: 'string'}, {invalid: 1}]}");
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, ItemsParsesSuccessfullyAsArrayAtTopLevel) {
- auto schema = fromjson("{items: [{type: 'string'}]}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, ItemsParsesSuccessfullyAsObjectAtTopLevel) {
- auto schema = fromjson("{items: {type: 'string'}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, ItemsParsesSuccessfullyAsArrayInNestedSchema) {
- auto schema = fromjson("{properties: {a: {items: [{maxLength: 4}, {minimum: 0}]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- auto expectedResult = fromjson(R"(
- {
- $or:
- [{a: {$not: {$exists: true}}}, {a: {$not: {$_internalSchemaType: [4]}}}, {
- $and: [
- {
- a: {
- $_internalSchemaMatchArrayIndex: {
- index: 0,
- namePlaceholder: "i",
- expression: {
- $or: [
- {i: {$not: {$_internalSchemaType: [2]}}},
- {i: {$_internalSchemaMaxLength: 4}}
- ]
- }
- }
- }
- },
- {
- a: {
- $_internalSchemaMatchArrayIndex : {
- index: 1,
- namePlaceholder: "i",
- expression: {
- $or: [
- {i : {$not: {$_internalSchemaType: ["number"]}}},
- {i: {$gte: 0}}
- ]
- }
- }
- }
- }
- ]
- }]
- })");
- ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
-}
-
-TEST(JSONSchemaParserTest, ItemsParsesSuccessfullyAsObjectInNestedSchema) {
- auto schema = fromjson("{properties: {a: {items: {type: 'string'}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{a: {$not: {$exists: true}}},
- {a: {$not: {$_internalSchemaType: [4]}}},
- {a: {$_internalSchemaAllElemMatchFromIndex : [ 0, {i: {$_internalSchemaType: [2]}}]}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfAdditionalItemsIsNotAnObjectOrBoolean) {
- auto schema = BSON("items" << BSONObj() << "additionalItems" << 1);
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::TypeMismatch);
-
- schema = BSON("additionalItems" << 1);
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfAdditionalItemsIsAnInvalidSchema) {
- auto schema = BSON("items" << BSONObj() << "additionalItems" << BSON("invalid" << 1));
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::FailedToParse);
-
- schema = BSON("additionalItems" << BSON("invalid" << 1));
- ASSERT_EQ(JSONSchemaParser::parse(new ExpressionContextForTest(), schema).getStatus(),
- ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, AdditionalItemsTranslatesSucessfullyAsBooleanAtTopLevel) {
- auto schema = fromjson("{items: [], additionalItems: true}");
- auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
-
- schema = fromjson("{items: [], additionalItems: false}");
- expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, AdditionalItemsTranslatesSucessfullyAsObjectAtTopLevel) {
- auto schema = fromjson("{items: [], additionalItems: {multipleOf: 7}}");
- auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, AdditionalItemsTranslatesSucessfullyAsBooleanInNestedSchema) {
- auto schema = fromjson("{properties: {a: {items: [], additionalItems: true}}}");
- auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- auto expectedResult = fromjson(R"(
- {
- $or:
- [{a: {$not: {$exists: true}}},
- {a: {$not: {$_internalSchemaType: [4]}}},
- {a: {$_internalSchemaAllElemMatchFromIndex: [0, {$alwaysTrue: 1}]}}]
- })");
- ASSERT_SERIALIZES_TO(optimizedExpr, expectedResult);
-
- schema = fromjson("{properties: {a: {items: [], additionalItems: false}}}");
- expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- expectedResult = fromjson(R"(
- {
- $or:
- [{a: {$not: {$exists: true}}},
- {a: {$not: {$_internalSchemaType: [4]}}},
- {a: {$_internalSchemaAllElemMatchFromIndex: [0, {$alwaysFalse: 1}]}}]
- })");
- ASSERT_SERIALIZES_TO(optimizedExpr, expectedResult);
-}
-
-TEST(JSONSchemaParserTest, AdditionalItemsGeneratesEmptyExpressionAtTopLevelIfItemsNotPresent) {
- auto schema = BSON("additionalItems" << true);
- auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
-
- schema = BSON("additionalItems" << false);
- expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
-
- schema = BSON("additionalItems" << BSON("minLength" << 1));
- expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, AdditionalItemsGeneratesEmptyExpressionInNestedSchemaIfItemsNotPresent) {
- auto schema = fromjson("{properties: {foo: {additionalItems: true}}}");
- auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
-
-
- schema = fromjson("{properties: {foo: {additionalItems: false}}}");
- expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- ASSERT_SERIALIZES_TO(optimizedExpr, fromjson("{}"));
-}
-
-TEST(JSONSchemaParserTest, AdditionalItemsGeneratesEmptyExpressionIfItemsAnObject) {
- auto schema = fromjson("{properties: {a: {items: {minimum: 7}, additionalItems: false}}}");
- auto expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- auto optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- auto expectedResult = fromjson(R"(
- {
- $or:
- [{a: {$not: {$exists: true}}},
- {a: {$not: {$_internalSchemaType: [4]}}},
- {
- a: {
- $_internalSchemaAllElemMatchFromIndex: [
- 0,
- {$or: [ {i: {$not: {$_internalSchemaType: ["number"]}}}, {i: {$gte: 7}}]}
- ]
- }
- }]
- })");
- ASSERT_SERIALIZES_TO(optimizedExpr, expectedResult);
-
- schema = fromjson("{properties: {a: {items: {minimum: 7}, additionalItems: {minLength: 7}}}}");
- expr = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(expr.getStatus());
- optimizedExpr = MatchExpression::optimize(std::move(expr.getValue()));
- expectedResult = fromjson(R"(
- {
- $or:
- [{a: {$not: {$exists: true}}},
- {a: {$not: {$_internalSchemaType: [4]}}},
- {
- a: {
- $_internalSchemaAllElemMatchFromIndex : [
- 0,
- {$or: [ {i: {$not: {$_internalSchemaType: ["number"]}}}, {i: {$gte: 7}}]}
- ]
- }
- }]
- })");
- ASSERT_SERIALIZES_TO(optimizedExpr, expectedResult);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfEnumIsNotAnArray) {
- BSONObj schema = fromjson("{properties: {foo: {enum: 'foo'}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseEnumIfArrayIsEmpty) {
- BSONObj schema = fromjson("{properties: {foo: {enum: []}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseEnumIfArrayContainsDuplicateValue) {
- BSONObj schema = fromjson("{properties: {foo: {enum: [1, 2, 1]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-
- schema = fromjson("{properties: {foo: {enum: [{a: 1, b: 1}, {b: 1, a: 1}]}}}");
- result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, EnumTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {foo: {enum: [1, '2', [3]]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
- $or:
- [{foo: {$not: {$exists: true}}},
- {foo: {$_internalSchemaEq: 1}},
- {foo: {$_internalSchemaEq: "2"}},
- {foo: {$_internalSchemaEq: [3]}}]
- })"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelEnumTranslatesCorrectly) {
- BSONObj schema = fromjson("{enum: [1, {foo: 1}]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{$_internalSchemaRootDocEq: {foo: 1}}"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelEnumWithZeroObjectsTranslatesCorrectly) {
- BSONObj schema = fromjson("{enum: [1, 'impossible', true]}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{$alwaysFalse: 1}"));
-}
-
-TEST(JSONSchemaParserTest, EncryptTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {foo: {encrypt: {}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
- {
- $or:
- [{foo: {$not: {$exists: true}}}, {
- $and:
- [ {foo: {$_internalSchemaBinDataSubType: 6}}, {foo: {$_internalSchemaType: [5]}} ]
- }]
- })"));
-}
-
-TEST(JSONSchemaParserTest, EncryptWithSingleBsonTypeTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {foo: {encrypt: {bsonType: \"string\"}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
- {
- $or:
- [{foo: {$not: {$exists: true}}}, {
- $and:
- [ {foo: {$_internalSchemaBinDataSubType: 6}},
- {foo: {$_internalSchemaBinDataEncryptedType: [2]}},
- {foo: {$_internalSchemaType: [5]}}]
- }]
- })"));
-}
-
-TEST(JSONSchemaParserTest, EncryptWithArrayOfMultipleTypesTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {foo: {encrypt: {bsonType: [\"string\",\"date\"]}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
- {
- $or:
- [{foo: {$not: {$exists: true}}}, {
- $and:
- [ {foo: {$_internalSchemaBinDataSubType: 6}},
- {foo: {$_internalSchemaBinDataEncryptedType: [2, 9]}},
- {foo: {$_internalSchemaType: [5]}}]
- }]
- })"));
-}
-
-TEST(JSONSchemaParserTest, TopLevelEncryptTranslatesCorrectly) {
- BSONObj schema = fromjson("{encrypt: {}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, BSON(AlwaysFalseMatchExpression::kName << 1));
-}
-
-TEST(JSONSchemaParserTest, NestedEncryptTranslatesCorrectly) {
- BSONObj schema =
- fromjson("{properties: {a: {type: 'object', properties: {b: {encrypt: {}}}}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
- {
- $or:
- [{a: {$not: {$exists: true}}}, {
- $and: [
- {
- a: {
- $_internalSchemaObjectMatch: {
- $or: [
- {b: {$not : {$exists: true}}},
- {
- $and: [
- {b: {$_internalSchemaBinDataSubType: 6}},
- {b: {$_internalSchemaType: [5]}}
- ]
- }
- ]
- }
- }
- },
- {a: {$_internalSchemaType: [3]}}
- ]
- }]
- })"));
-}
-
-TEST(JSONSchemaParserTest, NestedEncryptInArrayTranslatesCorrectly) {
- BSONObj schema = fromjson("{properties: {a: {type: 'array', items: {encrypt: {}}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
- auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
- ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
- {
- $or:
- [{a: {$not: {$exists: true}}}, {
- $and: [
- {
- a: {
- $_internalSchemaAllElemMatchFromIndex : [
- 0,
- {
- $and: [
- {i: {$_internalSchemaBinDataSubType: 6}},
- {i: {$_internalSchemaType: [5]}}
- ]
- }
- ]
- }
- },
- {a: {$_internalSchemaType: [4]}}
- ]
- }]
- })"));
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfBothEncryptAndTypeArePresent) {
- BSONObj schema = fromjson("{encrypt: {}, type: 'object'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfBothEncryptAndBSONTypeArePresent) {
- BSONObj schema = fromjson("{encrypt: {}, bsonType: 'binData'}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfEncryptValueIsNotObject) {
- BSONObj schema = fromjson("{properties: {foo: {encrypt: 12}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, ParseSucceedsWithEmptyEncryptObject) {
- BSONObj schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSONObj())));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, ParseSucceedsWithBsonType) {
- BSONObj schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("bsonType"
- << "int"))));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, ParseFailsWithBsonTypeGivenByCode) {
- BSONObj schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("bsonType" << 5))));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus().code(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, ParseSucceedsWithArrayOfBsonTypes) {
- BSONObj schema =
- BSON("properties" << BSON(
- "foo" << BSON("encrypt" << BSON("bsonType" << BSON_ARRAY("int"
- << "date"
- << "string")))));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, ParseSucceedsIfEncryptFieldsAreValid) {
- auto schema = BSON(
- "properties" << BSON(
- "foo" << BSON("encrypt" << BSON("bsonType"
- << "string"
- << "keyId"
- << "/pointer"
- << "algorithm"
- << "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"))));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfEncryptHasBadFieldName) {
- BSONObj schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("keyIdx"
- << "/pointer"))));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus().code(), 40415);
- schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("bsonType"
- << "bool"
- << "keyIdx"
- << "/pointer"))));
- result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus().code(), 40415);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseWithBadKeyIdArray) {
- auto schema = BSON(
- "properties" << BSON("foo" << BSON("encrypt" << BSON("keyId" << BSON_ARRAY("nonsense"
- << "again")))));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus().code(), 51088);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseWithBadBSONType) {
- auto schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("bsonType"
- << "Stringx"))));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus().code(), ErrorCodes::BadValue);
-
- schema = BSON("properties" << BSON(
- "foo" << BSON("encrypt" << BSON("bsonType" << (BSONType::JSTypeMax + 1)))));
- result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus().code(), ErrorCodes::TypeMismatch);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseWithBadAlgorithm) {
- auto schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("algorithm"
- << "Stringx"))));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus().code(), ErrorCodes::BadValue);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseWithBadPointer) {
- auto schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("keyId"
- << "invalidPointer"))));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus().code(), 51065);
-}
-
-TEST(JSONSchemaParserTest, TopLevelEncryptMetadataValidatedCorrectly) {
- BSONObj schema = fromjson(
- "{encryptMetadata: {algorithm: \"AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic\","
- " keyId: [{$binary: \"ASNFZ4mrze/ty6mHZUMhAQ==\", $type: \"04\"}]}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, NestedEncryptMetadataValidatedCorrectly) {
- BSONObj schema = fromjson(
- "{properties: {a: {encryptMetadata: {algorithm: \"AEAD_AES_256_CBC_HMAC_SHA_512-Random\", "
- "keyId: [{$binary: \"ASNFZ4mrze/ty6mHZUMhAQ==\", $type: \"04\"}]}}}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_OK(result.getStatus());
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfEncryptMetadataValueIsEmptyObject) {
- BSONObj schema = fromjson("{encryptMetadata: {}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseIfBothEncryptAndEncryptMetadataAreSiblings) {
- BSONObj schema = fromjson(
- "{encrypt: {}, encryptMetadata: {algorithm: "
- "\"AEAD_AES_256_CBC_HMAC_SHA_512-Random\"}}");
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
-}
-
-TEST(JSONSchemaParserTest, FailsToParseWithNonUUIDArrayElement) {
- BSONArrayBuilder builder;
- UUID::gen().appendToArrayBuilder(&builder);
- UUID::gen().appendToArrayBuilder(&builder);
- builder.appendBinData(16, BinDataType::Encrypt, "16charactershere");
- auto schema =
- BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("keyId" << builder.arr()))));
- auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
- ASSERT_EQ(result.getStatus().code(), 51084);
-}
-
-} // namespace
-} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/logical_keywords_test.cpp b/src/mongo/db/matcher/schema/logical_keywords_test.cpp
new file mode 100644
index 00000000000..1dbb709d4bf
--- /dev/null
+++ b/src/mongo/db/matcher/schema/logical_keywords_test.cpp
@@ -0,0 +1,271 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/json.h"
+#include "mongo/db/matcher/schema/json_schema_parser.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+#define ASSERT_SERIALIZES_TO(match, expected) \
+ do { \
+ BSONObjBuilder bob; \
+ match->serialize(&bob); \
+ ASSERT_BSONOBJ_EQ(bob.obj(), expected); \
+ } while (false)
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseIfAllOfIsNotAnArray) {
+ BSONObj schema = fromjson("{properties: {foo: {allOf: 'foo'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseAllOfIfArrayContainsInvalidSchema) {
+ BSONObj schema = fromjson("{properties: {foo: {allOf: [{type: {}}]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseAllOfIfArrayIsEmpty) {
+ BSONObj schema = fromjson("{properties: {foo: {allOf: []}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::BadValue);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, AllOfTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {allOf: [{minimum: 0}, {maximum: 10}]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ auto expectedResult = fromjson(
+ R"({
+ $or:
+ [{foo: {$not: {$exists: true}}},
+ {
+ $and : [
+ {$or: [ {foo: {$not: {$_internalSchemaType: ["number"]}}}, {foo: {$gte: 0}}]},
+ {$or: [ {foo: {$not: {$_internalSchemaType: ["number"]}}}, {foo: {$lte: 10}}]}
+ ]
+ }]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, TopLevelAllOfTranslatesCorrectly) {
+ BSONObj schema = fromjson("{allOf: [{properties: {foo: {type: 'string'}}}]}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}}, {foo: {$_internalSchemaType: [2]}}]
+ })"));
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseIfAnyOfIsNotAnArray) {
+ BSONObj schema = fromjson("{properties: {foo: {anyOf: 'foo'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseAnyOfIfArrayContainsInvalidSchema) {
+ BSONObj schema = fromjson("{properties: {foo: {anyOf: [{type: {}}]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseAnyOfIfArrayIsEmpty) {
+ BSONObj schema = fromjson("{properties: {foo: {anyOf: []}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::BadValue);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, AnyOfTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {anyOf: [{type: 'number'}, {type: 'string'}]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}},
+ {foo: {$_internalSchemaType: ["number"]}},
+ {foo: {$_internalSchemaType: [2]}}]
+ })"));
+}
+
+TEST(JSONSchemaLogicalKeywordTest, TopLevelAnyOfTranslatesCorrectly) {
+ BSONObj schema = fromjson("{anyOf: [{properties: {foo: {type: 'string'}}}]}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}}, {foo: {$_internalSchemaType: [2]}}]
+ })"));
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseIfOneOfIsNotAnArray) {
+ BSONObj schema = fromjson("{properties: {foo: {oneOf: 'foo'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseOneOfIfArrayContainsInvalidSchema) {
+ BSONObj schema = fromjson("{properties: {foo: {oneOf: [{type: {}}]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseOneOfIfArrayIsEmpty) {
+ BSONObj schema = fromjson("{properties: {foo: {oneOf: []}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::BadValue);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, OneOfTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {oneOf: [{minimum: 0}, {maximum: 10}]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}},
+ {
+ $_internalSchemaXor : [
+ {$or: [ {foo: {$not: {$_internalSchemaType: ["number"]}}}, {foo: {$gte : 0}}]},
+ {$or: [ {foo: {$not: {$_internalSchemaType: ["number"]}}}, {foo: {$lte : 10}}]}
+ ]
+ }]
+ })"));
+}
+
+TEST(JSONSchemaLogicalKeywordTest, TopLevelOneOfTranslatesCorrectly) {
+ BSONObj schema = fromjson("{oneOf: [{properties: {foo: {type: 'string'}}}]}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}}, {foo: {$_internalSchemaType: [2]}}]
+ })"));
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseIfNotIsNotAnObject) {
+ BSONObj schema = fromjson("{properties: {foo: {not: 'foo'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseNotIfObjectContainsInvalidSchema) {
+ BSONObj schema = fromjson("{properties: {foo: {not: {type: {}}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, NotTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {not: {type: 'number'}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}}, {foo: {$not: {$_internalSchemaType: ['number']}}}]
+ })"));
+}
+
+TEST(JSONSchemaLogicalKeywordTest, TopLevelNotTranslatesCorrectly) {
+ BSONObj schema = fromjson("{not: {properties: {foo: {type: 'string'}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $nor:
+ [{$or: [ {foo: {$not: {$exists: true}}}, {foo: {$_internalSchemaType: [2]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseIfEnumIsNotAnArray) {
+ BSONObj schema = fromjson("{properties: {foo: {enum: 'foo'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseEnumIfArrayIsEmpty) {
+ BSONObj schema = fromjson("{properties: {foo: {enum: []}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, FailsToParseEnumIfArrayContainsDuplicateValue) {
+ BSONObj schema = fromjson("{properties: {foo: {enum: [1, 2, 1]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+
+ schema = fromjson("{properties: {foo: {enum: [{a: 1, b: 1}, {b: 1, a: 1}]}}}");
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaLogicalKeywordTest, EnumTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {enum: [1, '2', [3]]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}},
+ {foo: {$_internalSchemaEq: 1}},
+ {foo: {$_internalSchemaEq: "2"}},
+ {foo: {$_internalSchemaEq: [3]}}]
+ })"));
+}
+
+TEST(JSONSchemaLogicalKeywordTest, TopLevelEnumTranslatesCorrectly) {
+ BSONObj schema = fromjson("{enum: [1, {foo: 1}]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{$_internalSchemaRootDocEq: {foo: 1}}"));
+}
+
+TEST(JSONSchemaLogicalKeywordTest, TopLevelEnumWithZeroObjectsTranslatesCorrectly) {
+ BSONObj schema = fromjson("{enum: [1, 'impossible', true]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{$alwaysFalse: 1}"));
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/object_keywords_test.cpp b/src/mongo/db/matcher/schema/object_keywords_test.cpp
new file mode 100644
index 00000000000..3829f9f68ff
--- /dev/null
+++ b/src/mongo/db/matcher/schema/object_keywords_test.cpp
@@ -0,0 +1,939 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/json.h"
+#include "mongo/db/matcher/expression_always_boolean.h"
+#include "mongo/db/matcher/schema/json_schema_parser.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+#define ASSERT_SERIALIZES_TO(match, expected) \
+ do { \
+ BSONObjBuilder bob; \
+ match->serialize(&bob); \
+ ASSERT_BSONOBJ_EQ(bob.obj(), expected); \
+ } while (false)
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfTypeIsNotAString) {
+ BSONObj schema = fromjson("{type: 1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseNicelyIfTypeIsKnownUnsupportedAlias) {
+ BSONObj schema = fromjson("{type: 'integer'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema type 'integer' is not currently supported");
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseUnknownKeyword) {
+ BSONObj schema = fromjson("{unknown: 1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfPropertiesIsNotAnObject) {
+ BSONObj schema = fromjson("{properties: 1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfPropertiesIsNotAnObjectWithType) {
+ BSONObj schema = fromjson("{type: 'string', properties: 1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfParticularPropertyIsNotAnObject) {
+ BSONObj schema = fromjson("{properties: {foo: 1}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfKeywordIsDuplicated) {
+ BSONObj schema = BSON("type"
+ << "object"
+ << "type"
+ << "object");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaObjectKeywordTest, EmptySchemaTranslatesCorrectly) {
+ BSONObj schema = fromjson("{}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, TypeObjectTranslatesCorrectly) {
+ BSONObj schema = fromjson("{type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{}"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, NestedTypeObjectTranslatesCorrectly) {
+ BSONObj schema =
+ fromjson("{properties: {a: {type: 'object', properties: {b: {type: 'string'}}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(
+ optimizedResult,
+ fromjson("{$or: [{a: {$not: {$exists: true }}}, {$and: [{a: {$_internalSchemaObjectMatch: "
+ "{$or: [{b: {$not: {$exists: true}}}, {b: {$_internalSchemaType: [2]}}]}}}, {a: "
+ "{$_internalSchemaType: [3]}}]}]}"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelNonObjectTypeTranslatesCorrectly) {
+ BSONObj schema = fromjson("{type: 'string'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, BSON(AlwaysFalseMatchExpression::kName << 1));
+}
+
+TEST(JSONSchemaObjectKeywordTest, TypeNumberTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {num: {type: 'number'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult,
+ fromjson("{$or: [{num: {$not: {$exists: true }}}, {num: "
+ "{ $_internalSchemaType: [ 'number' ]}}]}"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, RequiredFailsToParseIfNotAnArray) {
+ BSONObj schema = fromjson("{required: 'field'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaObjectKeywordTest, RequiredFailsToParseArrayIsEmpty) {
+ BSONObj schema = fromjson("{required: []}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaObjectKeywordTest, RequiredFailsToParseIfArrayContainsNonString) {
+ BSONObj schema = fromjson("{required: ['foo', 1]}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaObjectKeywordTest, RequiredFailsToParseIfArrayContainsDuplicates) {
+ BSONObj schema = fromjson("{required: ['foo', 'bar', 'foo']}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelRequiredTranslatesCorrectly) {
+ BSONObj schema = fromjson("{required: ['foo', 'bar']}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult,
+ fromjson("{$and: [{bar: {$exists: true}}, {foo: {$exists: true}}]}"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelRequiredTranslatesCorrectlyWithProperties) {
+ BSONObj schema = fromjson("{required: ['foo'], properties: {foo: {type: 'number'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $and: [
+ {foo: {$_internalSchemaType: ['number']}},
+ {foo: {$exists: true}}
+ ]
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, RequiredTranslatesCorrectlyWithMultipleElements) {
+ BSONObj schema = fromjson("{properties: {x: {required: ['y', 'z']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{x: {$not: {$exists: true}}}, {x: {$not: {$_internalSchemaType: [3]}}}, {
+ x: {
+ $_internalSchemaObjectMatch:
+ {$and: [ {y: {$exists: true}}, {z: {$exists: true}}]}
+ }
+ }]
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, RequiredTranslatesCorrectlyInsideProperties) {
+ BSONObj schema = fromjson("{properties: {x: {required: ['y']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{x: {$not: {$exists: true}}},
+ {x: {$not: {$_internalSchemaType: [3]}}},
+ {x: {$_internalSchemaObjectMatch: {y: {$exists: true}}}}]
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest,
+ RequiredTranslatesCorrectlyInsidePropertiesWithSiblingProperties) {
+ BSONObj schema =
+ fromjson("{properties: {x:{required: ['y'], properties: {y: {type: 'number'}}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ auto expectedResult = fromjson(
+ R"({
+ $or:
+ [{x: {$not: {$exists: true}}},
+ {
+ $and: [
+ {
+ $or: [
+ {x: {$not: {$_internalSchemaType: [3]}}},
+ {x: {$_internalSchemaObjectMatch: {y : {$_internalSchemaType: ["number"]}}}}
+ ]
+ },
+ {
+ $or: [
+ {x: {$not: {$_internalSchemaType: [3]}}},
+ {x: {$_internalSchemaObjectMatch: {y: {$exists: true}}}}
+ ]
+ }
+ ]
+ }]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
+}
+
+TEST(JSONSchemaObjectKeywordTest, SharedJsonAndBsonTypeAliasesTranslateIdentically) {
+ for (auto&& mapEntry : MatcherTypeSet::kJsonSchemaTypeAliasMap) {
+ auto typeAlias = mapEntry.first;
+ // JSON Schema spells its bool type as "boolean", whereas MongoDB calls it "bool".
+ auto bsonTypeAlias =
+ (typeAlias == JSONSchemaParser::kSchemaTypeBoolean) ? "bool" : typeAlias;
+
+ BSONObj typeSchema = BSON("properties" << BSON("f" << BSON("type" << typeAlias)));
+ BSONObj bsonTypeSchema =
+ BSON("properties" << BSON("f" << BSON("bsonType" << bsonTypeAlias)));
+ auto typeResult = JSONSchemaParser::parse(new ExpressionContextForTest(), typeSchema);
+ ASSERT_OK(typeResult.getStatus());
+ auto bsonTypeResult =
+ JSONSchemaParser::parse(new ExpressionContextForTest(), bsonTypeSchema);
+ ASSERT_OK(bsonTypeResult.getStatus());
+
+ BSONObjBuilder typeBuilder;
+ MatchExpression::optimize(std::move(typeResult.getValue()))->serialize(&typeBuilder);
+
+ BSONObjBuilder bsonTypeBuilder;
+ MatchExpression::optimize(std::move(bsonTypeResult.getValue()))
+ ->serialize(&bsonTypeBuilder);
+
+ ASSERT_BSONOBJ_EQ(typeBuilder.obj(), bsonTypeBuilder.obj());
+ }
+}
+
+TEST(JSONSchemaObjectKeywordTest, MinPropertiesFailsToParseIfNotNumber) {
+ BSONObj schema = fromjson("{minProperties: null}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, MaxPropertiesFailsToParseIfNotNumber) {
+ BSONObj schema = fromjson("{maxProperties: null}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, MinPropertiesFailsToParseIfNegative) {
+ BSONObj schema = fromjson("{minProperties: -2}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, MaxPropertiesFailsToParseIfNegative) {
+ BSONObj schema = fromjson("{maxProperties: -2}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, MinPropertiesFailsToParseIfNotAnInteger) {
+ BSONObj schema = fromjson("{minProperties: 1.1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, MaxPropertiesFailsToParseIfNotAnInteger) {
+ BSONObj schema = fromjson("{maxProperties: 1.1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelMinPropertiesTranslatesCorrectly) {
+ BSONObj schema = fromjson("{minProperties: 0}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{$_internalSchemaMinProperties: 0}"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelMaxPropertiesTranslatesCorrectly) {
+ BSONObj schema = fromjson("{maxProperties: 0}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{$_internalSchemaMaxProperties: 0}"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, NestedMinPropertiesTranslatesCorrectly) {
+ BSONObj schema =
+ fromjson("{properties: {obj: {type: 'object', minProperties: 2}}, required: ['obj']}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ auto expectedResult = fromjson(
+ R"({
+ $and: [
+ {obj: {$exists: true}},
+ {obj: {$_internalSchemaObjectMatch: {$_internalSchemaMinProperties: 2}}},
+ {obj: {$_internalSchemaType: [3]}}
+ ]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
+}
+
+TEST(JSONSchemaObjectKeywordTest, NestedMaxPropertiesTranslatesCorrectly) {
+ BSONObj schema =
+ fromjson("{properties: {obj: {type: 'object', maxProperties: 2}}, required: ['obj']}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ auto expectedResult = fromjson(
+ R"({
+ $and: [
+ {obj: {$exists: true}},
+ {obj: {$_internalSchemaObjectMatch: {$_internalSchemaMaxProperties: 2}}},
+ {obj: {$_internalSchemaType: [3]}}
+ ]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
+}
+
+TEST(JSONSchemaObjectKeywordTest, NestedMinPropertiesTranslatesCorrectlyWithoutRequired) {
+ BSONObj schema = fromjson("{properties: {obj: {type: 'object', minProperties: 2}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ auto expectedResult = fromjson(R"(
+ {
+ $or:
+ [{obj: {$not: {$exists: true}}}, {
+ $and: [
+ {obj: {$_internalSchemaObjectMatch: {$_internalSchemaMinProperties: 2}}},
+ {obj: {$_internalSchemaType: [3]}}
+ ]
+ }]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
+}
+
+TEST(JSONSchemaObjectKeywordTest, NestedMaxPropertiesTranslatesCorrectlyWithoutRequired) {
+ BSONObj schema = fromjson("{properties: {obj: {type: 'object', maxProperties: 2}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ auto expectedResult = fromjson(R"(
+ {
+ $or:
+ [{obj: {$not: {$exists: true}}}, {
+ $and: [
+ {obj: {$_internalSchemaObjectMatch: {$_internalSchemaMaxProperties: 2}}},
+ {obj: {$_internalSchemaType: [3]}}
+ ]
+ }]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfTypeArrayHasRepeatedAlias) {
+ BSONObj schema = fromjson("{properties: {obj: {type: ['object', 'string', 'object']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfBsonTypeArrayHasRepeatedAlias) {
+ BSONObj schema = fromjson("{properties: {obj: {bsonType: ['object', 'string', 'object']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfTypeArrayIsEmpty) {
+ BSONObj schema = fromjson("{properties: {obj: {type: []}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfBsonTypeArrayIsEmpty) {
+ BSONObj schema = fromjson("{properties: {obj: {bsonType: []}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfTypeArrayContainsNonString) {
+ BSONObj schema = fromjson("{properties: {obj: {type: [1]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfBsonTypeArrayContainsNonString) {
+ BSONObj schema = fromjson("{properties: {obj: {bsonType: [1]}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfTypeArrayContainsUnknownAlias) {
+ BSONObj schema = fromjson("{properties: {obj: {type: ['objectId']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseNicelyIfTypeArrayContainsKnownUnsupportedAlias) {
+ BSONObj schema = fromjson("{properties: {obj: {type: ['number', 'integer']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema type 'integer' is not currently supported");
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfBsonTypeArrayContainsUnknownAlias) {
+ BSONObj schema = fromjson("{properties: {obj: {bsonType: ['unknown']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, CanTranslateTopLevelTypeArrayWithoutObject) {
+ BSONObj schema = fromjson("{type: ['number', 'string']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_SERIALIZES_TO(result.getValue(), BSON(AlwaysFalseMatchExpression::kName << 1));
+}
+
+TEST(JSONSchemaObjectKeywordTest, CanTranslateTopLevelBsonTypeArrayWithoutObject) {
+ BSONObj schema = fromjson("{bsonType: ['number', 'string']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_SERIALIZES_TO(result.getValue(), BSON(AlwaysFalseMatchExpression::kName << 1));
+}
+
+TEST(JSONSchemaObjectKeywordTest, CanTranslateTopLevelTypeArrayWithObject) {
+ BSONObj schema = fromjson("{type: ['number', 'object']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_SERIALIZES_TO(result.getValue(), fromjson("{}"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, CanTranslateTopLevelBsonTypeArrayWithObject) {
+ BSONObj schema = fromjson("{bsonType: ['number', 'object']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_SERIALIZES_TO(result.getValue(), fromjson("{}"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, CanTranslateNestedTypeArray) {
+ BSONObj schema = fromjson("{properties: {a: {type: ['number', 'object']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}}, {a: {$_internalSchemaType: [ "number", 3 ]}}]
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, CanTranslateNestedBsonTypeArray) {
+ BSONObj schema = fromjson("{properties: {a: {bsonType: ['number', 'objectId']}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}}, {a: {$_internalSchemaType: [ "number", 7 ]}}]
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, DependenciesFailsToParseIfNotAnObject) {
+ BSONObj schema = fromjson("{dependencies: []}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, DependenciesFailsToParseIfDependencyIsNotObjectOrArray) {
+ BSONObj schema = fromjson("{dependencies: {a: ['b'], bad: 1}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, DependenciesFailsToParseIfNestedSchemaIsInvalid) {
+ BSONObj schema = fromjson("{dependencies: {a: {invalid: 1}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, PropertyDependencyFailsToParseIfEmptyArray) {
+ BSONObj schema = fromjson("{dependencies: {a: []}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, PropertyDependencyFailsToParseIfArrayContainsNonStringElement) {
+ BSONObj schema = fromjson("{dependencies: {a: ['b', 1]}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, PropertyDependencyFailsToParseIfRepeatedArrayElement) {
+ BSONObj schema = fromjson("{dependencies: {a: ['b', 'b']}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelSchemaDependencyTranslatesCorrectly) {
+ BSONObj schema = fromjson("{dependencies: {a: {properties: {b: {type: 'string'}}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $_internalSchemaCond:
+ [{a: {$exists: true}},
+ {$or: [ {b: {$not: {$exists: true}}}, {b: {$_internalSchemaType: [2]}}]},
+ {$alwaysTrue: 1}]
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelPropertyDependencyTranslatesCorrectly) {
+ BSONObj schema = fromjson("{dependencies: {a: ['b', 'c']}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $_internalSchemaCond: [
+ {a: {$exists: true}},
+ {
+ $and: [
+ {b: {$exists: true}},
+ {c: {$exists: true}}
+ ]
+ },
+ {$alwaysTrue: 1}
+ ]
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, NestedSchemaDependencyTranslatesCorrectly) {
+ BSONObj schema =
+ fromjson("{properties: {a: {dependencies: {b: {properties: {c: {type: 'object'}}}}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{a: {$not: {$exists: true}}}, {
+ $_internalSchemaCond: [
+ {a: {$_internalSchemaObjectMatch : {b: {$exists: true}}}},
+ {
+ $or : [
+ {a: {$not: {$_internalSchemaType: [3]}}},
+ {
+ a: {
+ $_internalSchemaObjectMatch : {
+ $or : [
+ {c: {$not: {$exists: true}}},
+ {c: {$_internalSchemaType: [3]}}
+ ]
+ }
+ }
+ }
+ ]
+ },
+ {$alwaysTrue : 1}
+ ]
+ }]
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, NestedPropertyDependencyTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {a: {dependencies: {b: ['c', 'd']}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ auto expectedResult = fromjson(R"(
+ {
+ $or:
+ [{a: {$not: {$exists: true}}}, {
+ $_internalSchemaCond: [
+ {a: {$_internalSchemaObjectMatch : {b : {$exists : true}}}},
+ {
+ $and: [
+ {a: {$_internalSchemaObjectMatch: {c: {$exists: true}}}},
+ {a: {$_internalSchemaObjectMatch: {d: {$exists: true}}}}
+ ]
+ },
+ {$alwaysTrue: 1}
+ ]
+ }]
+ })");
+ ASSERT_SERIALIZES_TO(optimizedResult, expectedResult);
+}
+
+TEST(JSONSchemaObjectKeywordTest, EmptyDependenciesTranslatesCorrectly) {
+ BSONObj schema = fromjson("{dependencies: {}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_SERIALIZES_TO(result.getValue(), fromjson("{$and: [{}]}"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, UnsupportedKeywordsFailNicely) {
+ auto result =
+ JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{default: {}}"));
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword 'default' is not currently supported");
+
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{definitions: {numberField: {type: 'number'}}}"));
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword 'definitions' is not currently supported");
+
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{format: 'email'}"));
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword 'format' is not currently supported");
+
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{id: 'someschema.json'}"));
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword 'id' is not currently supported");
+
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ BSON("$ref"
+ << "#/definitions/positiveInt"));
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword '$ref' is not currently supported");
+
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{$schema: 'hyper-schema'}"));
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword '$schema' is not currently supported");
+
+ result =
+ JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{$schema: 'http://json-schema.org/draft-04/schema#'}"));
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword '$schema' is not currently supported");
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfDescriptionIsNotAString) {
+ auto result =
+ JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{description: {}}"));
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, CorrectlyParsesDescriptionAsString) {
+ auto result =
+ JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{description: 'str'}"));
+ ASSERT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, CorrectlyParsesNestedDescriptionAsString) {
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{properties: {a: {description: 'str'}}}"));
+ ASSERT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseIfTitleIsNotAString) {
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{title: {}}"));
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, CorrectlyParsesTitleAsString) {
+ auto result =
+ JSONSchemaParser::parse(new ExpressionContextForTest(), fromjson("{title: 'str'}"));
+ ASSERT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, CorrectlyParsesNestedTitleAsString) {
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{properties: {a: {title: 'str'}}}"));
+ ASSERT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, PatternPropertiesFailsToParseIfNotObject) {
+ BSONObj schema = fromjson("{patternProperties: 1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, PatternPropertiesFailsToParseIfOnePropertyIsNotObject) {
+ BSONObj schema = fromjson("{patternProperties: {a: {}, b: 1}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, PatternPropertiesFailsToParseIfNestedSchemaIsInvalid) {
+ BSONObj schema = fromjson("{patternProperties: {a: {invalid: 1}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, PatternPropertiesFailsToParseIfPropertyNameIsAnInvalidRegex) {
+ BSONObj schema = fromjson("{patternProperties: {'[': {}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, AdditionalPropertiesFailsToParseIfNotBoolOrString) {
+ BSONObj schema = fromjson("{additionalProperties: 1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, AdditionalPropertiesFailsToParseIfNestedSchemaIsInvalid) {
+ BSONObj schema = fromjson("{additionalProperties: {invalid: 1}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_NOT_OK(result.getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelPatternPropertiesTranslatesCorrectly) {
+ BSONObj schema =
+ fromjson("{patternProperties: {'^a': {type: 'number'}, '^b': {type: 'string'}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $_internalSchemaAllowedProperties: {
+ properties: [],
+ namePlaceholder: "i",
+ patternProperties: [
+ {
+ regex: /^a/,
+ expression: {
+ i: {$_internalSchemaType: ['number']}
+ }
+ },
+ {
+ regex: /^b/,
+ expression: {
+ i: {$_internalSchemaType: [2]}
+ }
+ }
+ ],
+ otherwise: {$alwaysTrue: 1}
+ }
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelAdditionalPropertiesFalseTranslatesCorrectly) {
+ BSONObj schema = fromjson("{additionalProperties: false}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $_internalSchemaAllowedProperties: {
+ properties: [],
+ namePlaceholder: "i",
+ patternProperties: [],
+ otherwise: {$alwaysFalse: 1}
+ }
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelAdditionalPropertiesTrueTranslatesCorrectly) {
+ BSONObj schema = fromjson("{additionalProperties: true}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $_internalSchemaAllowedProperties: {
+ properties: [],
+ namePlaceholder: "i",
+ patternProperties: [],
+ otherwise: {$alwaysTrue: 1}
+ }
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, TopLevelAdditionalPropertiesTypeNumberTranslatesCorrectly) {
+ BSONObj schema = fromjson("{additionalProperties: {type: 'number'}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $_internalSchemaAllowedProperties: {
+ properties: [],
+ namePlaceholder: "i",
+ patternProperties: [],
+ otherwise: {i: {$_internalSchemaType: ['number']}}
+ }
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, NestedAdditionalPropertiesTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {obj: {additionalProperties: {type: 'number'}}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{obj: {$not: {$exists: true}}}, {obj: {$not: {$_internalSchemaType: [3]}}}, {
+ obj: {
+ $_internalSchemaObjectMatch: {
+ $_internalSchemaAllowedProperties: {
+ properties: [],
+ namePlaceholder: "i",
+ patternProperties: [],
+ otherwise: {i: {$_internalSchemaType: ["number"]}}
+ }
+ }
+ }
+ }]
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest,
+ PropertiesPatternPropertiesAndAdditionalPropertiesTranslateCorrectlyTogether) {
+ BSONObj schema = fromjson(
+ "{properties: {a: {}, b: {}}, patternProperties: {'^c': {}}, additionalProperties: false}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $_internalSchemaAllowedProperties: {
+ properties: ["a", "b"],
+ namePlaceholder: "i",
+ patternProperties: [
+ {regex: /^c/, expression: {}}
+ ],
+ otherwise: {$alwaysFalse: 1}
+ }
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest,
+ PropertiesPatternPropertiesAdditionalPropertiesAndRequiredTranslateCorrectlyTogether) {
+ BSONObj schema = fromjson(
+ "{properties: {a: {}, b: {}}, required: ['a'], patternProperties: {'^c': {}}, "
+ "additionalProperties: false}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $and: [
+ {
+ $_internalSchemaAllowedProperties: {
+ properties: ["a", "b"],
+ namePlaceholder: "i",
+ patternProperties: [
+ {regex: /^c/, expression: {}}
+ ],
+ otherwise: {$alwaysFalse: 1}
+ }
+ },
+ {a: {$exists: true}}
+ ]
+ })"));
+}
+
+TEST(JSONSchemaObjectKeywordTest, CorrectlyIgnoresUnknownKeywordsParameterIsSet) {
+ const auto ignoreUnknownKeywords = true;
+
+ auto schema = fromjson("{ignored_keyword: 1}");
+ ASSERT_OK(JSONSchemaParser::parse(new ExpressionContextForTest(), schema, ignoreUnknownKeywords)
+ .getStatus());
+
+ schema = fromjson("{properties: {a: {ignored_keyword: 1}}}");
+ ASSERT_OK(JSONSchemaParser::parse(new ExpressionContextForTest(), schema, ignoreUnknownKeywords)
+ .getStatus());
+
+ schema = fromjson("{properties: {a: {oneOf: [{ignored_keyword: {}}]}}}");
+ ASSERT_OK(JSONSchemaParser::parse(new ExpressionContextForTest(), schema, ignoreUnknownKeywords)
+ .getStatus());
+}
+
+TEST(JSONSchemaObjectKeywordTest, FailsToParseUnsupportedKeywordsWhenIgnoreUnknownParameterIsSet) {
+ const auto ignoreUnknownKeywords = true;
+
+ auto result = JSONSchemaParser::parse(
+ new ExpressionContextForTest(), fromjson("{default: {}}"), ignoreUnknownKeywords);
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword 'default' is not currently supported");
+
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{definitions: {numberField: {type: 'number'}}}"),
+ ignoreUnknownKeywords);
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword 'definitions' is not currently supported");
+
+ result = JSONSchemaParser::parse(
+ new ExpressionContextForTest(), fromjson("{format: 'email'}"), ignoreUnknownKeywords);
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword 'format' is not currently supported");
+
+ result = JSONSchemaParser::parse(
+ new ExpressionContextForTest(), fromjson("{id: 'someschema.json'}"), ignoreUnknownKeywords);
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword 'id' is not currently supported");
+
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ BSON("$ref"
+ << "#/definitions/positiveInt"),
+ ignoreUnknownKeywords);
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword '$ref' is not currently supported");
+
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{$schema: 'hyper-schema'}"),
+ ignoreUnknownKeywords);
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword '$schema' is not currently supported");
+
+ result =
+ JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{$schema: 'http://json-schema.org/draft-04/schema#'}"),
+ ignoreUnknownKeywords);
+ ASSERT_STRING_CONTAINS(result.getStatus().reason(),
+ "$jsonSchema keyword '$schema' is not currently supported");
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/scalar_keywords_test.cpp b/src/mongo/db/matcher/schema/scalar_keywords_test.cpp
new file mode 100644
index 00000000000..6de666dd3b9
--- /dev/null
+++ b/src/mongo/db/matcher/schema/scalar_keywords_test.cpp
@@ -0,0 +1,383 @@
+/**
+ * Copyright (C) 2019-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/bson/json.h"
+#include "mongo/db/matcher/expression_always_boolean.h"
+#include "mongo/db/matcher/schema/json_schema_parser.h"
+#include "mongo/db/pipeline/expression_context_for_test.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+#define ASSERT_SERIALIZES_TO(match, expected) \
+ do { \
+ BSONObjBuilder bob; \
+ match->serialize(&bob); \
+ ASSERT_BSONOBJ_EQ(bob.obj(), expected); \
+ } while (false)
+
+TEST(JSONSchemaParserScalarTest, MaximumTranslatesCorrectlyWithTypeNumber) {
+ BSONObj schema = fromjson("{properties: {num: {type: 'number', maximum: 0}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult,
+ fromjson("{$or: [{num: {$not: {$exists: true}}}, {$and: [{num: {$lte: "
+ "0}}, {num: {$_internalSchemaType: ['number']}}]}]}"));
+}
+
+TEST(JSONSchemaParserScalarTest, MaximumTranslatesCorrectlyWithBsonTypeLong) {
+ BSONObj schema =
+ fromjson("{properties: {num: {bsonType: 'long', maximum: 0}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult,
+ fromjson("{$or: [{num: {$not: {$exists: true}}}, {$and: [{num: {$lte: "
+ "0}}, {num: {$_internalSchemaType: [18]}}]}]}"));
+}
+
+TEST(JSONSchemaParserScalarTest, MaximumTranslatesCorrectlyWithTypeString) {
+ BSONObj schema = fromjson("{properties: {num: {type: 'string', maximum: 0}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(
+ optimizedResult,
+ fromjson("{$or: [{num: {$not: {$exists: true }}}, {num: {$_internalSchemaType: [2]}}]}"));
+}
+
+TEST(JSONSchemaParserScalarTest, MaximumTranslatesCorrectlyWithNoType) {
+ BSONObj schema = fromjson("{properties: {num: {maximum: 0}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{num: {$not: {$exists : true}}},
+ {num: {$not: {$_internalSchemaType: ["number"]}}},
+ {num: {$lte: 0}}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMaximumIsNotANumber) {
+ BSONObj schema = fromjson("{maximum: 'foo'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMaxLengthIsNotANumber) {
+ BSONObj schema = fromjson("{maxLength: 'foo'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMaxLengthIsLessThanZero) {
+ BSONObj schema = fromjson("{maxLength: -1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserScalarTest, MinimumTranslatesCorrectlyWithTypeNumber) {
+ BSONObj schema = fromjson("{properties: {num: {type: 'number', minimum: 0}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{num: {$not: {$exists : true}}},
+ {$and: [ {num: {$gte: 0}}, {num: {$_internalSchemaType: ["number"]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMaxLengthIsNonIntegralDouble) {
+ BSONObj schema =
+ fromjson("{properties: {foo: {type: 'string', maxLength: 5.5}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserScalarTest, MaxLengthTranslatesCorrectlyWithIntegralDouble) {
+ BSONObj schema =
+ fromjson("{properties: {foo: {type: 'string', maxLength: 5.0}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}},
+ {$and: [ {foo: {$_internalSchemaMaxLength: 5}}, {foo: {$_internalSchemaType: [2]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, MaxLengthTranslatesCorrectlyWithTypeString) {
+ BSONObj schema =
+ fromjson("{properties: {foo: {type: 'string', maxLength: 5}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists : true}}},
+ {$and: [ {foo: {$_internalSchemaMaxLength: 5}}, {foo: {$_internalSchemaType: [2]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, MinimumTranslatesCorrectlyWithBsonTypeLong) {
+ BSONObj schema =
+ fromjson("{properties: {num: {bsonType: 'long', minimum: 0}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{num: {$not: {$exists: true}}},
+ {$and: [ {num: {$gte: 0}}, {num: {$_internalSchemaType: [18]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, MinimumTranslatesCorrectlyWithTypeString) {
+ BSONObj schema = fromjson("{properties: {num: {type: 'string', minimum: 0}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{num: {$not: {$exists: true}}}, {num: {$_internalSchemaType: [2]}}]
+ })"));
+}
+
+
+TEST(JSONSchemaParserScalarTest, MinimumTranslatesCorrectlyWithNoType) {
+ BSONObj schema = fromjson("{properties: {num: {minimum: 0}}}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{num: {$not: {$exists: true}}},
+ {num: {$not: {$_internalSchemaType: ["number"]}}},
+ {num: {$gte: 0}}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, MaximumTranslatesCorrectlyWithExclusiveMaximumTrue) {
+ BSONObj schema = fromjson(
+ "{properties: {num: {bsonType: 'long', maximum: 0, exclusiveMaximum: true}},"
+ "type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{num: {$not: {$exists: true}}},
+ {$and: [ {num: {$lt: 0}}, {num: {$_internalSchemaType: [18]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, MaximumTranslatesCorrectlyWithExclusiveMaximumFalse) {
+ BSONObj schema = fromjson(
+ "{properties: {num: {bsonType: 'long', maximum: 0, exclusiveMaximum: false}},"
+ "type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{num: {$not: {$exists: true}}},
+ {$and: [ {num: {$lte: 0}}, {num: {$_internalSchemaType: [18]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfExclusiveMaximumIsPresentButMaximumIsNot) {
+ BSONObj schema = fromjson("{exclusiveMaximum: true}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfExclusiveMaximumIsNotABoolean) {
+ BSONObj schema = fromjson("{maximum: 5, exclusiveMaximum: 'foo'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserScalarTest, MinimumTranslatesCorrectlyWithExclusiveMinimumTrue) {
+ BSONObj schema = fromjson(
+ "{properties: {num: {bsonType: 'long', minimum: 0, exclusiveMinimum: true}},"
+ "type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{num: {$not: {$exists: true}}},
+ {$and: [ {num: {$gt: 0}}, {num: {$_internalSchemaType: [18]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, MinimumTranslatesCorrectlyWithExclusiveMinimumFalse) {
+ BSONObj schema = fromjson(
+ "{properties: {num: {bsonType: 'long', minimum: 0, exclusiveMinimum: false}},"
+ "type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{num: {$not: {$exists: true}}},
+ {$and: [ {num: {$gte: 0}}, {num: {$_internalSchemaType: [18]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfExclusiveMinimumIsPresentButMinimumIsNot) {
+ BSONObj schema = fromjson("{exclusiveMinimum: true}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfExclusiveMinimumIsNotABoolean) {
+ BSONObj schema = fromjson("{minimum: 5, exclusiveMinimum: 'foo'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMinLengthIsNotANumber) {
+ BSONObj schema = fromjson("{minLength: 'foo'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMinLengthIsLessThanZero) {
+ BSONObj schema = fromjson("{minLength: -1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMinLengthIsNonIntegralDouble) {
+ BSONObj schema =
+ fromjson("{properties: {foo: {type: 'string', minLength: 5.5}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserScalarTest, MinLengthTranslatesCorrectlyWithTypeString) {
+ BSONObj schema =
+ fromjson("{properties: {foo: {type: 'string', minLength: 5}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}},
+ {$and: [ {foo: {$_internalSchemaMinLength: 5}}, {foo: {$_internalSchemaType: [2]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, MinLengthTranslatesCorrectlyWithIntegralDouble) {
+ BSONObj schema =
+ fromjson("{properties: {foo: {type: 'string', minLength: 5.0}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}},
+ {$and: [ {foo: {$_internalSchemaMinLength: 5}}, {foo: {$_internalSchemaType: [2]}}]}]
+ })"));
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMinimumIsNotANumber) {
+ BSONObj schema = fromjson("{minimum: 'foo'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfPatternIsNotString) {
+ BSONObj schema = fromjson("{pattern: 6}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserScalarTest, PatternTranslatesCorrectlyWithString) {
+ BSONObj schema =
+ fromjson("{properties: {foo: {type: 'string', pattern: 'abc'}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ auto expected =
+ BSON("$or" << BSON_ARRAY(
+ BSON("foo" << BSON("$not" << BSON("$exists" << true)))
+ << BSON("$and" << BSON_ARRAY(
+ BSON("foo" << BSON("$regex"
+ << "abc"))
+ << BSON("foo" << BSON("$_internalSchemaType" << BSON_ARRAY(2)))))));
+ ASSERT_SERIALIZES_TO(optimizedResult, expected);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMultipleOfIsNotANumber) {
+ BSONObj schema = fromjson("{multipleOf: 'foo'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMultipleOfIsLessThanZero) {
+ BSONObj schema = fromjson("{multipleOf: -1}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserScalarTest, FailsToParseIfMultipleOfIsZero) {
+ BSONObj schema = fromjson("{multipleOf: 0}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserScalarTest, MultipleOfTranslatesCorrectlyWithTypeNumber) {
+ BSONObj schema = fromjson(
+ "{properties: {foo: {type: 'number', multipleOf: NumberDecimal('5.3')}}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(), schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"({
+ $or:
+ [{foo: {$not: {$exists: true}}}, {
+ $and: [
+ {foo: {$_internalSchemaFmod: [ NumberDecimal('5.3'), 0]}},
+ {foo: {$_internalSchemaType: ["number"]}}
+ ]
+ }]
+ })"));
+}
+
+} // namespace
+} // namespace mongo