0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-11-27 23:27:11 +01:00
mongodb/jstests/sharding/internal_txns/retryable_writes_oplog_entries.js
Matt Broadstone 771dabd098 SERVER-81339 Convert ReplSetTest and ShardingTest to modules (#26332)
GitOrigin-RevId: 744aa110a53786b23c62ff53f87a1418b5991e8d
2024-08-20 22:00:49 +00:00

228 lines
8.6 KiB
JavaScript

/*
* Tests that the stmtIds for write statements in an internal transaction for retryable writes
* are stored in the individual operation entries in the applyOps oplog entry for the transaction.
*
* Exclude this test from large_txn variants because the variant enforces that the max transaction
* oplog entry length is 2 operations, and the oplog length assertions in this test do not account
* for this. We are not losing test coverage as this test inherently tests large transactionss.
*
* @tags: [requires_fcv_60, uses_transactions, exclude_from_large_txns]
*/
import {ShardingTest} from "jstests/libs/shardingtest.js";
import {
getOplogEntriesForTxn,
makeCommitTransactionCmdObj,
makePrepareTransactionCmdObj,
} from "jstests/sharding/libs/sharded_transactions_helpers.js";
// This test requires running prepareTransaction and commitTransaction directly against the shard.
TestData.replicaSetEndpointIncompatible = true;
const kDbName = "testDb";
const kCollName = "testColl";
const st = new ShardingTest({shards: 1});
const mongosTestDB = st.s.getDB(kDbName);
const mongosTestColl = mongosTestDB.getCollection(kCollName);
const kStmtIdsOption = {
isComplete: 1,
isIncomplete: 2,
isRepeated: 3
};
/*
* Returns an array of NumberInts (i.e. stmtId type) based on the specified option:
* - If option is 'isComplete', returns [NumberInt(0), ..., NumberInt((numStmtIds-1)*10)].
* - If option is 'isIncomplete', returns [NumberInt(-1), NumberInt(1), ...,
* NumberInt(numStmtIds-1)].
* - If option is 'isRepeated', returns [NumberInt(1), ..., NumberInt(1)].
*/
function makeCustomStmtIdsForTest(numStmtIds, option) {
switch (option) {
case kStmtIdsOption.isComplete:
return [...Array(numStmtIds).keys()].map(i => NumberInt(i * 10));
case kStmtIdsOption.isIncomplete: {
let stmtIds = [...Array(numStmtIds).keys()];
stmtIds[0] = -1;
return stmtIds.map(i => NumberInt(i));
}
case kStmtIdsOption.isRepeated:
return Array(numStmtIds).fill(1).map(i => NumberInt(i));
}
}
function verifyOplogEntries(cmdObj,
lsid,
txnNumber,
numWriteStatements,
{shouldStoreStmtIds, customStmtIdsOption, isPreparedTransaction}) {
const writeCmdObj = Object.assign(cmdObj, {
lsid: lsid,
txnNumber: NumberLong(txnNumber),
startTransaction: true,
autocommit: false,
});
let stmtIds = null;
if (customStmtIdsOption) {
stmtIds = makeCustomStmtIdsForTest(numWriteStatements, customStmtIdsOption);
writeCmdObj.stmtIds = stmtIds;
}
const commitCmdObj = makeCommitTransactionCmdObj(lsid, txnNumber);
const writeRes = mongosTestDB.runCommand(writeCmdObj);
if (customStmtIdsOption == kStmtIdsOption.isRepeated) {
assert.commandFailedWithCode(writeRes, 5875600);
assert.commandWorked(mongosTestColl.remove({}));
return;
}
assert.commandWorked(writeRes);
if (isPreparedTransaction) {
const shard0Primary = st.rs0.getPrimary();
const prepareCmdObj = makePrepareTransactionCmdObj(lsid, txnNumber);
const isPreparedTransactionRes =
assert.commandWorked(shard0Primary.adminCommand(prepareCmdObj));
commitCmdObj.commitTimestamp = isPreparedTransactionRes.prepareTimestamp;
assert.commandWorked(shard0Primary.adminCommand(commitCmdObj));
}
assert.commandWorked(mongosTestDB.adminCommand(commitCmdObj));
const oplogEntries = getOplogEntriesForTxn(st.rs0, lsid, txnNumber);
assert.eq(oplogEntries.length, isPreparedTransaction ? 2 : 1, oplogEntries);
const applyOpsOplogEntry = oplogEntries[0];
assert(!applyOpsOplogEntry.hasOwnProperty("stmtId"));
const operations = applyOpsOplogEntry.o.applyOps;
operations.forEach((operation, index) => {
if (shouldStoreStmtIds) {
const operationStmtId = stmtIds ? stmtIds[index] : index;
if (operationStmtId == -1) {
// Uninitialized stmtIds should be ignored.
assert(!operation.hasOwnProperty("stmtId"), operation);
} else {
assert.eq(operation.stmtId, operationStmtId, operation);
}
} else {
assert(!operation.hasOwnProperty("stmtId"), operation);
}
});
if (isPreparedTransaction) {
const commitOplogEntry = oplogEntries[1];
assert(!commitOplogEntry.hasOwnProperty("stmtId"));
}
assert.commandWorked(mongosTestColl.remove({}));
}
function testInserts(lsid, txnNumber, testOptions) {
jsTest.log("Test batched inserts");
const insertCmdObj = {
insert: kCollName,
documents: [{_id: 0, x: 0}, {_id: 1, x: 1}],
};
verifyOplogEntries(insertCmdObj, lsid, txnNumber, insertCmdObj.documents.length, testOptions);
}
function testUpdates(lsid, txnNumber, testOptions) {
jsTest.log("Test batched updates");
assert.commandWorked(mongosTestColl.insert([{_id: 0, x: 0}, {_id: 1, x: 1}]));
const updateCmdObj = {
update: kCollName,
updates: [
{q: {_id: 0, x: 0}, u: {$inc: {x: -10}}},
{q: {_id: 1, x: 1}, u: {$inc: {x: 10}}},
],
};
verifyOplogEntries(updateCmdObj, lsid, txnNumber, updateCmdObj.updates.length, testOptions);
}
function testDeletes(lsid, txnNumber, testOptions) {
jsTest.log("Test batched deletes");
assert.commandWorked(mongosTestColl.insert([{_id: 0, x: 0}, {_id: 1, x: 1}]));
const deleteCmdObj = {
delete: kCollName,
deletes: [
{q: {_id: 0, x: 0}, limit: 1},
{q: {_id: 1, x: 1}, limit: 1},
],
};
verifyOplogEntries(deleteCmdObj, lsid, txnNumber, deleteCmdObj.deletes.length, testOptions);
}
{
jsTest.log("Test that oplog entries for non-internal transactions do not have stmtIds");
const lsid = {id: UUID()};
let txnNumber = 0;
const testOptions = {shouldStoreStmtIds: false};
testInserts(lsid, txnNumber++, testOptions);
testUpdates(lsid, txnNumber++, testOptions);
testDeletes(lsid, txnNumber++, testOptions);
}
{
jsTest.log(
"Test that oplog entries for non-retryable internal transactions do not have stmtIds");
const lsid = {id: UUID(), txnUUID: UUID()};
let txnNumber = 0;
const testOptions = {shouldStoreStmtIds: false};
testInserts(lsid, txnNumber++, testOptions);
testUpdates(lsid, txnNumber++, testOptions);
testDeletes(lsid, txnNumber++, testOptions);
}
{
jsTest.log("Test that oplog entries for retryable internal transactions have stmtIds");
const lsid = {id: UUID(), txnNumber: NumberLong(0), txnUUID: UUID()};
let txnNumber = 0;
let runTests = ({isPreparedTransaction}) => {
jsTest.log("Test prepared transactions: " + isPreparedTransaction);
jsTest.log("Test with default stmtIds");
const testOptions0 = {shouldStoreStmtIds: true, isPreparedTransaction};
testInserts(lsid, txnNumber++, testOptions0);
testUpdates(lsid, txnNumber++, testOptions0);
testDeletes(lsid, txnNumber++, testOptions0);
jsTest.log("Test with custom and valid stmtIds");
const testOptions1 = {
shouldStoreStmtIds: true,
customStmtIdsOption: kStmtIdsOption.isComplete,
isPreparedTransaction
};
testInserts(lsid, txnNumber++, testOptions1);
testUpdates(lsid, txnNumber++, testOptions1);
testDeletes(lsid, txnNumber++, testOptions1);
jsTest.log(
"Test with custom stmtIds containing -1. Verify that operation entries for write " +
"statements with stmtId=-1 do not have a 'stmtId' field");
const testOptions2 = {
shouldStoreStmtIds: true,
customStmtIdsOption: kStmtIdsOption.isIncomplete,
isPreparedTransaction
};
testInserts(lsid, txnNumber++, testOptions2);
testUpdates(lsid, txnNumber++, testOptions2);
testDeletes(lsid, txnNumber++, testOptions2);
jsTest.log(
"Test with custom stmtIds containing repeats. Verify that the command fails with " +
"a uassert instead causes the mongod that executes it to crash");
const testOptions3 = {
shouldStoreStmtIds: true,
customStmtIdsOption: kStmtIdsOption.isRepeated,
isPreparedTransaction
};
testInserts(lsid, txnNumber++, testOptions3);
testUpdates(lsid, txnNumber++, testOptions3);
testDeletes(lsid, txnNumber++, testOptions3);
};
runTests({isPreparedTransaction: false});
runTests({isPreparedTransaction: true});
}
st.stop();