mirror of
https://github.com/mongodb/mongo.git
synced 2024-11-27 23:27:11 +01:00
90ef12959b
GitOrigin-RevId: 80c235cf9f06dd5edb1a6850cf614e5956f73c4b
185 lines
6.8 KiB
JavaScript
185 lines
6.8 KiB
JavaScript
/**
|
|
* Utilities for testing transactions.
|
|
*/
|
|
import {OverrideHelpers} from "jstests/libs/override_methods/override_helpers.js";
|
|
|
|
export var TransactionsUtil = (function() {
|
|
// Although createCollection and createIndexes are supported inside multi-document
|
|
// transactions, we intentionally exclude them from this list since they are non-
|
|
// idempotent and, for createIndexes, are not supported inside multi-document
|
|
// transactions for all cases.
|
|
const kCmdsSupportingTransactions = new Set([
|
|
'aggregate',
|
|
'delete',
|
|
'find',
|
|
'findAndModify',
|
|
'findandmodify',
|
|
'getMore',
|
|
'insert',
|
|
'update',
|
|
'bulkWrite',
|
|
]);
|
|
|
|
const kCmdsThatWrite = new Set([
|
|
'insert',
|
|
'update',
|
|
'findAndModify',
|
|
'findandmodify',
|
|
'delete',
|
|
'bulkWrite',
|
|
]);
|
|
|
|
// Indicates an aggregation command with a pipeline that cannot run in a transaction but can
|
|
// still execute concurrently with other transactions. Pipelines with $changeStream or $out
|
|
// cannot run within a transaction.
|
|
function commandIsNonTxnAggregation(cmdName, cmdObj) {
|
|
return !("explain" in cmdObj) &&
|
|
OverrideHelpers.isAggregationWithOutOrMergeStage(cmdName, cmdObj) ||
|
|
OverrideHelpers.isAggregationWithChangeStreamStage(cmdName, cmdObj);
|
|
}
|
|
|
|
function commandSupportsTxn(dbName, cmdName, cmdObj) {
|
|
if (cmdName === 'commitTransaction' || cmdName === 'abortTransaction') {
|
|
return true;
|
|
}
|
|
|
|
if (!kCmdsSupportingTransactions.has(cmdName) ||
|
|
commandIsNonTxnAggregation(cmdName, cmdObj)) {
|
|
return false;
|
|
}
|
|
|
|
// bulkWrite always operates on the admin DB so cannot check the dbName directly.
|
|
// Operating namespaces are also contained within a 'nsInfo' array in the command.
|
|
if (cmdName === 'bulkWrite') {
|
|
// 'nsInfo' does not exist in command.
|
|
if (!cmdObj['nsInfo']) {
|
|
return false;
|
|
}
|
|
|
|
// Loop through 'nsInfo'.
|
|
for (const ns of cmdObj['nsInfo']) {
|
|
if (!ns['ns']) {
|
|
return false;
|
|
}
|
|
var db = ns['ns'].split('.', 1)[0];
|
|
if (db === 'local' || db === 'config' || db === 'system') {
|
|
return false;
|
|
}
|
|
// Make sure no namespaces are system. collections
|
|
var coll = ns['ns'].substring(ns['ns'].indexOf('.') + 1);
|
|
if (coll.startsWith('system.')) {
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if (dbName === 'local' || dbName === 'config' || dbName === 'admin') {
|
|
return false;
|
|
}
|
|
|
|
if (kCmdsThatWrite.has(cmdName)) {
|
|
if (cmdObj[cmdName].startsWith('system.')) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cmdObj.lsid === undefined) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function commandTypeCanSupportTxn(cmdName) {
|
|
if (cmdName === 'commitTransaction' || cmdName === 'abortTransaction') {
|
|
return true;
|
|
}
|
|
|
|
if (kCmdsSupportingTransactions.has(cmdName)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Make a deep copy of an object for retrying transactions. We make deep copies of object and
|
|
// array literals but not custom types like DB and DBCollection because they could have been
|
|
// modified before a transaction aborts. This function is adapted from the implementation of
|
|
// Object.extend() in src/mongo/shell/types.js.
|
|
function deepCopyObject(dst, src) {
|
|
for (var k in src) {
|
|
var v = src[k];
|
|
if (typeof (v) == "object" && v !== null) {
|
|
if (v.constructor === ObjectId) { // convert ObjectId properly
|
|
eval("v = " + tojson(v));
|
|
} else if (v instanceof NumberLong) { // convert NumberLong properly
|
|
eval("v = " + tojson(v));
|
|
} else if (v instanceof Date) { // convert Date properly
|
|
eval("v = " + tojson(v));
|
|
} else if (v instanceof Timestamp) { // convert Timestamp properly
|
|
eval("v = " + tojson(v));
|
|
} else if (Object.getPrototypeOf(v) === Object.prototype) {
|
|
v = deepCopyObject({}, v);
|
|
} else if (Array.isArray(v)) {
|
|
v = deepCopyObject([], v);
|
|
} else if (v instanceof Set) {
|
|
v = new Set(v);
|
|
} else if (v instanceof Map) {
|
|
v = new Map(v);
|
|
}
|
|
}
|
|
var desc = Object.getOwnPropertyDescriptor(src, k);
|
|
desc.value = v;
|
|
Object.defineProperty(dst, k, desc);
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
function isTransientTransactionError(res) {
|
|
return res.hasOwnProperty('errorLabels') &&
|
|
res.errorLabels.includes('TransientTransactionError');
|
|
}
|
|
|
|
function isConflictingOperationInProgress(res) {
|
|
return res != null && res.hasOwnProperty('codeName') &&
|
|
res.codeName === "ConflictingOperationInProgress";
|
|
}
|
|
|
|
// Runs a function 'func()' in a transaction on database 'db'. Invokes function
|
|
// 'beforeTransactionFunc()' before the transaction (can be used to get references to
|
|
// collections etc.). Ensures that the transaction is successfully committed, by retrying the
|
|
// function 'func' in case of the transaction being aborted until the timeout is hit.
|
|
//
|
|
// Function 'beforeTransactionFunc(db, session)' accepts database in session 'db' and the
|
|
// session 'session'.
|
|
// Function 'func(db, state)' accepts database in session 'db' and an object returned by
|
|
// 'beforeTransactionFunc()' - 'state'.
|
|
// 'transactionOptions' - parameters for the transaction.
|
|
function runInTransaction(db, beforeTransactionFunc, func, transactionOptions = {}) {
|
|
const session = db.getMongo().startSession();
|
|
const sessionDb = session.getDatabase(db.getName());
|
|
const state = beforeTransactionFunc(sessionDb, session);
|
|
let commandResponse;
|
|
assert.soon(() => {
|
|
session.startTransaction(transactionOptions);
|
|
func(sessionDb, state);
|
|
try {
|
|
commandResponse = assert.commandWorked(session.commitTransaction_forTesting());
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
return commandResponse;
|
|
}
|
|
|
|
return {
|
|
commandIsNonTxnAggregation,
|
|
commandSupportsTxn,
|
|
commandTypeCanSupportTxn,
|
|
deepCopyObject,
|
|
isTransientTransactionError,
|
|
isConflictingOperationInProgress,
|
|
runInTransaction,
|
|
};
|
|
})();
|