// Tests for $merge with a non-existing target collection.
(function() {
"use strict";

load("jstests/aggregation/extras/merge_helpers.js");  // For withEachMergeMode.
load("jstests/aggregation/extras/utils.js");          // For assertErrorCode.

const st = new ShardingTest({shards: 2, rs: {nodes: 1}});
const sourceDB = st.s0.getDB("source_db");

/**
 * Run an aggregation on 'sourceColl' that writes documents to 'targetColl' with $merge.
 */
function testMerge(sourceColl, targetColl, shardedSource) {
    sourceColl.drop();

    if (shardedSource) {
        st.shardColl(sourceColl, {_id: 1}, {_id: 0}, {_id: 1}, sourceDB.getName());
    }

    for (let i = 0; i < 10; i++) {
        assert.commandWorked(sourceColl.insert({_id: i}));
    }

    // Test the behavior for each of the $merge modes. Since the target collection does not
    // exist, the behavior should be identical.
    withEachMergeMode(({whenMatchedMode, whenNotMatchedMode}) => {
        // Skip the combination of merge modes which will fail depending on the contents of the
        // source and target collection, as this will cause the assertion below to trip.
        if (whenMatchedMode == "fail" || whenNotMatchedMode == "fail")
            return;

        targetColl.drop();
        sourceColl.aggregate([{
            $merge: {
                into: {db: targetColl.getDB().getName(), coll: targetColl.getName()},
                whenMatched: whenMatchedMode,
                whenNotMatched: whenNotMatchedMode,
                on: "_id"
            }
        }]);
        assert.eq(whenNotMatchedMode == "discard" ? 0 : 10, targetColl.find().itcount());
    });

    // Test that $merge fails if the "on" field is anything but "_id" when the target collection
    // does not exist.
    withEachMergeMode(({whenMatchedMode, whenNotMatchedMode}) => {
        // Skip the combination of merge modes which will fail depending on the contents of the
        // source and target collection, as this will cause the assertion below to trip.
        if (whenMatchedMode == "fail" || whenNotMatchedMode == "fail")
            return;

        targetColl.drop();
        assertErrorCode(
            sourceColl,
            [{
                $merge: {
                    into: {db: targetColl.getDB().getName(), coll: targetColl.getName()},
                    whenMatched: whenMatchedMode,
                    whenNotMatched: whenNotMatchedMode,
                    on: "not_allowed"
                }
            }],
            51190);
    });

    // If 'targetColl' is in the same database as 'sourceColl', test that the legacy $out works
    // correctly.
    if (targetColl.getDB() == sourceColl.getDB()) {
        jsTestLog(`Testing $out from ${sourceColl.getFullName()} ` +
                  `(${shardedSource ? "sharded" : "unsharded"}) to ${targetColl.getFullName()} ` +
                  `with legacy syntax`);

        targetColl.drop();
        sourceColl.aggregate([{$out: targetColl.getName()}]);
        assert.eq(10, targetColl.find().itcount());
    }
}

const sourceColl = sourceDB["source_coll"];
const outputCollSameDb = sourceDB["output_coll"];

// Test $merge from an unsharded source collection to a non-existent output collection in the
// same database.
testMerge(sourceColl, outputCollSameDb, false);

// Like the last test case, but perform a $merge from a sharded source collection to a
// non-existent output collection in the same database.
testMerge(sourceColl, outputCollSameDb, true);

// Test that $merge in a sharded cluster fails when the output is sent to a different database
// that doesn't exist.
const foreignDb = st.s0.getDB("foreign_db");
const outputCollDiffDb = foreignDb["output_coll"];
foreignDb.dropDatabase();
assert.throws(() => testMerge(sourceColl, outputCollDiffDb, false));
assert.throws(() => testMerge(sourceColl, outputCollDiffDb, true));

// Test $merge from an unsharded source collection to an output collection in a different
// database where the database exists but the collection does not.
assert.commandWorked(foreignDb["test"].insert({_id: "forcing database creation"}));
testMerge(sourceColl, outputCollDiffDb, false);

// Like the last test, but with a sharded source collection.
testMerge(sourceColl, outputCollDiffDb, true);
st.stop();
}());
