summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Russotto <matthew.russotto@10gen.com>2018-04-13 16:14:09 -0400
committerMatthew Russotto <matthew.russotto@10gen.com>2018-04-13 16:14:12 -0400
commit829d3c528e81e5d93f6815377f513c6d5f54e3f6 (patch)
treec6f7ebedb30464ca88c277e459e6402f682124c1
parent22038f42e0dd1fcfe4d56b7d1911f7837b4aee48 (diff)
downloadmongo-829d3c528e81e5d93f6815377f513c6d5f54e3f6.tar.gz
SERVER-33412 Error on writes with unsupported transaction options
-rw-r--r--jstests/core/txns/multi_statement_transaction_command_args.js24
-rw-r--r--jstests/core/txns/no_read_or_write_concern_inside_txn.js159
-rw-r--r--jstests/core/txns/no_snapshot_writes_outside_txn.js78
-rw-r--r--jstests/core/txns/statement_ids_accepted.js9
-rw-r--r--jstests/libs/override_methods/set_read_and_write_concerns.js12
-rw-r--r--jstests/noPassthrough/readConcern_snapshot.js87
-rw-r--r--jstests/noPassthrough/read_concern_snapshot_yielding.js76
-rw-r--r--jstests/noPassthrough/snapshot_reads.js24
-rw-r--r--src/mongo/db/service_entry_point_common.cpp15
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",