Files
mongo/jstests/core/query/update/update_hint.js
Joshua Siegel 169f8dc283 SERVER-111817 Add tag to exclude tests from timeseries CRUD suite (#44113)
GitOrigin-RevId: a3ab3b89275fa89c9824f6af467d56d605096159
2025-11-21 18:08:16 +00:00

183 lines
6.1 KiB
JavaScript

/**
* Tests passing hint to the update command:
* - A bad argument to the hint option should raise an error.
* - The hint option should support both the name of the index, and the object spec of the
* index.
*
* @tags: [
* assumes_unsharded_collection,
* requires_multi_updates,
* requires_non_retryable_writes,
* # Ignore because the find command is rewritten for TS collections before reaching the failpoint.
* exclude_from_timeseries_crud_passthrough,
* ]
*/
import {getPlanStage} from "jstests/libs/query/analyze_plan.js";
function assertCommandUsesIndex(command, expectedHintKeyPattern) {
const out = assert.commandWorked(coll.runCommand({explain: command}));
const planStage = getPlanStage(out, "IXSCAN");
assert.neq(null, planStage);
assert.eq(planStage.keyPattern, expectedHintKeyPattern, tojson(planStage));
}
const coll = db[jsTestName()];
function normalIndexTest() {
// Hint using a key pattern.
coll.drop();
assert.commandWorked(coll.insert({x: 1, y: 1}));
assert.commandWorked(coll.createIndex({x: 1}));
assert.commandWorked(coll.createIndex({y: -1}));
// Hint using index key pattern.
let updateCmd = {
update: coll.getName(),
updates: [{q: {x: 1}, u: {$set: {y: 1}}, hint: {x: 1}}],
};
assertCommandUsesIndex(updateCmd, {x: 1});
// Hint using an index name.
updateCmd = {update: coll.getName(), updates: [{q: {x: 1}, u: {$set: {y: 1}}, hint: "y_-1"}]};
assertCommandUsesIndex(updateCmd, {y: -1});
// Passing a hint should not use the idhack fast-path.
updateCmd = {update: coll.getName(), updates: [{q: {_id: 1}, u: {$set: {y: 1}}, hint: "y_-1"}]};
assertCommandUsesIndex(updateCmd, {y: -1});
}
function sparseIndexTest() {
// Create a sparse index with 2 documents.
coll.drop();
assert.commandWorked(coll.insert([{x: 1}, {x: 1}, {x: 1, s: 0}, {x: 1, s: 0}]));
assert.commandWorked(coll.createIndex({x: 1}));
assert.commandWorked(coll.createIndex({s: 1}, {sparse: true}));
// Hint should be respected, even on incomplete indexes.
let updateCmd = {
update: coll.getName(),
updates: [{q: {_id: 1}, u: {$set: {y: 1}}, hint: {s: 1}}],
};
assertCommandUsesIndex(updateCmd, {s: 1});
// Update hinting a sparse index updates only the document in the sparse index.
updateCmd = {
update: coll.getName(),
updates: [{q: {}, u: {$set: {s: 1}}, hint: {s: 1}, multi: true}],
};
assert.commandWorked(coll.runCommand(updateCmd));
assert.eq(2, coll.count({s: 1}));
// Update hinting a sparse index with upsert option can result in an insert even if the
// correct behaviour would be to update an existing document.
assert.commandWorked(coll.insert({x: 2}));
updateCmd = {
update: coll.getName(),
updates: [{q: {x: 2}, u: {$set: {x: 1}}, hint: {s: 1}, upsert: true}],
};
let res = assert.commandWorked(coll.runCommand(updateCmd));
assert.eq(res.upserted.length, 1);
}
function shellHelpersTest() {
coll.drop();
assert.commandWorked(coll.insert([{x: 1}, {x: 1, s: 0}, {x: 1, s: 0}]));
assert.commandWorked(coll.createIndex({x: 1}));
assert.commandWorked(coll.createIndex({s: 1}, {sparse: true}));
// Test shell helpers using a hinted sparse index should only update documents that exist in
// the sparse index.
let res = coll.update({x: 1}, {$set: {y: 2}}, {hint: {s: 1}, multi: true});
assert.eq(res.nMatched, 2);
// Insert document that will not be in the sparse index. Update hinting sparse index should
// result in upsert.
assert.commandWorked(coll.insert({x: 2}));
res = coll.updateOne({x: 2}, {$set: {y: 2}}, {hint: {s: 1}, upsert: true});
assert(res.upsertedId);
res = coll.updateMany({x: 1}, {$set: {y: 2}}, {hint: {s: 1}});
assert.eq(res.matchedCount, 2);
// Test bulk writes.
let bulk = coll.initializeUnorderedBulkOp();
bulk.find({x: 1})
.hint({s: 1})
.update({$set: {y: 1}});
res = bulk.execute();
assert.eq(res.nMatched, 2);
bulk = coll.initializeUnorderedBulkOp();
bulk.find({x: 2})
.hint({s: 1})
.upsert()
.updateOne({$set: {y: 1}});
res = bulk.execute();
assert.eq(res.nUpserted, 1);
bulk = coll.initializeUnorderedBulkOp();
bulk.find({x: 2})
.hint({s: 1})
.upsert()
.replaceOne({$set: {y: 1}});
res = bulk.execute();
assert.eq(res.nUpserted, 1);
res = coll.bulkWrite([
{
updateOne: {
filter: {x: 2},
update: {$set: {y: 2}},
hint: {s: 1},
upsert: true,
},
},
]);
assert.eq(res.upsertedCount, 1);
res = coll.bulkWrite([
{
updateMany: {
filter: {x: 1},
update: {$set: {y: 2}},
hint: {s: 1},
},
},
]);
assert.eq(res.matchedCount, 2);
res = coll.bulkWrite([
{
replaceOne: {
filter: {x: 2},
replacement: {x: 2, y: 3},
hint: {s: 1},
upsert: true,
},
},
]);
assert.eq(res.upsertedCount, 1);
}
function failedHintTest() {
coll.drop();
assert.commandWorked(coll.insert({x: 1}));
assert.commandWorked(coll.createIndex({x: 1}));
// Command should fail with incorrectly formatted hints.
let updateCmd = {update: coll.getName(), updates: [{q: {_id: 1}, u: {$set: {y: 1}}, hint: 1}]};
assert.commandFailedWithCode(coll.runCommand(updateCmd), ErrorCodes.FailedToParse);
updateCmd = {update: coll.getName(), updates: [{q: {_id: 1}, u: {$set: {y: 1}}, hint: true}]};
assert.commandFailedWithCode(coll.runCommand(updateCmd), ErrorCodes.FailedToParse);
// Command should fail with hints to non-existent indexes.
updateCmd = {
update: coll.getName(),
updates: [{q: {_id: 1}, u: {$set: {y: 1}}, hint: {badHint: 1}}],
};
assert.commandFailedWithCode(coll.runCommand(updateCmd), ErrorCodes.BadValue);
}
normalIndexTest();
sparseIndexTest();
shellHelpersTest();
failedHintTest();