diff options
author | Matthew Russotto <matthew.russotto@10gen.com> | 2018-04-13 16:14:09 -0400 |
---|---|---|
committer | Matthew Russotto <matthew.russotto@10gen.com> | 2018-04-13 16:14:12 -0400 |
commit | 829d3c528e81e5d93f6815377f513c6d5f54e3f6 (patch) | |
tree | c6f7ebedb30464ca88c277e459e6402f682124c1 | |
parent | 22038f42e0dd1fcfe4d56b7d1911f7837b4aee48 (diff) | |
download | mongo-829d3c528e81e5d93f6815377f513c6d5f54e3f6.tar.gz |
SERVER-33412 Error on writes with unsupported transaction options
-rw-r--r-- | jstests/core/txns/multi_statement_transaction_command_args.js | 24 | ||||
-rw-r--r-- | jstests/core/txns/no_read_or_write_concern_inside_txn.js | 159 | ||||
-rw-r--r-- | jstests/core/txns/no_snapshot_writes_outside_txn.js | 78 | ||||
-rw-r--r-- | jstests/core/txns/statement_ids_accepted.js | 9 | ||||
-rw-r--r-- | jstests/libs/override_methods/set_read_and_write_concerns.js | 12 | ||||
-rw-r--r-- | jstests/noPassthrough/readConcern_snapshot.js | 87 | ||||
-rw-r--r-- | jstests/noPassthrough/read_concern_snapshot_yielding.js | 76 | ||||
-rw-r--r-- | jstests/noPassthrough/snapshot_reads.js | 24 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_common.cpp | 15 |
9 files changed, 327 insertions, 157 deletions
diff --git a/jstests/core/txns/multi_statement_transaction_command_args.js b/jstests/core/txns/multi_statement_transaction_command_args.js index c24205a12b0..41970729f05 100644 --- a/jstests/core/txns/multi_statement_transaction_command_args.js +++ b/jstests/core/txns/multi_statement_transaction_command_args.js @@ -172,9 +172,31 @@ })); /*********************************************************************************************** - * Setting autocommit=true or omitting autocommit on a non-initial transaction operation fails. + * Setting autocommit=true or omitting autocommit on a transaction operation fails. **********************************************************************************************/ + jsTestLog("Run an initial transaction operation with autocommit=true"); + txnNumber++; + + assert.commandFailedWithCode(sessionDb.runCommand({ + find: collName, + filter: {}, + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(txnNumber), + startTransaction: true, + autocommit: true + }), + ErrorCodes.InvalidOptions); + + // Committing the transaction should fail. + assert.commandFailedWithCode(sessionDb.adminCommand({ + commitTransaction: 1, + txnNumber: NumberLong(txnNumber), + autocommit: false, + writeConcern: {w: "majority"} + }), + ErrorCodes.NoSuchTransaction); + jsTestLog("Run a non-initial transaction operation with autocommit=true"); txnNumber++; diff --git a/jstests/core/txns/no_read_or_write_concern_inside_txn.js b/jstests/core/txns/no_read_or_write_concern_inside_txn.js new file mode 100644 index 00000000000..f4d26e4c74b --- /dev/null +++ b/jstests/core/txns/no_read_or_write_concern_inside_txn.js @@ -0,0 +1,159 @@ +/** + * Verify that readConcern and writeConcern are not allowed in transactions other than the + * first statement (for readConcern) and the commit (for writeConcern) + */ + +(function() { + "use strict"; + const dbName = "test"; + const collName = "no_read_or_write_concerns_inside_txn"; + const testDB = db.getSiblingDB(dbName); + const testColl = testDB[collName]; + + // Set up the test collection. + testDB.runCommand({drop: collName, writeConcern: {w: "majority"}}); + + assert.commandWorked(testDB.createCollection(collName, {writeConcern: {w: "majority"}})); + + // Initiate the session. + const sessionOptions = {causalConsistency: false}; + let session = db.getMongo().startSession(sessionOptions); + let sessionDb = session.getDatabase(dbName); + let txnNumber = 0; + let stmtId = 0; + + jsTestLog("Starting first transaction"); + assert.commandWorked(sessionDb.runCommand({ + insert: collName, + documents: [{_id: 0}], + readConcern: {level: "snapshot"}, + startTransaction: true, + autocommit: false, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + })); + + jsTestLog("Attempting to insert with readConcern: snapshot within a transaction."); + assert.commandFailedWithCode(sessionDb.runCommand({ + insert: collName, + documents: [{_id: 1}], + readConcern: {level: "snapshot"}, + autocommit: false, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + }), + ErrorCodes.InvalidOptions); + + jsTestLog("Attempting to insert with readConcern:majority within a transaction."); + assert.commandFailedWithCode(sessionDb.runCommand({ + insert: collName, + documents: [{_id: 2}], + readConcern: {level: "majority"}, + autocommit: false, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + }), + ErrorCodes.InvalidOptions); + + jsTestLog("Attempting to insert with readConcern:local within a transaction."); + assert.commandFailedWithCode(sessionDb.runCommand({ + insert: collName, + documents: [{_id: 3}], + readConcern: {level: "local"}, + autocommit: false, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + }), + ErrorCodes.InvalidOptions); + + jsTestLog("Transaction should still commit."); + assert.commandWorked(sessionDb.adminCommand({ + commitTransaction: 1, + autocommit: false, + writeConcern: {w: "majority"}, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + })); + assert.docEq(testColl.find().toArray(), [{_id: 0}]); + + // Drop and re-create collection to keep parts of test isolated from one another. + testDB.runCommand({drop: collName, writeConcern: {w: "majority"}}); + assert.commandWorked(testDB.createCollection(collName, {writeConcern: {w: "majority"}})); + + txnNumber++; + stmtId = 0; + + jsTestLog("Attempting to start transaction with local writeConcern."); + assert.commandFailedWithCode(sessionDb.runCommand({ + insert: collName, + documents: [{_id: 4}], + readConcern: {level: "snapshot"}, + writeConcern: {w: 1}, + startTransaction: true, + autocommit: false, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + }), + ErrorCodes.InvalidOptions); + txnNumber++; + stmtId = 0; + + jsTestLog("Attempting to start transaction with majority writeConcern."); + assert.commandFailedWithCode(sessionDb.runCommand({ + insert: collName, + documents: [{_id: 5}], + readConcern: {level: "snapshot"}, + writeConcern: {w: "majority"}, + startTransaction: true, + autocommit: false, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + }), + ErrorCodes.InvalidOptions); + txnNumber++; + stmtId = 0; + + jsTestLog("Starting transaction normally."); + assert.commandWorked(sessionDb.runCommand({ + insert: collName, + documents: [{_id: 6}], + readConcern: {level: "snapshot"}, + startTransaction: true, + autocommit: false, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + })); + + jsTestLog("Attempting to write within transaction with majority write concern."); + assert.commandFailedWithCode(sessionDb.runCommand({ + insert: collName, + documents: [{_id: 7}], + writeConcern: {w: "majority"}, + autocommit: false, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + }), + ErrorCodes.InvalidOptions); + + jsTestLog("Attempting to write within transaction with local write concern."); + assert.commandFailedWithCode(sessionDb.runCommand({ + insert: collName, + documents: [{_id: 8}], + writeConcern: {w: 1}, + autocommit: false, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + }), + ErrorCodes.InvalidOptions); + + jsTestLog("Transaction should still commit."); + assert.commandWorked(sessionDb.adminCommand({ + commitTransaction: 1, + autocommit: false, + writeConcern: {w: "majority"}, + txnNumber: NumberLong(txnNumber), + stmtId: NumberInt(stmtId++) + })); + assert.docEq(testColl.find().toArray(), [{_id: 6}]); + session.endSession(); +}()); diff --git a/jstests/core/txns/no_snapshot_writes_outside_txn.js b/jstests/core/txns/no_snapshot_writes_outside_txn.js new file mode 100644 index 00000000000..2d04e16f7b2 --- /dev/null +++ b/jstests/core/txns/no_snapshot_writes_outside_txn.js @@ -0,0 +1,78 @@ +/** + * Verify that readConcern: snapshot is not permitted on writes outside transactions. + */ + +(function() { + "use strict"; + const dbName = "test"; + const collName = "no_snapshot_writes_outside_txn"; + const testDB = db.getSiblingDB(dbName); + + // Set up the test collection. + testDB.runCommand({drop: collName, writeConcern: {w: "majority"}}); + + assert.commandWorked(testDB.createCollection(collName, {writeConcern: {w: "majority"}})); + + // Initiate the session. + const sessionOptions = {causalConsistency: false}; + let session = db.getMongo().startSession(sessionOptions); + let sessionDb = session.getDatabase(dbName); + let txnNumber = 0; + let stmtId = 0; + + function tryWrites({testDB, useSnapshotReadSyntax, message}) { + jsTestLog("Verify that inserts cannot use readConcern snapshot " + message); + let cmd = { + insert: collName, + documents: [{_id: 0}], + readConcern: {level: "snapshot"}, + }; + if (useSnapshotReadSyntax) { + Object.assign(cmd, {txnNumber: NumberLong(txnNumber++), stmtId: NumberInt(stmtId)}); + } + assert.commandFailedWithCode(testDB.runCommand(cmd), ErrorCodes.InvalidOptions); + + jsTestLog("Verify that updates cannot use readConcern snapshot " + message); + cmd = { + update: collName, + updates: [{q: {_id: 0}, u: {$set: {x: 1}}}], + readConcern: {level: "snapshot"}, + }; + if (useSnapshotReadSyntax) { + Object.assign(cmd, {txnNumber: NumberLong(txnNumber++), stmtId: NumberInt(stmtId)}); + } + assert.commandFailedWithCode(testDB.runCommand(cmd), ErrorCodes.InvalidOptions); + + jsTestLog("Verify that deletes cannot use readConcern snapshot " + message); + cmd = { + delete: collName, + deletes: [{q: {_id: 0}, limit: 1}], + readConcern: {level: "snapshot"}, + }; + if (useSnapshotReadSyntax) { + Object.assign(cmd, {txnNumber: NumberLong(txnNumber++), stmtId: NumberInt(stmtId)}); + } + assert.commandFailedWithCode(testDB.runCommand(cmd), ErrorCodes.InvalidOptions); + + jsTestLog("Verify that findAndModify cannot use readConcern snapshot " + message); + cmd = { + findAndModify: collName, + query: {_id: 0}, + remove: true, + readConcern: {level: "snapshot"}, + }; + if (useSnapshotReadSyntax) { + Object.assign(cmd, {txnNumber: NumberLong(txnNumber++), stmtId: NumberInt(stmtId)}); + } + assert.commandFailedWithCode(testDB.runCommand(cmd), ErrorCodes.InvalidOptions); + } + tryWrites({ + testDB: sessionDb, + useSnapshotReadSyntax: true, + message: "in session using snapshot read syntax." + }); + tryWrites({testDB: sessionDb, useSnapshotReadSyntax: false, message: "in session."}); + tryWrites({testDB: testDB, useSnapshotReadSyntax: false, message: "outside session."}); + + session.endSession(); +}()); diff --git a/jstests/core/txns/statement_ids_accepted.js b/jstests/core/txns/statement_ids_accepted.js index c38ea597c76..75e71cdfbd9 100644 --- a/jstests/core/txns/statement_ids_accepted.js +++ b/jstests/core/txns/statement_ids_accepted.js @@ -265,15 +265,6 @@ stmtId: NumberInt(0) })); - jsTestLog("Check that parallelCollectionScan accepts a statement ID"); - assert.commandWorked(sessionDb.runCommand({ - parallelCollectionScan: collName, - numCursors: 2, - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber++), - stmtId: NumberInt(0), - })); - jsTestLog("Check that prepareTransaction accepts a statement ID"); assert.commandWorked(sessionDb.runCommand({ find: collName, diff --git a/jstests/libs/override_methods/set_read_and_write_concerns.js b/jstests/libs/override_methods/set_read_and_write_concerns.js index 9c6762d8a75..3ff6618fc59 100644 --- a/jstests/libs/override_methods/set_read_and_write_concerns.js +++ b/jstests/libs/override_methods/set_read_and_write_concerns.js @@ -56,6 +56,7 @@ "_configsvrUpdateZoneKeyRange", "_mergeAuthzCollections", "_recvChunkStart", + "abortTransaction", "appendOplogNote", "applyOps", "aggregate", @@ -65,6 +66,7 @@ "cloneCollection", "cloneCollectionAsCapped", "collMod", + "commitTransaction", "convertToCapped", "copydb", "create", @@ -103,6 +105,9 @@ "updateUser", ]); + const kCommandsSupportingWriteConcernInTransaction = + new Set(["doTxn", "abortTransaction", "commitTransaction"]); + function runCommandWithReadAndWriteConcerns( conn, dbName, commandName, commandObj, func, makeFuncArgs) { if (typeof commandObj !== "object" || commandObj === null) { @@ -126,6 +131,13 @@ let shouldForceReadConcern = kCommandsSupportingReadConcern.has(commandName); let shouldForceWriteConcern = kCommandsSupportingWriteConcern.has(commandName); + // All commands in a multi-document transaction have the autocommit property. + if (commandObj.hasOwnProperty("autocommit")) { + shouldForceReadConcern = false; + if (!kCommandsSupportingWriteConcernInTransaction.has(commandName)) { + shouldForceWriteConcern = false; + } + } if (commandName === "aggregate") { if (OverrideHelpers.isAggregationWithListLocalCursorsStage(commandName, commandObjUnwrapped)) { diff --git a/jstests/noPassthrough/readConcern_snapshot.js b/jstests/noPassthrough/readConcern_snapshot.js index 4f5a484fa57..8f7187d26c7 100644 --- a/jstests/noPassthrough/readConcern_snapshot.js +++ b/jstests/noPassthrough/readConcern_snapshot.js @@ -176,93 +176,6 @@ txnNumber: NumberLong(txnNumber++) })); - // TODO SERVER-33412 Move all write related commands out of this test file when writes - // with snapshot read concern are only allowed in transactions. - // readConcern 'snapshot' is supported by insert. - assert.commandWorked(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "single-insert"}], - readConcern: {level: "snapshot"}, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber++) - })); - assert.eq({_id: "single-insert"}, sessionDb.coll.findOne({_id: "single-insert"})); - - // readConcern 'snapshot' is supported by batch insert. - assert.commandWorked(sessionDb.runCommand({ - insert: collName, - documents: [{_id: "batch-insert-1"}, {_id: "batch-insert-2"}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber++) - })); - assert.eq({_id: "batch-insert-1"}, sessionDb.coll.findOne({_id: "batch-insert-1"})); - assert.eq({_id: "batch-insert-2"}, sessionDb.coll.findOne({_id: "batch-insert-2"})); - - // readConcern 'snapshot' is supported by update. - assert.commandWorked(sessionDb.coll.insert({_id: 0}, {writeConcern: {w: "majority"}})); - printjson(assert.commandWorked(sessionDb.runCommand({ - update: collName, - updates: [{q: {_id: 0}, u: {$inc: {a: 1}}}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber++) - }))); - assert.eq({_id: 0, a: 1}, sessionDb.coll.findOne({_id: 0})); - - // readConcern 'snapshot' is supported by batch updates. - assert.commandWorked(sessionDb.coll.insert({_id: 1}, {writeConcern: {w: "majority"}})); - assert.commandWorked(sessionDb.runCommand({ - update: collName, - updates: [{q: {_id: 0}, u: {$inc: {a: 1}}}, {q: {_id: 1}, u: {$inc: {a: 1}}}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber++) - })); - assert.eq({_id: 0, a: 2}, sessionDb.coll.findOne({_id: 0})); - assert.eq({_id: 1, a: 1}, sessionDb.coll.findOne({_id: 1})); - - // readConcern 'snapshot' is supported by delete. - assert.commandWorked(sessionDb.coll.insert({_id: 2}, {writeConcern: {w: "majority"}})); - assert.commandWorked(sessionDb.runCommand({ - delete: collName, - deletes: [{q: {}, limit: 1}], - readConcern: {level: "snapshot"}, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber++) - })); - - // readConcern 'snapshot' is supported by findAndModify. - assert.commandWorked(sessionDb.runCommand({ - findAndModify: collName, - query: {_id: 1}, - update: {$set: {b: 1}}, - readConcern: {level: "snapshot"}, - writeConcern: {w: "majority"}, - txnNumber: NumberLong(txnNumber++), - })); - assert.eq({_id: 1, a: 1, b: 1}, sessionDb.coll.findOne({_id: 1})); - - assert.commandWorked(sessionDb.runCommand({ - findAndModify: collName, - query: {_id: 1}, - remove: true, - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber++), - })); - assert.eq(0, sessionDb.coll.find({_id: 1}).itcount()); - - // readConcern 'snapshot' is supported by parallelCollectionScan. - const res = assert.commandWorked(sessionDb.runCommand({ - parallelCollectionScan: collName, - numCursors: 1, - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(txnNumber) - })); - assert(res.hasOwnProperty("cursors")); - assert.eq(res.cursors.length, 1); - assert(res.cursors[0].hasOwnProperty("cursor")); - const cursorId = res.cursors[0].cursor.id; - assert.commandWorked(sessionDb.runCommand( - {getMore: cursorId, collection: collName, txnNumber: NumberLong(txnNumber++)})); - // readConcern 'snapshot' is not supported by non-CRUD commands. assert.commandFailedWithCode(sessionDb.runCommand({ createIndexes: collName, diff --git a/jstests/noPassthrough/read_concern_snapshot_yielding.js b/jstests/noPassthrough/read_concern_snapshot_yielding.js index ad05e255d5f..69b302434b1 100644 --- a/jstests/noPassthrough/read_concern_snapshot_yielding.js +++ b/jstests/noPassthrough/read_concern_snapshot_yielding.js @@ -337,38 +337,7 @@ assert.eq(res.count, 4); }, {"command.group.ns": "coll"}, {"command.group.ns": "coll"}); - // Test getMore on a parallelCollectionScan established cursor. We skip testing for - // parallelCollectionScan itself as it returns a cursor only and may not hit an interrupt point. - testCommand(function() { - assert.commandWorked(db.adminCommand( - {configureFailPoint: "setInterruptOnlyPlansCheckForInterruptHang", mode: "off"})); - let res = assert.commandWorked(db.runCommand({ - parallelCollectionScan: "coll", - numCursors: 1, - readConcern: {level: "snapshot"}, - lsid: TestData.sessionId, - txnNumber: NumberLong(TestData.txnNumber) - })); - assert(res.hasOwnProperty("cursors")); - assert.eq(res.cursors.length, 1, tojson(res)); - assert(res.cursors[0].hasOwnProperty("cursor"), tojson(res)); - const cursorId = res.cursors[0].cursor.id; - - assert.commandWorked(db.adminCommand( - {configureFailPoint: "setInterruptOnlyPlansCheckForInterruptHang", mode: "alwaysOn"})); - res = assert.commandWorked(db.runCommand({ - getMore: NumberLong(cursorId), - collection: "coll", - lsid: TestData.sessionId, - txnNumber: NumberLong(TestData.txnNumber) - })); - assert(res.hasOwnProperty("cursor"), tojson(res)); - assert(res.cursor.hasOwnProperty("nextBatch"), tojson(res)); - assert.eq(res.cursor.nextBatch.length, TestData.numDocs, tojson(res)); - }, {"originatingCommand.parallelCollectionScan": "coll"}, {op: "getmore"}); - // Test update. - // TODO SERVER-33412: Perform writes under autocommit:false transaction. // TODO SERVER-33548: We cannot provide a 'profilerFilter' because profiling is turned off for // batch write commands in transactions. testCommand(function() { @@ -377,9 +346,19 @@ updates: [{q: {}, u: {$set: {updated: true}}}, {q: {new: 1}, u: {$set: {updated: true}}}], readConcern: {level: "snapshot"}, + startTransaction: true, + autocommit: false, + stmtId: NumberInt(0), lsid: TestData.sessionId, txnNumber: NumberLong(TestData.txnNumber) })); + assert.commandWorked(db.adminCommand({ + commitTransaction: 1, + autocommit: false, + lsid: TestData.sessionId, + stmtId: NumberInt(1), + txnNumber: NumberLong(TestData.txnNumber) + })); // Only update one existing doc committed before the transaction. assert.eq(res.n, 1, tojson(res)); assert.eq(res.nModified, 1, tojson(res)); @@ -393,7 +372,17 @@ delete: "coll", deletes: [{q: {}, limit: 1}, {q: {new: 1}, limit: 1}], readConcern: {level: "snapshot"}, + startTransaction: true, + autocommit: false, + txnNumber: NumberLong(TestData.txnNumber), + stmtId: NumberInt(0), + lsid: TestData.sessionId + })); + assert.commandWorked(db.adminCommand({ + commitTransaction: 1, + autocommit: false, lsid: TestData.sessionId, + stmtId: NumberInt(1), txnNumber: NumberLong(TestData.txnNumber) })); // Only remove one existing doc committed before the transaction. @@ -401,15 +390,24 @@ }, {op: "remove"}, null, true); // Test findAndModify. - // TODO SERVER-33412: Perform writes under autocommit:false transaction. testCommand(function() { const res = assert.commandWorked(db.runCommand({ findAndModify: "coll", query: {new: 1}, update: {$set: {findAndModify: 1}}, readConcern: {level: "snapshot"}, - lsid: TestData.sessionId, + startTransaction: true, + autocommit: false, txnNumber: NumberLong(TestData.txnNumber), + stmtId: NumberInt(0), + lsid: TestData.sessionId, + })); + assert.commandWorked(db.adminCommand({ + commitTransaction: 1, + autocommit: false, + lsid: TestData.sessionId, + stmtId: NumberInt(1), + txnNumber: NumberLong(TestData.txnNumber) })); assert(res.hasOwnProperty("lastErrorObject")); assert.eq(res.lastErrorObject.n, 0, tojson(res)); @@ -422,8 +420,18 @@ query: {new: 1}, update: {$set: {findAndModify: 1}}, readConcern: {level: "snapshot"}, - lsid: TestData.sessionId, + startTransaction: true, + autocommit: false, txnNumber: NumberLong(TestData.txnNumber), + stmtId: NumberInt(0), + lsid: TestData.sessionId, + })); + assert.commandWorked(db.adminCommand({ + commitTransaction: 1, + autocommit: false, + lsid: TestData.sessionId, + stmtId: NumberInt(1), + txnNumber: NumberLong(TestData.txnNumber) })); assert(res.hasOwnProperty("lastErrorObject")); assert.eq(res.lastErrorObject.n, 0, tojson(res)); diff --git a/jstests/noPassthrough/snapshot_reads.js b/jstests/noPassthrough/snapshot_reads.js index a431861aab4..87942a43400 100644 --- a/jstests/noPassthrough/snapshot_reads.js +++ b/jstests/noPassthrough/snapshot_reads.js @@ -155,29 +155,5 @@ runTest({useCausalConsistency: true, readFromSecondary: false, establishCursorCmd: aggCmd}); runTest({useCausalConsistency: false, readFromSecondary: true, establishCursorCmd: aggCmd}); runTest({useCausalConsistency: true, readFromSecondary: true, establishCursorCmd: aggCmd}); - - // Test snapshot reads using parallelCollectionScan. - let parallelCollScanCmd = {parallelCollectionScan: collName, numCursors: 1}; - runTest({ - useCausalConsistency: false, - readFromSecondary: false, - establishCursorCmd: parallelCollScanCmd - }); - runTest({ - useCausalConsistency: true, - readFromSecondary: false, - establishCursorCmd: parallelCollScanCmd - }); - runTest({ - useCausalConsistency: false, - readFromSecondary: true, - establishCursorCmd: parallelCollScanCmd - }); - runTest({ - useCausalConsistency: true, - readFromSecondary: true, - establishCursorCmd: parallelCollScanCmd - }); - rst.stopSet(); })(); diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index 6dbaea30378..01fafcf60ce 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -484,6 +484,13 @@ bool runCommandImpl(OperationContext* opCtx, invokeInTransaction(opCtx, invocation, &crb); } else { auto wcResult = uassertStatusOK(extractWriteConcern(opCtx, request.body)); + auto session = OperationContextSession::get(opCtx); + uassert(ErrorCodes::InvalidOptions, + "writeConcern is not allowed within a multi-statement transaction", + wcResult.usedDefault || !session || !session->inMultiDocumentTransaction() || + invocation->definition()->getName() == "commitTransaction" || + invocation->definition()->getName() == "abortTransaction" || + invocation->definition()->getName() == "doTxn"); auto lastOpBeforeRun = repl::ReplClientInfo::forClient(opCtx->getClient()).getLastOp(); @@ -748,8 +755,12 @@ void execCommandDatabase(OperationContext* opCtx, if (readConcernArgs.getLevel() == repl::ReadConcernLevel::kSnapshotReadConcern) { uassert(ErrorCodes::InvalidOptions, - "readConcern level snapshot in only valid in multi-statement transactions", - getTestCommandsEnabled() || + "readConcern level snapshot is only valid in multi-statement transactions", + // With test commands enabled, a read command with readConcern snapshot is + // a valid snapshot read. + (getTestCommandsEnabled() && + invocation->definition()->getReadWriteType() == + BasicCommand::ReadWriteType::kRead) || (autocommitVal != boost::none && *autocommitVal == false)); uassert(ErrorCodes::InvalidOptions, "readConcern level snapshot requires a session ID", |