Files
mongo/jstests/aggregation/sources/out/unique_key_validation.js

138 lines
6.2 KiB
JavaScript

/**
* 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, 51132);
// Missing "name".
assert.commandWorked(source.update({_id: 0}, {_id: 0, team: "query"}));
assertErrorCode(source, pipelineNameTeam, 51132);
// Missing "team".
assert.commandWorked(source.update({_id: 0}, {_id: 0, name: "nicholas"}));
assertErrorCode(source, pipelineNameTeam, 51132);
// 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, 51132);
// Explicit undefined "song" (a prefix of a "uniqueKey" field).
assert.commandWorked(source.update({_id: 0}, {_id: 0, song: undefined}));
assertErrorCode(source, pipelineSongDotArtist, 51132);
// Explicit null "song.artist".
assert.commandWorked(source.update({_id: 0}, {_id: 0, song: {artist: null}}));
assertErrorCode(source, pipelineSongDotArtist, 51132);
// Explicit undefined "song.artist".
assert.commandWorked(source.update({_id: 0}, {_id: 0, song: {artist: undefined}}));
assertErrorCode(source, pipelineSongDotArtist, 51132);
// 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, 51185);
// "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, 51132);
// 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"}}]);
}());