mirror of https://github.com/mongodb/mongo
158 lines
6.3 KiB
JavaScript
158 lines
6.3 KiB
JavaScript
/**
|
|
* Tests the propagation of Operation FCV in the oplog's `versionContext` field for:
|
|
* - Non-applyOps entries from operations outside transactions.
|
|
* - applyOps entries for batched writes.
|
|
* - applyOps entries for unprepared transactions.
|
|
* - applyOps entries for prepared transactions.
|
|
*
|
|
* @tags: [
|
|
* required_fcv_83,
|
|
* requires_replication,
|
|
* uses_transactions,
|
|
* uses_prepare_transaction
|
|
* ]
|
|
*/
|
|
import {ReplSetTest} from "jstests/libs/replsettest.js";
|
|
import {PrepareHelpers} from "jstests/core/txns/libs/prepare_helpers.js";
|
|
|
|
let rst = new ReplSetTest({nodes: 2});
|
|
rst.startSet();
|
|
rst.initiate();
|
|
const conn = rst.getPrimary();
|
|
const db = conn.getDB("test");
|
|
|
|
const currentFCV = assert.commandWorked(db.adminCommand({getParameter: 1, featureCompatibilityVersion: 1}))
|
|
.featureCompatibilityVersion.version;
|
|
|
|
// Force operations to use an Operation FCV, so they emit the `versionContext` field in their oplog
|
|
// entries. This is required since not all operations use an OFCV yet.
|
|
const originalMongoRunCommand = Mongo.prototype.runCommand;
|
|
Mongo.prototype.runCommand = function runCommandSpy(dbName, cmdObj, options) {
|
|
cmdObj.versionContext = {OFCV: currentFCV};
|
|
return originalMongoRunCommand.apply(this, arguments);
|
|
};
|
|
|
|
function findOneInOplog(filter) {
|
|
const oplogEntry = db.getSiblingDB("local").oplog.rs.findOne(filter);
|
|
assert(oplogEntry, "Could not find an oplog entry for filter: " + tojson(filter));
|
|
return oplogEntry;
|
|
}
|
|
|
|
// Verifies that oplog entry format for a standalone entry (not a transaction nor a batched write).
|
|
// We expect those entries to have the versionContext field, except for no-op entries.
|
|
function verifyStandaloneOplogEntry(oplogEntry) {
|
|
assert(
|
|
!(oplogEntry.op === "c" && oplogEntry.o.applyOps),
|
|
"Unexpected format for standalone oplog entry: " + tojson(oplogEntry),
|
|
);
|
|
|
|
if (oplogEntry.op === "n") {
|
|
assert(!oplogEntry.versionContext, "Unexpected versionContext in no-op entry: " + tojson(oplogEntry));
|
|
} else {
|
|
assert.eq({OFCV: currentFCV}, oplogEntry.versionContext, tojson(oplogEntry));
|
|
}
|
|
}
|
|
|
|
(function testStandaloneOperations() {
|
|
const coll = db.no_txn;
|
|
|
|
const insertResult = assert.commandWorked(db.runCommand({insert: coll.getName(), documents: [{x: 1}]}));
|
|
verifyStandaloneOplogEntry(findOneInOplog({ts: insertResult.operationTime}));
|
|
|
|
const updateResult = assert.commandWorked(
|
|
db.runCommand({update: coll.getName(), updates: [{q: {x: 1}, u: {$set: {x: 2}}}]}),
|
|
);
|
|
verifyStandaloneOplogEntry(findOneInOplog({ts: updateResult.operationTime}));
|
|
|
|
const dropResult = assert.commandWorked(db.runCommand({drop: coll.getName()}));
|
|
verifyStandaloneOplogEntry(findOneInOplog({ts: dropResult.operationTime}));
|
|
|
|
const noopResult = assert.commandWorked(
|
|
db.adminCommand({
|
|
appendOplogNote: 1,
|
|
data: {msg: "test oplog note"},
|
|
}),
|
|
);
|
|
verifyStandaloneOplogEntry(findOneInOplog({ts: noopResult.operationTime}));
|
|
})();
|
|
|
|
// Verifies that oplog entry format for a batched write.
|
|
// We expect it to specify versionContext on the oplog entry, but *not* for each sub-operation,
|
|
// since batched writes are generated by a single operation (with its single OFCV).
|
|
function verifyBatchedWritesEntry(oplogEntry) {
|
|
assert(
|
|
oplogEntry.op === "c" && oplogEntry.o.applyOps && !oplogEntry.txnNumber,
|
|
"Unexpected format for batched writes oplog entry:" + tojson(oplogEntry),
|
|
);
|
|
|
|
assert.eq({OFCV: currentFCV}, oplogEntry.versionContext, tojson(oplogEntry));
|
|
for (const nestedOp of oplogEntry.o.applyOps) {
|
|
assert(!nestedOp.versionContext, tojson(oplogEntry));
|
|
}
|
|
}
|
|
|
|
(function testBatchedWrites() {
|
|
const coll = db.getCollection("batched_writes");
|
|
|
|
const insertResult = assert.commandWorked(db.runCommand({insert: coll.getName(), documents: [{x: 1}, {x: 2}]}));
|
|
verifyBatchedWritesEntry(findOneInOplog({ts: insertResult.operationTime}));
|
|
|
|
const deleteResult = assert.commandWorked(
|
|
db.runCommand({
|
|
delete: coll.getName(),
|
|
deletes: [{q: {x: {$gt: 0}}, limit: 0}],
|
|
}),
|
|
);
|
|
verifyBatchedWritesEntry(findOneInOplog({ts: deleteResult.operationTime}));
|
|
})();
|
|
|
|
// Verifies that oplog entry format for a (prepared or unprepared) transaction.
|
|
// We expect it to not specify versionContext for each sub-operation, but not on the oplog entry,
|
|
// since transactions can be generated by multiple operations (each with its OFCV).
|
|
function verifyTransactionOplogEntry(oplogEntry) {
|
|
assert(
|
|
oplogEntry.op === "c" && oplogEntry.o.applyOps && oplogEntry.txnNumber,
|
|
"Unexpected format for transaction oplog entry:" + tojson(oplogEntry),
|
|
);
|
|
|
|
assert(!oplogEntry.versionContext, tojson(oplogEntry));
|
|
for (const nestedOp of oplogEntry.o.applyOps) {
|
|
assert.eq({OFCV: currentFCV}, nestedOp.versionContext, tojson(oplogEntry));
|
|
}
|
|
}
|
|
|
|
(function testUnpreparedTransaction() {
|
|
const session = conn.startSession();
|
|
session.startTransaction();
|
|
const sessionDB = session.getDatabase(db.getName());
|
|
const coll = sessionDB.getCollection("unprepared_txn");
|
|
|
|
assert.commandWorked(coll.createIndex({x: 1}));
|
|
assert.commandWorked(coll.insertOne({x: 123}));
|
|
assert.commandWorked(coll.updateOne({x: 123}, {$set: {x: 321}}));
|
|
const commitResult = assert.commandWorked(session.commitTransaction_forTesting());
|
|
verifyTransactionOplogEntry(findOneInOplog({ts: commitResult.operationTime}));
|
|
})();
|
|
|
|
(function testPreparedTransaction() {
|
|
const session = conn.startSession();
|
|
session.startTransaction();
|
|
const sessionDB = session.getDatabase(db.getName());
|
|
const coll = sessionDB.getCollection("prepared_txn");
|
|
|
|
// Collections can't be created in a prepared transaction, so do it outside
|
|
assert.commandWorked(db.createCollection(coll.getName()));
|
|
|
|
assert.commandWorked(coll.insertOne({x: 1}));
|
|
assert.commandWorked(coll.updateOne({x: 1}, {$set: {x: 2}}));
|
|
const prepareTimestamp = PrepareHelpers.prepareTransaction(session);
|
|
const commitResult = assert.commandWorked(PrepareHelpers.commitTransaction(session, prepareTimestamp));
|
|
|
|
verifyTransactionOplogEntry(findOneInOplog({ts: prepareTimestamp}));
|
|
verifyStandaloneOplogEntry(findOneInOplog({ts: commitResult.operationTime}));
|
|
})();
|
|
|
|
Mongo.prototype.runCommand = originalMongoRunCommand;
|
|
|
|
rst.stopSet();
|