diff options
author | Kyle Suarez <kyle.suarez@mongodb.com> | 2017-09-11 10:22:52 -0400 |
---|---|---|
committer | Kyle Suarez <kyle.suarez@mongodb.com> | 2017-09-11 10:22:58 -0400 |
commit | 8244b17489f7f6d7daae9920de0fb1ef12e271fc (patch) | |
tree | d32903263a580dff542258e3ac7db4854ed79d8e | |
parent | 1db33f3a391f5f92a898ab5c7a7cc6246d4e36b3 (diff) | |
download | mongo-8244b17489f7f6d7daae9920de0fb1ef12e271fc.tar.gz |
SERVER-30178 extend JSON Schema parser to handle "uniqueItems"
-rw-r--r-- | buildscripts/resmokeconfig/suites/json_schema.yml | 1 | ||||
-rw-r--r-- | jstests/core/json_schema/array_keywords.js | 19 | ||||
-rw-r--r-- | src/mongo/db/matcher/schema/json_schema_parser.cpp | 22 | ||||
-rw-r--r-- | src/mongo/db/matcher/schema/json_schema_parser_test.cpp | 69 |
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 |