summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyle Suarez <kyle.suarez@mongodb.com>2017-09-11 10:22:52 -0400
committerKyle Suarez <kyle.suarez@mongodb.com>2017-09-11 10:22:58 -0400
commit8244b17489f7f6d7daae9920de0fb1ef12e271fc (patch)
treed32903263a580dff542258e3ac7db4854ed79d8e
parent1db33f3a391f5f92a898ab5c7a7cc6246d4e36b3 (diff)
downloadmongo-8244b17489f7f6d7daae9920de0fb1ef12e271fc.tar.gz
SERVER-30178 extend JSON Schema parser to handle "uniqueItems"
-rw-r--r--buildscripts/resmokeconfig/suites/json_schema.yml1
-rw-r--r--jstests/core/json_schema/array_keywords.js19
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser.cpp22
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser_test.cpp69
4 files changed, 109 insertions, 2 deletions
diff --git a/buildscripts/resmokeconfig/suites/json_schema.yml b/buildscripts/resmokeconfig/suites/json_schema.yml
index 04c7a6b77a8..00cf6c863ea 100644
--- a/buildscripts/resmokeconfig/suites/json_schema.yml
+++ b/buildscripts/resmokeconfig/suites/json_schema.yml
@@ -25,7 +25,6 @@ selector:
- src/third_party/JSON-Schema-Test-Suite/tests/draft4/ref.json
- src/third_party/JSON-Schema-Test-Suite/tests/draft4/refRemote.json
- src/third_party/JSON-Schema-Test-Suite/tests/draft4/type.json
- - src/third_party/JSON-Schema-Test-Suite/tests/draft4/uniqueItems.json
executor:
config:
diff --git a/jstests/core/json_schema/array_keywords.js b/jstests/core/json_schema/array_keywords.js
index 9faf0275226..4f6194dd67b 100644
--- a/jstests/core/json_schema/array_keywords.js
+++ b/jstests/core/json_schema/array_keywords.js
@@ -46,4 +46,23 @@
[{_id: 0}, {_id: 1}, {_id: 4}]);
assertFindResultsSortedEq({$jsonSchema: {properties: {a: {maxItems: 2}}}},
[{_id: 0}, {_id: 1}, {_id: 2}, {_id: 4}]);
+
+ // Test that the JSON Schema fails to parse if "uniqueItems" is not a boolean.
+ assert.throws(() => coll.find({$jsonSchema: {uniqueItems: 1}}).itcount());
+ assert.throws(() => coll.find({$jsonSchema: {uniqueItems: 1.0}}).itcount());
+ assert.throws(() => coll.find({$jsonSchema: {uniqueItems: "true"}}).itcount());
+
+ // Test that "uniqueItems" only enforces uniqueness when specified.
+ assertFindResultsSortedEq({$jsonSchema: {uniqueItems: true}},
+ [{_id: 0}, {_id: 1}, {_id: 2}, {_id: 3}, {_id: 4}]);
+ assertFindResultsSortedEq({$jsonSchema: {properties: {a: {uniqueItems: false}}}},
+ [{_id: 0}, {_id: 1}, {_id: 2}, {_id: 3}, {_id: 4}]);
+ assertFindResultsSortedEq({$jsonSchema: {properties: {a: {uniqueItems: true}}}},
+ [{_id: 0}, {_id: 1}, {_id: 2}, {_id: 4}]);
+
+ // TODO (SERVER-30178): uniqueItems should compare objects in a field order-insensitive way when
+ // testing for uniqueness.
+ assert.writeOK(coll.insert({_id: 5, a: [{x: 1, y: 1}, {y: 1, x: 1}]}));
+ assertFindResultsSortedEq({$jsonSchema: {properties: {a: {uniqueItems: true}}}},
+ [{_id: 0}, {_id: 1}, {_id: 2}, {_id: 4}, {_id: 5}]);
}());
diff --git a/src/mongo/db/matcher/schema/json_schema_parser.cpp b/src/mongo/db/matcher/schema/json_schema_parser.cpp
index 535c674e710..a69f78dd24d 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser.cpp
@@ -907,6 +907,25 @@ Status translateArrayKeywords(StringMap<BSONElement>* keywordMap,
andExpr->add(maxItemsExpr.getValue().release());
}
+ if (auto uniqueItemsElt = keywordMap->get(kSchemaUniqueItemsKeyword)) {
+ if (!uniqueItemsElt.isBoolean()) {
+ return {ErrorCodes::TypeMismatch,
+ str::stream() << "$jsonSchema keyword '" << kSchemaUniqueItemsKeyword
+ << "' must be a boolean"};
+ } else if (path.empty()) {
+ andExpr->add(stdx::make_unique<AlwaysTrueMatchExpression>().release());
+ } else if (uniqueItemsElt.boolean()) {
+ auto uniqueItemsExpr = stdx::make_unique<InternalSchemaUniqueItemsMatchExpression>();
+ auto status = uniqueItemsExpr->init(path);
+ if (!status.isOK()) {
+ return status;
+ }
+ andExpr->add(
+ makeRestriction(BSONType::Array, path, std::move(uniqueItemsExpr), typeExpr)
+ .release());
+ }
+ }
+
return Status::OK();
}
@@ -1135,8 +1154,8 @@ StatusWithMatchExpression _parse(StringData path, BSONObj schema) {
{kSchemaAllOfKeyword, {}},
{kSchemaAnyOfKeyword, {}},
{kSchemaBsonTypeKeyword, {}},
- {kSchemaDescriptionKeyword, {}},
{kSchemaDependenciesKeyword, {}},
+ {kSchemaDescriptionKeyword, {}},
{kSchemaExclusiveMaximumKeyword, {}},
{kSchemaExclusiveMinimumKeyword, {}},
{kSchemaMaxItemsKeyword, {}},
@@ -1156,6 +1175,7 @@ StatusWithMatchExpression _parse(StringData path, BSONObj schema) {
{kSchemaRequiredKeyword, {}},
{kSchemaTitleKeyword, {}},
{kSchemaTypeKeyword, {}},
+ {kSchemaUniqueItemsKeyword, {}},
};
for (auto&& elt : schema) {
diff --git a/src/mongo/db/matcher/schema/json_schema_parser_test.cpp b/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
index 96c882a59c2..fca370cdeae 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
@@ -1825,5 +1825,74 @@ TEST(JSONSchemaParserTest,
)"));
}
+TEST(JSONSchemaParserTest, FailsToParseIfUniqueItemsIsNotABoolean) {
+ auto schema = BSON("uniqueItems" << 1);
+ ASSERT_EQ(JSONSchemaParser::parse(schema).getStatus(), ErrorCodes::TypeMismatch);
+}
+
+TEST(JSONSchemaParserTest, NoMatchExpressionGeneratedIfUniqueItemsFalse) {
+ auto schema = fromjson("{properties: {a: {uniqueItems: false}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ ASSERT_SERIALIZES_TO(result.getValue().get(), fromjson(R"(
+ {$and: [
+ {$and: [
+ {$or: [
+ {$nor: [{a: {$exists: true}}]},
+ {}
+ ]}
+ ]}
+ ]})"));
+}
+
+TEST(JSONSchemaParserTest, UniqueItemsTranslatesCorrectlyWithNoType) {
+ auto schema = BSON("uniqueItems" << true);
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ ASSERT_SERIALIZES_TO(result.getValue().get(), fromjson("{$and: [{$alwaysTrue: 1}]}"));
+
+ schema = fromjson("{properties: {a: {uniqueItems: true}}}");
+ result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ ASSERT_SERIALIZES_TO(result.getValue().get(), fromjson(R"(
+ {$and:
+ [{
+ $and : [ {
+ $or : [
+ {$nor : [ {a : {$exists : true}} ]},
+ {
+ $and : [ {
+ $or : [
+ {$nor : [ {a : {$_internalSchemaType : [4]}} ]},
+ {a : {$_internalSchemaUniqueItems : true}}
+ ]
+ } ]
+ }
+ ]
+ } ]
+ }]
+ })"));
+}
+
+TEST(JSONSchemaParserTest, UniqueItemsTranslatesCorrectlyWithTypeArray) {
+ auto schema = fromjson("{properties: {a: {type: 'array', uniqueItems: true}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ ASSERT_SERIALIZES_TO(result.getValue().get(), fromjson(R"(
+ {$and: [{
+ $and: [{
+ $or: [
+ {$nor: [{a: {$exists: true}}]},
+ {
+ $and: [
+ {a: {$_internalSchemaUniqueItems: true}},
+ {a: {$_internalSchemaType: [4]}}
+ ]
+ }
+ ]
+ }]
+ }]
+ })"));
+}
} // namespace
} // namespace mongo