summaryrefslogtreecommitdiff
path: root/jstests/aggregation
diff options
context:
space:
mode:
authorKyle Suarez <kyle.suarez@mongodb.com>2018-09-11 15:35:22 -0400
committerKyle Suarez <kyle.suarez@mongodb.com>2018-09-11 15:35:22 -0400
commit602bc97e78d99d22d7ae6b867b6e92f5e2e279ca (patch)
tree1730701a6a89b0d3060ff69c0e2b90ef09d1dafb /jstests/aggregation
parent597c8ef841424814dbf2f5dca75a012a88dfac4e (diff)
downloadmongo-602bc97e78d99d22d7ae6b867b6e92f5e2e279ca.tar.gz
SERVER-36367 validation for $out "uniqueKey"
Diffstat (limited to 'jstests/aggregation')
-rw-r--r--jstests/aggregation/sources/out/unique_key_requires_index.js1
-rw-r--r--jstests/aggregation/sources/out/unique_key_validation.js137
2 files changed, 137 insertions, 1 deletions
diff --git a/jstests/aggregation/sources/out/unique_key_requires_index.js b/jstests/aggregation/sources/out/unique_key_requires_index.js
index 4e183a2d0bc..a2410cbcb93 100644
--- a/jstests/aggregation/sources/out/unique_key_requires_index.js
+++ b/jstests/aggregation/sources/out/unique_key_requires_index.js
@@ -77,7 +77,6 @@
assertUniqueKeyIsInvalid({uniqueKey: {a: 1, b: 1}, targetColl: target.getName()});
assertUniqueKeyIsInvalid({uniqueKey: {b: 1}, targetColl: target.getName()});
assertUniqueKeyIsInvalid({uniqueKey: {a: 1}, targetColl: target.getName()});
- assertUniqueKeyIsInvalid({uniqueKey: {}, targetColl: target.getName()});
assert.commandWorked(target.dropIndex({a: 1, _id: 1}));
assert.commandWorked(target.createIndex({a: 1}, {unique: true}));
diff --git a/jstests/aggregation/sources/out/unique_key_validation.js b/jstests/aggregation/sources/out/unique_key_validation.js
new file mode 100644
index 00000000000..9af0486f7dd
--- /dev/null
+++ b/jstests/aggregation/sources/out/unique_key_validation.js
@@ -0,0 +1,137 @@
+/**
+ * Tests for the validation of the "uniqueKey" at parse-time of the "uniqueKey" specification
+ * itself, as well as during runtime extraction of the "uniqueKey" from documents in the aggregation
+ * pipeline.
+ *
+ * This test creates unique indexes on various combinations of fields, so it cannot be run in suites
+ * that implicitly shard the collection with a hashed shard key.
+ * @tags: [cannot_create_unique_index_when_using_hashed_shard_key]
+ */
+(function() {
+ "use strict";
+
+ load("jstests/aggregation/extras/utils.js"); // For assertErrorCode.
+
+ const source = db.unique_key_validation_source;
+ const target = db.unique_key_validation_target;
+
+ [source, target].forEach(coll => coll.drop());
+ assert.commandWorked(source.insert({_id: 0}));
+
+ //
+ // Tests for invalid "uniqueKey" specifications.
+ //
+ function assertUniqueKeyIsInvalid(uniqueKey, expectedErrorCode) {
+ const stage = {
+ $out: {to: target.getName(), mode: "replaceDocuments", uniqueKey: uniqueKey}
+ };
+ assertErrorCode(source, stage, expectedErrorCode);
+ }
+
+ // A non-object "uniqueKey" is prohibited.
+ assertUniqueKeyIsInvalid(3.14, ErrorCodes.TypeMismatch);
+ assertUniqueKeyIsInvalid("_id", ErrorCodes.TypeMismatch);
+
+ // Explicitly specifying an empty-object "uniqueKey" is invalid.
+ assertUniqueKeyIsInvalid({}, ErrorCodes.InvalidOptions);
+
+ // The "uniqueKey" won't be accepted if any field is not a number.
+ assertUniqueKeyIsInvalid({name: "hashed"}, ErrorCodes.TypeMismatch);
+ assertUniqueKeyIsInvalid({x: 1, y: 1, z: [1]}, ErrorCodes.TypeMismatch);
+ assertUniqueKeyIsInvalid({nested: {field: 1}}, ErrorCodes.TypeMismatch);
+ assertUniqueKeyIsInvalid({uniqueKey: true}, ErrorCodes.TypeMismatch);
+ assertUniqueKeyIsInvalid({string: "true"}, ErrorCodes.TypeMismatch);
+ assertUniqueKeyIsInvalid({bool: false}, ErrorCodes.TypeMismatch);
+
+ // A numerical "uniqueKey" won't be accepted if any field isn't exactly the value 1.
+ assertUniqueKeyIsInvalid({_id: -1}, ErrorCodes.BadValue);
+ assertUniqueKeyIsInvalid({x: 10}, ErrorCodes.BadValue);
+
+ // Test that the value 1 represented as different numerical types will be accepred.
+ [1.0, NumberInt(1), NumberLong(1), NumberDecimal(1)].forEach(one => {
+ assert.commandWorked(target.remove({}));
+ assert.doesNotThrow(
+ () => source.aggregate(
+ {$out: {to: target.getName(), mode: "replaceDocuments", uniqueKey: {_id: one}}}));
+ assert.eq(target.find().toArray(), [{_id: 0}]);
+ });
+
+ //
+ // An error is raised if $out encounters a document that is missing one or more of the
+ // "uniqueKey" fields.
+ //
+ assert.commandWorked(target.remove({}));
+ assert.commandWorked(target.createIndex({name: 1, team: -1}, {unique: true}));
+ const pipelineNameTeam =
+ [{$out: {to: target.getName(), mode: "replaceDocuments", uniqueKey: {name: 1, team: 1}}}];
+
+ // Missing both "name" and "team".
+ assertErrorCode(source, pipelineNameTeam, 50905);
+
+ // Missing "name".
+ assert.commandWorked(source.update({_id: 0}, {_id: 0, team: "query"}));
+ assertErrorCode(source, pipelineNameTeam, 50905);
+
+ // Missing "team".
+ assert.commandWorked(source.update({_id: 0}, {_id: 0, name: "nicholas"}));
+ assertErrorCode(source, pipelineNameTeam, 50905);
+
+ // A document with both "name" and "team" will be accepted.
+ assert.commandWorked(source.update({_id: 0}, {_id: 0, name: "nicholas", team: "query"}));
+ assert.doesNotThrow(() => source.aggregate(pipelineNameTeam));
+ assert.eq(target.find().toArray(), [{_id: 0, name: "nicholas", team: "query"}]);
+
+ //
+ // An error is raised if $out encounters a document where one of the "uniqueKey" fields is a
+ // nullish value.
+ //
+ assert.commandWorked(target.remove({}));
+ assert.commandWorked(target.createIndex({"song.artist": 1}, {unique: 1}));
+ const pipelineSongDotArtist =
+ [{$out: {to: target.getName(), mode: "replaceDocuments", uniqueKey: {"song.artist": 1}}}];
+
+ // Explicit null "song" (a prefix of a "uniqueKey" field).
+ assert.commandWorked(source.update({_id: 0}, {_id: 0, song: null}));
+ assertErrorCode(source, pipelineSongDotArtist, 50905);
+
+ // Explicit undefined "song" (a prefix of a "uniqueKey" field).
+ assert.commandWorked(source.update({_id: 0}, {_id: 0, song: undefined}));
+ assertErrorCode(source, pipelineSongDotArtist, 50905);
+
+ // Explicit null "song.artist".
+ assert.commandWorked(source.update({_id: 0}, {_id: 0, song: {artist: null}}));
+ assertErrorCode(source, pipelineSongDotArtist, 50905);
+
+ // Explicit undefined "song.artist".
+ assert.commandWorked(source.update({_id: 0}, {_id: 0, song: {artist: undefined}}));
+ assertErrorCode(source, pipelineSongDotArtist, 50905);
+
+ // A valid "artist" will be accepted.
+ assert.commandWorked(source.update({_id: 0}, {_id: 0, song: {artist: "Illenium"}}));
+ assert.doesNotThrow(() => source.aggregate(pipelineSongDotArtist));
+ assert.eq(target.find().toArray(), [{_id: 0, song: {artist: "Illenium"}}]);
+
+ //
+ // An error is raised if $out encounters a document where one of the "uniqueKey" fields (or a
+ // prefix of a "uniqueKey" field) is an array.
+ //
+ assert.commandWorked(target.remove({}));
+ assert.commandWorked(target.createIndex({"address.street": 1}, {unique: 1}));
+ const pipelineAddressDotStreet = [
+ {$out: {to: target.getName(), mode: "replaceDocuments", uniqueKey: {"address.street": 1}}}
+ ];
+
+ // "address.street" is an array.
+ assert.commandWorked(
+ source.update({_id: 0}, {_id: 0, address: {street: ["West 43rd St", "1633 Broadway"]}}));
+ assertErrorCode(source, pipelineAddressDotStreet, 50943);
+
+ // "address" is an array (a prefix of a "uniqueKey" field).
+ assert.commandWorked(source.update({_id: 0}, {_id: 0, address: [{street: "1633 Broadway"}]}));
+ assertErrorCode(source, pipelineAddressDotStreet, 50905);
+
+ // A scalar "address.street" is accepted.
+ assert.commandWorked(source.update({_id: 0}, {_id: 0, address: {street: "1633 Broadway"}}));
+ assert.doesNotThrow(() => source.aggregate(pipelineAddressDotStreet));
+ assert.eq(target.find().toArray(), [{_id: 0, address: {street: "1633 Broadway"}}]);
+}());