/**
 * When doing multi-update, we need to ensure that even if a record shows up in the scan multiple
 * times, we only update it once. Note: these tests don't verify the correctness of specific update
 * operators as it's done elsewhere.
 *
 * @tags: [
 *   requires_multi_updates,
 *   requires_non_retryable_writes,
 *   # 'upsert' requires a filter that includes the shard key, which is '_id' but for 'testUpsert()'
 *   # we need to ensure that either collscan or ixscan of a specific index are used.
 *   assumes_unsharded_collection
 * ]
 */

let coll = db.update_multi_halts;

function testUpsert(withIndex) {
    coll.drop();

    // Insert something into the collection, as update on empty collections might be optimized.
    assert.commandWorked(coll.insertMany([{key: 0, cUpdates: 0}]));
    if (withIndex) {
        assert.commandWorked(coll.createIndex({key: 1}));
    }

    let options = {upsert: true};
    if (withIndex) {
        // Make sure the index is used.
        options.hint = {key: 1};
    }

    // The upserted record matches the filter but shouldn't be re-updated.
    assert.commandWorked(
        coll.updateMany({key: {$gt: 0}}, {$set: {key: 1}, $inc: {cUpdates: 1}}, options));

    assert.eq(2, coll.count(), "Count of documents in the collection after upsert");
    assert.eq(1, coll.count({key: 1}), "Count of documents with the new key");
    assert.eq(1, coll.find({key: 1}).toArray()[0].cUpdates, "Number of updates on the target doc");
}
testUpsert(false /* use collscan for the filter */);
testUpsert(true /* use ixscan for the filter */);

(function testCollscanNotInplaceUpdate() {
    coll.drop();

    assert.commandWorked(coll.insert([{key: 1, cUpdates: 0}]));

    // The record that cannot be updated in-place (because adding a new field) should not show up
    // again during the scan.
    assert.commandWorked(coll.updateMany({key: 1}, {$set: {new: 42}, $inc: {cUpdates: 1}}));

    assert.eq(1, coll.count(), "Count of documents in the collection after update");
    assert.eq(1, coll.find().toArray()[0].cUpdates, "Number of updates on the target doc");
})();

(function testIxscanUpdate() {
    coll.drop();

    coll.insert({key: 0, cUpdates: 0});
    coll.createIndex({key: 1});

    // The update of 'key' pushes the updated record "down" the index so it will reappear in the
    // index scan.
    assert.commandWorked(coll.updateMany({key: {$gte: 0}},                // filter
                                         {$inc: {key: 10, cUpdates: 1}},  // update
                                         {hint: {key: 1}}));              // options

    assert.eq(1, coll.count(), "Count of documents in the collection after update");
    assert.eq(1, coll.find().toArray()[0].cUpdates, "Number of updates on the target doc");
})();