summaryrefslogtreecommitdiff
path: root/jstests/aggregation/sources/merge/mode_merge_insert.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/aggregation/sources/merge/mode_merge_insert.js')
-rw-r--r--jstests/aggregation/sources/merge/mode_merge_insert.js711
1 files changed, 354 insertions, 357 deletions
diff --git a/jstests/aggregation/sources/merge/mode_merge_insert.js b/jstests/aggregation/sources/merge/mode_merge_insert.js
index 370963a24d2..577479f7a46 100644
--- a/jstests/aggregation/sources/merge/mode_merge_insert.js
+++ b/jstests/aggregation/sources/merge/mode_merge_insert.js
@@ -4,368 +4,365 @@
// exists when none is expected.
// @tags: [assumes_no_implicit_collection_creation_after_drop]
(function() {
- "use strict";
-
- load("jstests/aggregation/extras/utils.js"); // For assertArrayEq.
- load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers.isMongos.
-
- const source = db[`${jsTest.name()}_source`];
- source.drop();
- const target = db[`${jsTest.name()}_target`];
- target.drop();
- const mergeStage = {
- $merge: {into: target.getName(), whenMatched: "merge", whenNotMatched: "insert"}
- };
- const pipeline = [mergeStage];
-
- // Test $merge into a non-existent collection.
- (function testMergeIntoNonExistentCollection() {
- assert.commandWorked(source.insert({_id: 1, a: 1, b: "a"}));
- assert.doesNotThrow(() => source.aggregate(pipeline));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [
- {_id: 1, a: 1, b: "a"},
- ]
- });
- })();
-
- // Test $merge into an existing collection.
- (function testMergeIntoExistentCollection() {
- assert.commandWorked(source.insert({_id: 2, a: 2, b: "b"}));
- assert.doesNotThrow(() => source.aggregate(pipeline));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [{_id: 1, a: 1, b: "a"}, {_id: 2, a: 2, b: "b"}]
- });
- })();
-
- // Test $merge does not update documents in the target collection if they were not modified
- // in the source collection.
- (function testMergeDoesNotUpdateUnmodifiedDocuments() {
- assert.doesNotThrow(() => source.aggregate(pipeline));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [{_id: 1, a: 1, b: "a"}, {_id: 2, a: 2, b: "b"}]
- });
- })();
-
- // Test $merge updates documents in the target collection if they were modified in the source
- // collection.
- (function testMergeUpdatesModifiedDocuments() {
- // Update and merge a single document.
- assert.commandWorked(source.update({_id: 2}, {a: 22, c: "c"}));
- assert.doesNotThrow(() => source.aggregate(pipeline));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [{_id: 1, a: 1, b: "a"}, {_id: 2, a: 22, b: "b", c: "c"}]
- });
-
- // Update and merge multiple documents.
- assert.commandWorked(source.update({_id: 1}, {a: 11}));
- assert.commandWorked(source.update({_id: 2}, {a: 22, c: "c", d: "d"}));
- assert.doesNotThrow(() => source.aggregate(pipeline));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [{_id: 1, a: 11, b: "a"}, {_id: 2, a: 22, b: "b", c: "c", d: "d"}]
- });
- })();
-
- // Test $merge inserts a new document into the target collection if it was inserted into the
- // source collection.
- (function testMergeInsertsNewDocument() {
- // Insert and merge a single document.
- assert.commandWorked(source.insert({_id: 3, a: 3, b: "c"}));
- assert.doesNotThrow(() => source.aggregate(pipeline));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [
- {_id: 1, a: 11, b: "a"},
- {_id: 2, a: 22, b: "b", c: "c", d: "d"},
- {_id: 3, a: 3, b: "c"}
- ]
- });
- assert.commandWorked(source.deleteOne({_id: 3}));
- assert.commandWorked(target.deleteOne({_id: 3}));
-
- // Insert and merge multiple documents.
- assert.commandWorked(source.insert({_id: 3, a: 3, b: "c"}));
- assert.commandWorked(source.insert({_id: 4, a: 4, c: "d"}));
- assert.doesNotThrow(() => source.aggregate(pipeline));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [
- {_id: 1, a: 11, b: "a"},
- {_id: 2, a: 22, b: "b", c: "c", d: "d"},
- {_id: 3, a: 3, b: "c"},
- {_id: 4, a: 4, c: "d"}
- ]
- });
- assert.commandWorked(source.deleteMany({_id: {$in: [3, 4]}}));
- assert.commandWorked(target.deleteMany({_id: {$in: [3, 4]}}));
- })();
-
- // Test $merge doesn't modify the target collection if a document has been removed from the
- // source collection.
- (function testMergeDoesNotUpdateDeletedDocument() {
- assert.commandWorked(source.deleteOne({_id: 1}));
- assert.doesNotThrow(() => source.aggregate(pipeline));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [
- {_id: 1, a: 11, b: "a"},
- {_id: 2, a: 22, b: "b", c: "c", d: "d"},
- ]
- });
- })();
-
- // Test $merge fails if a unique index constraint in the target collection is violated.
- (function testMergeFailsIfTargetUniqueKeyIsViolated() {
- if (FixtureHelpers.isSharded(source)) {
- // Skip this test if the collection sharded, because an implicitly created sharded
- // key of {_id: 1} will not be covered by a unique index created in this test, which
- // is not allowed.
- return;
- }
-
- assert(source.drop());
- assert.commandWorked(source.insert({_id: 4, a: 11}));
- assert.commandWorked(target.createIndex({a: 1}, {unique: true}));
- const error = assert.throws(() => source.aggregate(pipeline));
- assert.commandFailedWithCode(error, ErrorCodes.DuplicateKey);
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [
- {_id: 1, a: 11, b: "a"},
- {_id: 2, a: 22, b: "b", c: "c", d: "d"},
- ]
- });
- assert.commandWorked(target.dropIndex({a: 1}));
- })();
-
- // Test $merge fails if it cannot find an index to verify that the 'on' fields will be unique.
- (function testMergeFailsIfOnFieldCannotBeVerifiedForUniquness() {
- // The 'on' fields contains a single document field.
- let error =
- assert.throws(() => source.aggregate(
- [{$merge: Object.assign({on: "nonexistent"}, mergeStage.$merge)}]));
- assert.commandFailedWithCode(error, [51190, 51183]);
-
- // The 'on' fields contains multiple document fields.
- error = assert.throws(() => source.aggregate([
- {$merge: Object.assign({on: ["nonexistent1", "nonexistent2"]}, mergeStage.$merge)}
- ]));
- assert.commandFailedWithCode(error, [51190, 51183]);
- })();
-
- // Test $merge with an explicit 'on' field over a single or multiple document fields which
- // differ from the _id field.
- (function testMergeWithOnFields() {
- if (FixtureHelpers.isSharded(source)) {
- // Skip this test if the collection sharded, because an implicitly created sharded
- // key of {_id: 1} will not be covered by a unique index created in this test, which
- // is not allowed.
- return;
- }
-
- // The 'on' fields contains a single document field.
- assert(source.drop());
- assert(target.drop());
- assert.commandWorked(source.createIndex({a: 1}, {unique: true}));
- assert.commandWorked(target.createIndex({a: 1}, {unique: true}));
- assert.commandWorked(source.insert(
- [{_id: 1, a: 1, b: "a"}, {_id: 2, a: 2, b: "b"}, {_id: 3, a: 30, b: "c"}]));
- assert.commandWorked(target.insert(
- [{_id: 1, a: 1, c: "x"}, {_id: 4, a: 30, c: "y"}, {_id: 5, a: 40, c: "z"}]));
- assert.doesNotThrow(
- () => source.aggregate(
- [{$project: {_id: 0}}, {$merge: Object.assign({on: "a"}, mergeStage.$merge)}]));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [
- {_id: 1, a: 1, b: "a", c: "x"},
- {_id: 2, a: 2, b: "b"},
- {_id: 4, a: 30, b: "c", c: "y"},
- {_id: 5, a: 40, c: "z"}
- ]
- });
-
- // The 'on' fields contains multiple document fields.
- assert(source.drop());
- assert(target.drop());
- assert.commandWorked(source.createIndex({a: 1, b: 1}, {unique: true}));
- assert.commandWorked(target.createIndex({a: 1, b: 1}, {unique: true}));
- assert.commandWorked(source.insert(
- [{_id: 1, a: 1, b: "a", c: "x"}, {_id: 2, a: 2, b: "b"}, {_id: 3, a: 30, b: "c"}]));
- assert.commandWorked(target.insert(
- [{_id: 1, a: 1, b: "a"}, {_id: 4, a: 30, b: "c", c: "y"}, {_id: 5, a: 40, c: "z"}]));
- assert.doesNotThrow(() => source.aggregate([
- {$project: {_id: 0}},
- {$merge: Object.assign({on: ["a", "b"]}, mergeStage.$merge)}
- ]));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [
- {_id: 1, a: 1, b: "a", c: "x"},
- {_id: 2, a: 2, b: "b"},
- {_id: 4, a: 30, b: "c", c: "y"},
- {_id: 5, a: 40, c: "z"}
- ]
- });
- assert.commandWorked(source.dropIndex({a: 1, b: 1}));
- assert.commandWorked(target.dropIndex({a: 1, b: 1}));
- })();
-
- // Test $merge with a dotted path in the 'on' field.
- (function testMergeWithDottedOnField() {
- if (FixtureHelpers.isSharded(source)) {
- // Skip this test if the collection sharded, because an implicitly created sharded
- // key of {_id: 1} will not be covered by a unique index created in this test, which
- // is not allowed.
- return;
- }
-
- assert(source.drop());
- assert(target.drop());
- assert.commandWorked(source.createIndex({"a.b": 1}, {unique: true}));
- assert.commandWorked(target.createIndex({"a.b": 1}, {unique: true}));
- assert.commandWorked(source.insert([
+"use strict";
+
+load("jstests/aggregation/extras/utils.js"); // For assertArrayEq.
+load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers.isMongos.
+
+const source = db[`${jsTest.name()}_source`];
+source.drop();
+const target = db[`${jsTest.name()}_target`];
+target.drop();
+const mergeStage = {
+ $merge: {into: target.getName(), whenMatched: "merge", whenNotMatched: "insert"}
+};
+const pipeline = [mergeStage];
+
+// Test $merge into a non-existent collection.
+(function testMergeIntoNonExistentCollection() {
+ assert.commandWorked(source.insert({_id: 1, a: 1, b: "a"}));
+ assert.doesNotThrow(() => source.aggregate(pipeline));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [
+ {_id: 1, a: 1, b: "a"},
+ ]
+ });
+})();
+
+// Test $merge into an existing collection.
+(function testMergeIntoExistentCollection() {
+ assert.commandWorked(source.insert({_id: 2, a: 2, b: "b"}));
+ assert.doesNotThrow(() => source.aggregate(pipeline));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [{_id: 1, a: 1, b: "a"}, {_id: 2, a: 2, b: "b"}]
+ });
+})();
+
+// Test $merge does not update documents in the target collection if they were not modified
+// in the source collection.
+(function testMergeDoesNotUpdateUnmodifiedDocuments() {
+ assert.doesNotThrow(() => source.aggregate(pipeline));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [{_id: 1, a: 1, b: "a"}, {_id: 2, a: 2, b: "b"}]
+ });
+})();
+
+// Test $merge updates documents in the target collection if they were modified in the source
+// collection.
+(function testMergeUpdatesModifiedDocuments() {
+ // Update and merge a single document.
+ assert.commandWorked(source.update({_id: 2}, {a: 22, c: "c"}));
+ assert.doesNotThrow(() => source.aggregate(pipeline));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [{_id: 1, a: 1, b: "a"}, {_id: 2, a: 22, b: "b", c: "c"}]
+ });
+
+ // Update and merge multiple documents.
+ assert.commandWorked(source.update({_id: 1}, {a: 11}));
+ assert.commandWorked(source.update({_id: 2}, {a: 22, c: "c", d: "d"}));
+ assert.doesNotThrow(() => source.aggregate(pipeline));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [{_id: 1, a: 11, b: "a"}, {_id: 2, a: 22, b: "b", c: "c", d: "d"}]
+ });
+})();
+
+// Test $merge inserts a new document into the target collection if it was inserted into the
+// source collection.
+(function testMergeInsertsNewDocument() {
+ // Insert and merge a single document.
+ assert.commandWorked(source.insert({_id: 3, a: 3, b: "c"}));
+ assert.doesNotThrow(() => source.aggregate(pipeline));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [
+ {_id: 1, a: 11, b: "a"},
+ {_id: 2, a: 22, b: "b", c: "c", d: "d"},
+ {_id: 3, a: 3, b: "c"}
+ ]
+ });
+ assert.commandWorked(source.deleteOne({_id: 3}));
+ assert.commandWorked(target.deleteOne({_id: 3}));
+
+ // Insert and merge multiple documents.
+ assert.commandWorked(source.insert({_id: 3, a: 3, b: "c"}));
+ assert.commandWorked(source.insert({_id: 4, a: 4, c: "d"}));
+ assert.doesNotThrow(() => source.aggregate(pipeline));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [
+ {_id: 1, a: 11, b: "a"},
+ {_id: 2, a: 22, b: "b", c: "c", d: "d"},
+ {_id: 3, a: 3, b: "c"},
+ {_id: 4, a: 4, c: "d"}
+ ]
+ });
+ assert.commandWorked(source.deleteMany({_id: {$in: [3, 4]}}));
+ assert.commandWorked(target.deleteMany({_id: {$in: [3, 4]}}));
+})();
+
+// Test $merge doesn't modify the target collection if a document has been removed from the
+// source collection.
+(function testMergeDoesNotUpdateDeletedDocument() {
+ assert.commandWorked(source.deleteOne({_id: 1}));
+ assert.doesNotThrow(() => source.aggregate(pipeline));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [
+ {_id: 1, a: 11, b: "a"},
+ {_id: 2, a: 22, b: "b", c: "c", d: "d"},
+ ]
+ });
+})();
+
+// Test $merge fails if a unique index constraint in the target collection is violated.
+(function testMergeFailsIfTargetUniqueKeyIsViolated() {
+ if (FixtureHelpers.isSharded(source)) {
+ // Skip this test if the collection sharded, because an implicitly created sharded
+ // key of {_id: 1} will not be covered by a unique index created in this test, which
+ // is not allowed.
+ return;
+ }
+
+ assert(source.drop());
+ assert.commandWorked(source.insert({_id: 4, a: 11}));
+ assert.commandWorked(target.createIndex({a: 1}, {unique: true}));
+ const error = assert.throws(() => source.aggregate(pipeline));
+ assert.commandFailedWithCode(error, ErrorCodes.DuplicateKey);
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [
+ {_id: 1, a: 11, b: "a"},
+ {_id: 2, a: 22, b: "b", c: "c", d: "d"},
+ ]
+ });
+ assert.commandWorked(target.dropIndex({a: 1}));
+})();
+
+// Test $merge fails if it cannot find an index to verify that the 'on' fields will be unique.
+(function testMergeFailsIfOnFieldCannotBeVerifiedForUniquness() {
+ // The 'on' fields contains a single document field.
+ let error = assert.throws(
+ () => source.aggregate([{$merge: Object.assign({on: "nonexistent"}, mergeStage.$merge)}]));
+ assert.commandFailedWithCode(error, [51190, 51183]);
+
+ // The 'on' fields contains multiple document fields.
+ error = assert.throws(
+ () => source.aggregate(
+ [{$merge: Object.assign({on: ["nonexistent1", "nonexistent2"]}, mergeStage.$merge)}]));
+ assert.commandFailedWithCode(error, [51190, 51183]);
+})();
+
+// Test $merge with an explicit 'on' field over a single or multiple document fields which
+// differ from the _id field.
+(function testMergeWithOnFields() {
+ if (FixtureHelpers.isSharded(source)) {
+ // Skip this test if the collection sharded, because an implicitly created sharded
+ // key of {_id: 1} will not be covered by a unique index created in this test, which
+ // is not allowed.
+ return;
+ }
+
+ // The 'on' fields contains a single document field.
+ assert(source.drop());
+ assert(target.drop());
+ assert.commandWorked(source.createIndex({a: 1}, {unique: true}));
+ assert.commandWorked(target.createIndex({a: 1}, {unique: true}));
+ assert.commandWorked(
+ source.insert([{_id: 1, a: 1, b: "a"}, {_id: 2, a: 2, b: "b"}, {_id: 3, a: 30, b: "c"}]));
+ assert.commandWorked(
+ target.insert([{_id: 1, a: 1, c: "x"}, {_id: 4, a: 30, c: "y"}, {_id: 5, a: 40, c: "z"}]));
+ assert.doesNotThrow(
+ () => source.aggregate(
+ [{$project: {_id: 0}}, {$merge: Object.assign({on: "a"}, mergeStage.$merge)}]));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [
+ {_id: 1, a: 1, b: "a", c: "x"},
+ {_id: 2, a: 2, b: "b"},
+ {_id: 4, a: 30, b: "c", c: "y"},
+ {_id: 5, a: 40, c: "z"}
+ ]
+ });
+
+ // The 'on' fields contains multiple document fields.
+ assert(source.drop());
+ assert(target.drop());
+ assert.commandWorked(source.createIndex({a: 1, b: 1}, {unique: true}));
+ assert.commandWorked(target.createIndex({a: 1, b: 1}, {unique: true}));
+ assert.commandWorked(source.insert(
+ [{_id: 1, a: 1, b: "a", c: "x"}, {_id: 2, a: 2, b: "b"}, {_id: 3, a: 30, b: "c"}]));
+ assert.commandWorked(target.insert(
+ [{_id: 1, a: 1, b: "a"}, {_id: 4, a: 30, b: "c", c: "y"}, {_id: 5, a: 40, c: "z"}]));
+ assert.doesNotThrow(
+ () => source.aggregate(
+ [{$project: {_id: 0}}, {$merge: Object.assign({on: ["a", "b"]}, mergeStage.$merge)}]));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [
+ {_id: 1, a: 1, b: "a", c: "x"},
+ {_id: 2, a: 2, b: "b"},
+ {_id: 4, a: 30, b: "c", c: "y"},
+ {_id: 5, a: 40, c: "z"}
+ ]
+ });
+ assert.commandWorked(source.dropIndex({a: 1, b: 1}));
+ assert.commandWorked(target.dropIndex({a: 1, b: 1}));
+})();
+
+// Test $merge with a dotted path in the 'on' field.
+(function testMergeWithDottedOnField() {
+ if (FixtureHelpers.isSharded(source)) {
+ // Skip this test if the collection sharded, because an implicitly created sharded
+ // key of {_id: 1} will not be covered by a unique index created in this test, which
+ // is not allowed.
+ return;
+ }
+
+ assert(source.drop());
+ assert(target.drop());
+ assert.commandWorked(source.createIndex({"a.b": 1}, {unique: true}));
+ assert.commandWorked(target.createIndex({"a.b": 1}, {unique: true}));
+ assert.commandWorked(source.insert([
+ {_id: 1, a: {b: "b"}, c: "x"},
+ {_id: 2, a: {b: "c"}, c: "y"},
+ {_id: 3, a: {b: 30}, b: "c"}
+ ]));
+ assert.commandWorked(target.insert({_id: 2, a: {b: "c"}}));
+ assert.doesNotThrow(
+ () => source.aggregate(
+ [{$project: {_id: 0}}, {$merge: Object.assign({on: "a.b"}, mergeStage.$merge)}]));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [
{_id: 1, a: {b: "b"}, c: "x"},
{_id: 2, a: {b: "c"}, c: "y"},
{_id: 3, a: {b: 30}, b: "c"}
- ]));
- assert.commandWorked(target.insert({_id: 2, a: {b: "c"}}));
- assert.doesNotThrow(
- () => source.aggregate(
- [{$project: {_id: 0}}, {$merge: Object.assign({on: "a.b"}, mergeStage.$merge)}]));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [
- {_id: 1, a: {b: "b"}, c: "x"},
- {_id: 2, a: {b: "c"}, c: "y"},
- {_id: 3, a: {b: 30}, b: "c"}
- ]
- });
- })();
-
- // Test $merge fails if the value of the 'on' field in a document is invalid, e.g. missing,
- // null or an array.
- (function testMergeFailsIfOnFieldIsInvalid() {
- if (FixtureHelpers.isSharded(source)) {
- // Skip this test if the collection sharded, because an implicitly created sharded
- // key of {_id: 1} will not be covered by a unique index created in this test, which
- // is not allowed.
- return;
- }
-
- assert(source.drop());
- assert(target.drop());
- assert.commandWorked(source.createIndex({"z": 1}, {unique: true}));
- assert.commandWorked(target.createIndex({"z": 1}, {unique: true}));
-
- // The 'on' field is missing.
- assert.commandWorked(source.insert({_id: 1}));
- let error = assert.throws(
- () => source.aggregate(
- [{$project: {_id: 0}}, {$merge: Object.assign({on: "z"}, mergeStage.$merge)}]));
- assert.commandFailedWithCode(error, 51132);
-
- // The 'on' field is null.
- assert.commandWorked(source.update({_id: 1}, {z: null}));
- error = assert.throws(
- () => source.aggregate(
- [{$project: {_id: 0}}, {$merge: Object.assign({on: "z"}, mergeStage.$merge)}]));
- assert.commandFailedWithCode(error, 51132);
-
- // The 'on' field is an array.
- assert.commandWorked(source.update({_id: 1}, {z: [1, 2]}));
- error = assert.throws(
- () => source.aggregate(
- [{$project: {_id: 0}}, {$merge: Object.assign({on: "z"}, mergeStage.$merge)}]));
- assert.commandFailedWithCode(error, 51185);
- })();
-
- // Test $merge when the _id field is removed from the aggregate projection but is used in the
- // $merge's 'on' field.
- (function testMergeWhenDocIdIsRemovedFromProjection() {
- // The _id is a single 'on' field (a default one).
- assert(source.drop());
- assert(target.drop());
- assert.commandWorked(source.insert([{_id: 1, a: 1, b: "a"}, {_id: 2, a: 2, b: "b"}]));
- assert.commandWorked(target.insert({_id: 1, b: "c"}));
- assert.doesNotThrow(() => source.aggregate([{$project: {_id: 0}}, mergeStage]));
- assertArrayEq({
- actual: target.find({}, {_id: 0}).toArray(),
- expected: [{b: "c"}, {a: 1, b: "a"}, {a: 2, b: "b"}]
- });
-
- // The _id is part of the compound 'on' field.
- assert(target.drop());
- assert.commandWorked(target.insert({_id: 1, b: "c"}));
- assert.commandWorked(source.createIndex({_id: 1, a: -1}, {unique: true}));
- assert.commandWorked(target.createIndex({_id: 1, a: -1}, {unique: true}));
- assert.doesNotThrow(() => source.aggregate([
- {$project: {_id: 0}},
- {$merge: Object.assign({on: ["_id", "a"]}, mergeStage.$merge)}
- ]));
- assertArrayEq({
- actual: target.find({}, {_id: 0}).toArray(),
- expected: [{b: "c"}, {a: 1, b: "a"}, {a: 2, b: "b"}]
- });
- assert.commandWorked(source.dropIndex({_id: 1, a: -1}));
- assert.commandWorked(target.dropIndex({_id: 1, a: -1}));
- })();
-
- // Test $merge preserves indexes and options of the existing target collection.
- (function testMergePresrvesIndexesAndOptions() {
- const validator = {a: {$gt: 0}};
- assert(target.drop());
- assert.commandWorked(db.createCollection(target.getName(), {validator: validator}));
- assert.commandWorked(target.createIndex({a: 1}));
- assert.doesNotThrow(() => source.aggregate(pipeline));
- assertArrayEq({
- actual: target.find().toArray(),
- expected: [{_id: 1, a: 1, b: "a"}, {_id: 2, a: 2, b: "b"}]
- });
- assert.eq(2, target.getIndexes().length);
-
- const listColl = db.runCommand({listCollections: 1, filter: {name: target.getName()}});
- assert.commandWorked(listColl);
- assert.eq(validator, listColl.cursor.firstBatch[0].options["validator"]);
- })();
-
- // Test $merge implicitly creates a new database when the target collection's database doesn't
- // exist.
- (function testMergeImplicitlyCreatesTargetDatabase() {
- assert(source.drop());
- assert.commandWorked(source.insert({_id: 1, a: 1, b: "a"}));
-
- const foreignDb = db.getSiblingDB(`${jsTest.name()}_foreign_db`);
- assert.commandWorked(foreignDb.dropDatabase());
- const foreignTarget = foreignDb[`${jsTest.name()}_target`];
- const foreignPipeline = [{
- $merge: {
- into: {db: foreignDb.getName(), coll: foreignTarget.getName()},
- whenMatched: "merge",
- whenNotMatched: "insert"
- }
- }];
-
- if (!FixtureHelpers.isMongos(db)) {
- assert.doesNotThrow(() => source.aggregate(foreignPipeline));
- assertArrayEq(
- {actual: foreignTarget.find().toArray(), expected: [{_id: 1, a: 1, b: "a"}]});
- } else {
- // Implicit database creation is prohibited in a cluster.
- const error = assert.throws(() => source.aggregate(foreignPipeline));
- assert.commandFailedWithCode(error, ErrorCodes.NamespaceNotFound);
-
- // Force a creation of the database and collection, then fall through the test below.
- assert.commandWorked(foreignTarget.insert({_id: 1, a: 1}));
+ ]
+ });
+})();
+
+// Test $merge fails if the value of the 'on' field in a document is invalid, e.g. missing,
+// null or an array.
+(function testMergeFailsIfOnFieldIsInvalid() {
+ if (FixtureHelpers.isSharded(source)) {
+ // Skip this test if the collection sharded, because an implicitly created sharded
+ // key of {_id: 1} will not be covered by a unique index created in this test, which
+ // is not allowed.
+ return;
+ }
+
+ assert(source.drop());
+ assert(target.drop());
+ assert.commandWorked(source.createIndex({"z": 1}, {unique: true}));
+ assert.commandWorked(target.createIndex({"z": 1}, {unique: true}));
+
+ // The 'on' field is missing.
+ assert.commandWorked(source.insert({_id: 1}));
+ let error = assert.throws(
+ () => source.aggregate(
+ [{$project: {_id: 0}}, {$merge: Object.assign({on: "z"}, mergeStage.$merge)}]));
+ assert.commandFailedWithCode(error, 51132);
+
+ // The 'on' field is null.
+ assert.commandWorked(source.update({_id: 1}, {z: null}));
+ error = assert.throws(
+ () => source.aggregate(
+ [{$project: {_id: 0}}, {$merge: Object.assign({on: "z"}, mergeStage.$merge)}]));
+ assert.commandFailedWithCode(error, 51132);
+
+ // The 'on' field is an array.
+ assert.commandWorked(source.update({_id: 1}, {z: [1, 2]}));
+ error = assert.throws(
+ () => source.aggregate(
+ [{$project: {_id: 0}}, {$merge: Object.assign({on: "z"}, mergeStage.$merge)}]));
+ assert.commandFailedWithCode(error, 51185);
+})();
+
+// Test $merge when the _id field is removed from the aggregate projection but is used in the
+// $merge's 'on' field.
+(function testMergeWhenDocIdIsRemovedFromProjection() {
+ // The _id is a single 'on' field (a default one).
+ assert(source.drop());
+ assert(target.drop());
+ assert.commandWorked(source.insert([{_id: 1, a: 1, b: "a"}, {_id: 2, a: 2, b: "b"}]));
+ assert.commandWorked(target.insert({_id: 1, b: "c"}));
+ assert.doesNotThrow(() => source.aggregate([{$project: {_id: 0}}, mergeStage]));
+ assertArrayEq({
+ actual: target.find({}, {_id: 0}).toArray(),
+ expected: [{b: "c"}, {a: 1, b: "a"}, {a: 2, b: "b"}]
+ });
+
+ // The _id is part of the compound 'on' field.
+ assert(target.drop());
+ assert.commandWorked(target.insert({_id: 1, b: "c"}));
+ assert.commandWorked(source.createIndex({_id: 1, a: -1}, {unique: true}));
+ assert.commandWorked(target.createIndex({_id: 1, a: -1}, {unique: true}));
+ assert.doesNotThrow(() => source.aggregate([
+ {$project: {_id: 0}},
+ {$merge: Object.assign({on: ["_id", "a"]}, mergeStage.$merge)}
+ ]));
+ assertArrayEq({
+ actual: target.find({}, {_id: 0}).toArray(),
+ expected: [{b: "c"}, {a: 1, b: "a"}, {a: 2, b: "b"}]
+ });
+ assert.commandWorked(source.dropIndex({_id: 1, a: -1}));
+ assert.commandWorked(target.dropIndex({_id: 1, a: -1}));
+})();
+
+// Test $merge preserves indexes and options of the existing target collection.
+(function testMergePresrvesIndexesAndOptions() {
+ const validator = {a: {$gt: 0}};
+ assert(target.drop());
+ assert.commandWorked(db.createCollection(target.getName(), {validator: validator}));
+ assert.commandWorked(target.createIndex({a: 1}));
+ assert.doesNotThrow(() => source.aggregate(pipeline));
+ assertArrayEq({
+ actual: target.find().toArray(),
+ expected: [{_id: 1, a: 1, b: "a"}, {_id: 2, a: 2, b: "b"}]
+ });
+ assert.eq(2, target.getIndexes().length);
+
+ const listColl = db.runCommand({listCollections: 1, filter: {name: target.getName()}});
+ assert.commandWorked(listColl);
+ assert.eq(validator, listColl.cursor.firstBatch[0].options["validator"]);
+})();
+
+// Test $merge implicitly creates a new database when the target collection's database doesn't
+// exist.
+(function testMergeImplicitlyCreatesTargetDatabase() {
+ assert(source.drop());
+ assert.commandWorked(source.insert({_id: 1, a: 1, b: "a"}));
+
+ const foreignDb = db.getSiblingDB(`${jsTest.name()}_foreign_db`);
+ assert.commandWorked(foreignDb.dropDatabase());
+ const foreignTarget = foreignDb[`${jsTest.name()}_target`];
+ const foreignPipeline = [{
+ $merge: {
+ into: {db: foreignDb.getName(), coll: foreignTarget.getName()},
+ whenMatched: "merge",
+ whenNotMatched: "insert"
}
+ }];
+ if (!FixtureHelpers.isMongos(db)) {
assert.doesNotThrow(() => source.aggregate(foreignPipeline));
assertArrayEq({actual: foreignTarget.find().toArray(), expected: [{_id: 1, a: 1, b: "a"}]});
- assert.commandWorked(foreignDb.dropDatabase());
- })();
+ } else {
+ // Implicit database creation is prohibited in a cluster.
+ const error = assert.throws(() => source.aggregate(foreignPipeline));
+ assert.commandFailedWithCode(error, ErrorCodes.NamespaceNotFound);
+
+ // Force a creation of the database and collection, then fall through the test below.
+ assert.commandWorked(foreignTarget.insert({_id: 1, a: 1}));
+ }
+
+ assert.doesNotThrow(() => source.aggregate(foreignPipeline));
+ assertArrayEq({actual: foreignTarget.find().toArray(), expected: [{_id: 1, a: 1, b: "a"}]});
+ assert.commandWorked(foreignDb.dropDatabase());
+})();
}());