summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregory Noma <gregory.noma@gmail.com>2020-04-10 17:16:51 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-04-10 21:28:00 +0000
commit7a4da2ea31f9c699e9458853d9029bd3a1f7de75 (patch)
tree11ffb9da88a4116bc52da77b5d67f2d7b019f73f
parent880dd3e91f5b38105d909072d8d036378575d93d (diff)
downloadmongo-7a4da2ea31f9c699e9458853d9029bd3a1f7de75.tar.gz
SERVER-45956 Allow findAndModify with upsert=true inside multi-document transactions
(cherry picked from commit 8c7d32e37a2c82d08bfe7fb7cd755ac0022a53a8)
-rw-r--r--jstests/concurrency/fsm_workloads/CRUD_and_commands.js4
-rw-r--r--jstests/core/find_and_modify.js65
-rw-r--r--jstests/core/txns/commands_in_txns_read_concern.js4
-rw-r--r--jstests/core/txns/concurrent_drops_and_creates.js26
-rw-r--r--jstests/core/txns/create_collection.js30
-rw-r--r--jstests/core/txns/create_collection_parallel.js38
-rw-r--r--jstests/core/txns/implicit_collection_creation_in_txn.js34
-rw-r--r--jstests/libs/create_collection_txn_helpers.js39
-rw-r--r--jstests/libs/override_methods/network_error_and_txn_override.js30
-rw-r--r--jstests/replsets/txn_override_unittests.js402
-rw-r--r--src/mongo/db/commands/find_and_modify.cpp20
11 files changed, 361 insertions, 331 deletions
diff --git a/jstests/concurrency/fsm_workloads/CRUD_and_commands.js b/jstests/concurrency/fsm_workloads/CRUD_and_commands.js
index 25c0df1c8a5..794ae20b52f 100644
--- a/jstests/concurrency/fsm_workloads/CRUD_and_commands.js
+++ b/jstests/concurrency/fsm_workloads/CRUD_and_commands.js
@@ -89,10 +89,6 @@ var $config = (function() {
e["errorLabels"] = ["TransientTransactionError"];
throw e;
}
- } else if (e.code === ErrorCodes.OperationNotSupportedInTransaction) {
- // TODO (SERVER-45956): Remove this case once fineAndModify with upsert=true
- // is allowed in transactions.
- throw e;
} else {
assertAlways.contains(
e.code,
diff --git a/jstests/core/find_and_modify.js b/jstests/core/find_and_modify.js
index daab98dc7a8..df698f17811 100644
--- a/jstests/core/find_and_modify.js
+++ b/jstests/core/find_and_modify.js
@@ -93,60 +93,37 @@ assert.throws(function() {
// SERVER-17387: Find and modify should throw in the case of invalid projection.
//
-t.drop();
+function runFindAndModify(shouldMatch, upsert, newParam) {
+ t.drop();
+ if (shouldMatch) {
+ assert.commandWorked(t.insert({_id: "found"}));
+ }
+ const query = shouldMatch ? "found" : "miss";
+ const res = db.runCommand({
+ findAndModify: t.getName(),
+ query: {_id: query},
+ update: {$inc: {y: 1}},
+ fields: {foo: {$pop: ["bar"]}},
+ upsert: upsert,
+ new: newParam
+ });
+ assert.commandFailedWithCode(res, 31325);
+}
// Insert case.
-var cmdRes = db.runCommand({
- findAndModify: t.getName(),
- query: {_id: "miss"},
- update: {$inc: {y: 1}},
- fields: {foo: {$pop: ["bar"]}},
- upsert: true,
- new: true
-});
-assert.commandFailed(cmdRes);
-
-t.insert({_id: "found"});
+runFindAndModify(false /* shouldMatch */, true /* upsert */, true /* new */);
// Update with upsert + new.
-cmdRes = db.runCommand({
- findAndModify: t.getName(),
- query: {_id: "found"},
- update: {$inc: {y: 1}},
- fields: {foo: {$pop: ["bar"]}},
- upsert: true,
- new: true
-});
-assert.commandFailed(cmdRes);
+runFindAndModify(true /* shouldMatch */, true /* upsert */, true /* new */);
// Update with just new: true.
-cmdRes = db.runCommand({
- findAndModify: t.getName(),
- query: {_id: "found"},
- update: {$inc: {y: 1}},
- fields: {foo: {$pop: ["bar"]}},
- new: true
-});
-assert.commandFailed(cmdRes);
+runFindAndModify(true /* shouldMatch */, false /* upsert */, true /* new */);
// Update with just upsert: true.
-cmdRes = db.runCommand({
- findAndModify: t.getName(),
- query: {_id: "found"},
- update: {$inc: {y: 1}},
- fields: {foo: {$pop: ["bar"]}},
- upsert: true
-});
-assert.commandFailed(cmdRes);
+runFindAndModify(true /* shouldMatch */, true /* upsert */, false /* new */);
// Update with neither upsert nor new flags.
-cmdRes = db.runCommand({
- findAndModify: t.getName(),
- query: {_id: "found"},
- update: {$inc: {y: 1}},
- fields: {foo: {$pop: ["bar"]}},
-});
-assert.commandFailed(cmdRes);
+runFindAndModify(true /* shouldMatch */, false /* upsert */, false /* new */);
//
// SERVER-17372
diff --git a/jstests/core/txns/commands_in_txns_read_concern.js b/jstests/core/txns/commands_in_txns_read_concern.js
index 6eae3de2e9b..587dd6552f4 100644
--- a/jstests/core/txns/commands_in_txns_read_concern.js
+++ b/jstests/core/txns/commands_in_txns_read_concern.js
@@ -25,7 +25,7 @@ otherColl.drop({writeConcern: {w: "majority"}});
jsTest.log("Testing createCollection in a transaction with local readConcern");
session.startTransaction({readConcern: {level: "local"}, writeConcern: {w: "majority"}});
-createCollAndCRUDInTxn(sessionDB, collName, true /*explicitCreate*/, false /*upsert*/);
+createCollAndCRUDInTxn(sessionDB, collName, "insert", true /*explicitCreate*/);
assert.commandWorked(session.commitTransaction_forTesting());
assert.eq(sessionColl.find({}).itcount(), 1);
@@ -47,7 +47,7 @@ jsTest.log("Testing createCollection in a transaction with local readConcern, wi
"operations preceeding it");
session.startTransaction({readConcern: {level: "local"}, writeConcern: {w: "majority"}});
assert.eq(otherColl.find({a: 1}).itcount(), 1);
-createCollAndCRUDInTxn(sessionDB, collName, true /*explicitCreate*/, false /*upsert*/);
+createCollAndCRUDInTxn(sessionDB, collName, "insert", true /*explicitCreate*/);
assert.commandWorked(session.commitTransaction_forTesting());
assert.eq(sessionColl.find({}).itcount(), 1);
diff --git a/jstests/core/txns/concurrent_drops_and_creates.js b/jstests/core/txns/concurrent_drops_and_creates.js
index 50e53d9e53e..7956f3566b2 100644
--- a/jstests/core/txns/concurrent_drops_and_creates.js
+++ b/jstests/core/txns/concurrent_drops_and_creates.js
@@ -1,6 +1,14 @@
-// Test that a transaction cannot write to a collection that has been dropped or created since the
-// transaction started.
-// @tags: [uses_transactions, uses_snapshot_read_concern]
+/**
+ * Test that a transaction cannot write to a collection that has been dropped or created since the
+ * transaction started.
+ *
+ * @tags: [
+ * assumes_no_implicit_collection_creation_after_drop,
+ * uses_transactions,
+ * uses_snapshot_read_concern,
+ * requires_fcv_44
+ * ]
+ */
(function() {
"use strict";
@@ -41,11 +49,13 @@ assert.commandWorked(testDB2.runCommand({drop: collNameB, writeConcern: {w: "maj
// Ensure the collection drop is visible to the transaction, since our implementation of the in-
// memory collection catalog always has the most recent collection metadata. We can detect the
-// drop by attempting a findandmodify with upsert=true on the dropped collection, since findand-
-// -modify on a nonexisting collection is not supported inside multi-document transactions.
-// TODO(SERVER-45956) remove or rethink this test case.
-assert.throws(() => (sessionCollB.findAndModify(({update: {a: 1}, upsert: true}))));
-assert.commandFailedWithCode(session.abortTransaction_forTesting(), ErrorCodes.NoSuchTransaction);
+// drop by attempting a findAndModify on the dropped collection. Since the collection drop is
+// visible, the findAndModify will not match any existing documents.
+const res =
+ sessionDB2.runCommand({findAndModify: sessionCollB.getName(), update: {a: 1}, upsert: true});
+assert.commandWorked(res);
+assert.eq(res.value, null);
+assert.commandWorked(session.commitTransaction_forTesting());
//
// A transaction with snapshot read concern cannot write to a collection that has been created
diff --git a/jstests/core/txns/create_collection.js b/jstests/core/txns/create_collection.js
index 7490fdf1606..ab12cf10cdc 100644
--- a/jstests/core/txns/create_collection.js
+++ b/jstests/core/txns/create_collection.js
@@ -12,7 +12,7 @@
load("jstests/libs/create_collection_txn_helpers.js");
load("jstests/libs/fixture_helpers.js"); // for isMongos
-function runCollectionCreateTest(explicitCreate, upsert) {
+function runCollectionCreateTest(command, explicitCreate) {
const session = db.getMongo().startSession();
const collName = "create_new_collection";
const secondCollName = collName + "_second";
@@ -25,14 +25,14 @@ function runCollectionCreateTest(explicitCreate, upsert) {
jsTest.log("Testing createCollection in a transaction");
session.startTransaction({writeConcern: {w: "majority"}});
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
session.commitTransaction();
assert.eq(sessionColl.find({}).itcount(), 1);
jsTest.log("Testing createCollection in a transaction, implicitly creating database");
assert.commandWorked(sessionDB.dropDatabase());
session.startTransaction({writeConcern: {w: "majority"}});
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
session.commitTransaction();
assert.eq(sessionColl.find({}).itcount(), 1);
@@ -40,8 +40,8 @@ function runCollectionCreateTest(explicitCreate, upsert) {
jsTest.log("Testing multiple createCollections in a transaction");
session.startTransaction({writeConcern: {w: "majority"}});
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
- createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
+ createCollAndCRUDInTxn(sessionDB, secondCollName, command, explicitCreate);
session.commitTransaction();
assert.eq(sessionColl.find({}).itcount(), 1);
assert.eq(secondSessionColl.find({}).itcount(), 1);
@@ -51,15 +51,15 @@ function runCollectionCreateTest(explicitCreate, upsert) {
jsTest.log("Testing createCollection in a transaction that aborts");
session.startTransaction({writeConcern: {w: "majority"}});
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
assert.commandWorked(session.abortTransaction_forTesting());
assert.eq(sessionColl.find({}).itcount(), 0);
jsTest.log("Testing multiple createCollections in a transaction that aborts");
session.startTransaction({writeConcern: {w: "majority"}});
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
- createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
+ createCollAndCRUDInTxn(sessionDB, secondCollName, command, explicitCreate);
session.abortTransaction();
assert.eq(sessionColl.find({}).itcount(), 0);
assert.eq(secondSessionColl.find({}).itcount(), 0);
@@ -71,7 +71,7 @@ function runCollectionCreateTest(explicitCreate, upsert) {
"Testing createCollection on an existing collection in a transaction (SHOULD ABORT)");
assert.commandWorked(sessionDB.runCommand({create: collName, writeConcern: {w: "majority"}}));
session.startTransaction({writeConcern: {w: "majority"}});
- createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, secondCollName, command, explicitCreate);
assert.commandFailedWithCode(sessionDB.runCommand({create: collName}),
ErrorCodes.NamespaceExists);
assert.commandFailedWithCode(session.abortTransaction_forTesting(),
@@ -90,7 +90,7 @@ function runCollectionCreateTest(explicitCreate, upsert) {
db.adminCommand({configureFailPoint: "throwWCEDuringTxnCollCreate", mode: "alwaysOn"}));
session.startTransaction({writeConcern: {w: "majority"}});
assertCollCreateFailedWithCode(
- sessionDB, collName, explicitCreate, upsert, ErrorCodes.WriteConflict);
+ sessionDB, collName, command, explicitCreate, ErrorCodes.WriteConflict);
assert.commandFailedWithCode(session.abortTransaction_forTesting(),
ErrorCodes.NoSuchTransaction);
assert.eq(sessionColl.find({}).itcount(), 0);
@@ -101,8 +101,10 @@ function runCollectionCreateTest(explicitCreate, upsert) {
session.endSession();
}
-runCollectionCreateTest(true /*explicitCreate*/, true /*upsert*/);
-runCollectionCreateTest(false /*explicitCreate*/, false /*upsert*/);
-runCollectionCreateTest(true /*explicitCreate*/, false /*upsert*/);
-runCollectionCreateTest(false /*explicitCreate*/, true /*upsert*/);
+runCollectionCreateTest("insert", true /*explicitCreate*/);
+runCollectionCreateTest("insert", false /*explicitCreate*/);
+runCollectionCreateTest("update", true /*explicitCreate*/);
+runCollectionCreateTest("update", false /*explicitCreate*/);
+runCollectionCreateTest("findAndModify", true /*explicitCreate*/);
+runCollectionCreateTest("findAndModify", false /*explicitCreate*/);
}());
diff --git a/jstests/core/txns/create_collection_parallel.js b/jstests/core/txns/create_collection_parallel.js
index fb5fea5668b..861250a733f 100644
--- a/jstests/core/txns/create_collection_parallel.js
+++ b/jstests/core/txns/create_collection_parallel.js
@@ -10,7 +10,7 @@
load("jstests/libs/create_collection_txn_helpers.js");
-function runParallelCollectionCreateTest(explicitCreate, upsert) {
+function runParallelCollectionCreateTest(command, explicitCreate) {
const dbName = "test";
const collName = "create_new_collection";
const distinctCollName = collName + "_second";
@@ -29,7 +29,7 @@ function runParallelCollectionCreateTest(explicitCreate, upsert) {
session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
jsTest.log("Committing transaction 1");
session.commitTransaction();
assert.eq(sessionColl.find({}).itcount(), 1);
@@ -49,8 +49,8 @@ function runParallelCollectionCreateTest(explicitCreate, upsert) {
session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
- createCollAndCRUDInTxn(secondSessionDB, distinctCollName, explicitCreate, upsert);
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(secondSessionDB, distinctCollName, command, explicitCreate);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
jsTest.log("Committing transaction 1");
session.commitTransaction();
assert.eq(sessionColl.find({}).itcount(), 1);
@@ -67,7 +67,7 @@ function runParallelCollectionCreateTest(explicitCreate, upsert) {
jsTest.log("Testing duplicate createCollections, one inside and one outside a txn");
session.startTransaction({writeConcern: {w: "majority"}});
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
assert.commandWorked(secondSessionDB.runCommand({create: collName})); // outside txn
assert.commandWorked(secondSessionDB.getCollection(collName).insert({a: 1}));
@@ -81,10 +81,10 @@ function runParallelCollectionCreateTest(explicitCreate, upsert) {
"Testing duplicate createCollections in parallel, both attempt to commit, second to commit fails");
secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
- createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, command, explicitCreate);
session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
jsTest.log("Committing transaction 2");
secondSession.commitTransaction();
@@ -99,10 +99,10 @@ function runParallelCollectionCreateTest(explicitCreate, upsert) {
assert.commandWorked(sessionDB.dropDatabase());
secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
- createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, command, explicitCreate);
session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
jsTest.log("Committing transaction 2");
secondSession.commitTransaction();
@@ -116,12 +116,12 @@ function runParallelCollectionCreateTest(explicitCreate, upsert) {
"previously committed collection.");
secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
- createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, command, explicitCreate);
session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
createCollAndCRUDInTxn(
- sessionDB, distinctCollName, explicitCreate, upsert); // does not conflict
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); // conflicts
+ sessionDB, distinctCollName, command, explicitCreate); // does not conflict
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate); // conflicts
jsTest.log("Committing transaction 2");
secondSession.commitTransaction();
@@ -135,10 +135,10 @@ function runParallelCollectionCreateTest(explicitCreate, upsert) {
jsTest.log("Testing distinct createCollections in parallel, both successfully commit.");
session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, collName, command, explicitCreate);
secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
- createCollAndCRUDInTxn(secondSessionDB, distinctCollName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(secondSessionDB, distinctCollName, command, explicitCreate);
session.commitTransaction();
secondSession.commitTransaction();
@@ -146,8 +146,10 @@ function runParallelCollectionCreateTest(explicitCreate, upsert) {
secondSession.endSession();
session.endSession();
}
-runParallelCollectionCreateTest(true /*explicitCreate*/, true /*upsert*/);
-runParallelCollectionCreateTest(false /*explicitCreate*/, true /*upsert*/);
-runParallelCollectionCreateTest(true /*explicitCreate*/, false /*upsert*/);
-runParallelCollectionCreateTest(false /*explicitCreate*/, false /*upsert*/);
+runParallelCollectionCreateTest("insert", true /*explicitCreate*/);
+runParallelCollectionCreateTest("insert", false /*explicitCreate*/);
+runParallelCollectionCreateTest("update", true /*explicitCreate*/);
+runParallelCollectionCreateTest("update", false /*explicitCreate*/);
+runParallelCollectionCreateTest("findAndModify", true /*explicitCreate*/);
+runParallelCollectionCreateTest("findAndModify", false /*explicitCreate*/);
}());
diff --git a/jstests/core/txns/implicit_collection_creation_in_txn.js b/jstests/core/txns/implicit_collection_creation_in_txn.js
index 491f1b0ec37..c02bcef9ed4 100644
--- a/jstests/core/txns/implicit_collection_creation_in_txn.js
+++ b/jstests/core/txns/implicit_collection_creation_in_txn.js
@@ -1,5 +1,5 @@
// Tests that it is allowed to implicitly create a collection using insert or upsert in a
-// multi-document transaction, except for via findAndModify.
+// multi-document transaction.
// @tags: [uses_transactions, requires_fcv_44]
(function() {
"use strict";
@@ -60,35 +60,37 @@ assert.commandWorked(sessionColl.update({_id: "doc"}, {$set: {updated: true}}, {
assert.commandWorked(session.commitTransaction_forTesting());
assert.eq(null, testColl.findOne({_id: "doc"}));
-jsTest.log("Cannot implicitly create a collection in a transaction using findAndModify.");
+jsTest.log("Implicitly create a collection in a transaction using findAndModify.");
// findAndModify with upsert=true succeeds when the collection exists.
assert.commandWorked(testDB.createCollection(testColl.getName(), {writeConcern: {w: "majority"}}));
session.startTransaction({writeConcern: {w: "majority"}});
-let res =
- sessionColl.findAndModify({query: {_id: "doc"}, update: {$set: {updated: true}}, upsert: true});
-assert.eq(null, res);
+let res = sessionDb.runCommand(
+ {findAndModify: collName, query: {_id: "doc"}, update: {$set: {updated: true}}, upsert: true});
+assert.commandWorked(res);
+assert.eq(null, res.value);
assert.commandWorked(session.commitTransaction_forTesting());
assert.eq({_id: "doc", updated: true}, testColl.findOne({_id: "doc"}));
-// findAndModify with upsert=true fails when the collection does not exist.
+// findAndModify with upsert=true succeeds when the collection does not exist.
assert.commandWorked(testDB.runCommand({drop: collName, writeConcern: {w: "majority"}}));
session.startTransaction({writeConcern: {w: "majority"}});
-res = assert.throws(() => sessionColl.findAndModify(
- {query: {_id: "doc"}, update: {$set: {updated: true}}, upsert: true}));
-assert.commandFailedWithCode(res, ErrorCodes.OperationNotSupportedInTransaction);
-
-// Committing the transaction should fail, since it should never have been started.
-assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.NoSuchTransaction);
-assert.eq(null, testColl.findOne({_id: "doc"}));
+res = sessionDb.runCommand(
+ {findAndModify: collName, query: {_id: "doc"}, update: {$set: {updated: true}}, upsert: true});
+assert.commandWorked(res);
+assert.eq(null, res.value);
+assert.commandWorked(session.commitTransaction_forTesting());
+assert.eq({_id: "doc", updated: true}, testColl.findOne({_id: "doc"}));
// findAndModify with upsert=false succeeds when the collection does not exist.
+assert.commandWorked(testDB.runCommand({drop: collName, writeConcern: {w: "majority"}}));
session.startTransaction({writeConcern: {w: "majority"}});
-res = sessionColl.findAndModify(
- {query: {_id: "doc"}, update: {$set: {updated: true}}, upsert: false});
-assert.eq(null, res);
+res = sessionDb.runCommand(
+ {findAndModify: collName, query: {_id: "doc"}, update: {$set: {updated: true}}, upsert: false});
+assert.commandWorked(res);
+assert.eq(null, res.value);
assert.commandWorked(session.commitTransaction_forTesting());
assert.eq(null, testColl.findOne({_id: "doc"}));
diff --git a/jstests/libs/create_collection_txn_helpers.js b/jstests/libs/create_collection_txn_helpers.js
index 3b113ebd5f8..d90600749d9 100644
--- a/jstests/libs/create_collection_txn_helpers.js
+++ b/jstests/libs/create_collection_txn_helpers.js
@@ -1,48 +1,59 @@
/**
* Helper function shared by createCollection inside txns tests.
*/
-const createCollAndCRUDInTxn = function(sessionDB, collName, explicitCreate, upsert) {
+const createCollAndCRUDInTxn = function(sessionDB, collName, command, explicitCreate) {
if (undefined === explicitCreate) {
doassert('createCollAndCRUDInTxn called with undefined explicitCreate');
}
- if (undefined === upsert) {
- doassert('createCollAndCRUDInTxn called with undefined upsert');
- }
if (explicitCreate) {
assert.commandWorked(sessionDB.runCommand({create: collName}));
}
let sessionColl = sessionDB[collName];
- if (upsert) {
+ if (command === "insert") {
+ assert.commandWorked(sessionColl.insert({a: 1}));
+ } else if (command === "update") {
assert.commandWorked(sessionColl.update({_id: 1}, {$inc: {a: 1}}, {upsert: true}));
+ } else if (command === "findAndModify") {
+ assert.commandWorked(sessionDB.runCommand(
+ {findAndModify: collName, query: {_id: 1}, update: {$inc: {a: 1}}, upsert: true}));
} else {
- assert.commandWorked(sessionColl.insert({a: 1}));
+ doassert("createCollAndCRUDInTxn called with invalid command. " +
+ "Must be 'insert', 'update', or 'findAndModify'.");
}
assert.eq(sessionColl.find({a: 1}).itcount(), 1);
assert.commandWorked(sessionColl.insert({_id: 2}));
- let resDoc = sessionColl.findAndModify({query: {_id: 2}, update: {$inc: {a: 1}}});
- assert.eq(resDoc._id, 2);
+ let res =
+ sessionDB.runCommand({findAndModify: collName, query: {_id: 2}, update: {$inc: {a: 1}}});
+ assert.commandWorked(res);
+ assert.eq(res.value._id, 2);
assert.commandWorked(sessionColl.update({_id: 2}, {$inc: {a: 1}}));
assert.commandWorked(sessionColl.deleteOne({_id: 2}));
assert.eq(sessionColl.find({}).itcount(), 1);
};
-const assertCollCreateFailedWithCode = function(sessionDB, collName, explicitCreate, upsert, code) {
+const assertCollCreateFailedWithCode = function(
+ sessionDB, collName, command, explicitCreate, code) {
if (undefined === explicitCreate) {
doassert('assertWriteConflictForCollCreate called with undefined explicitCreate');
}
- if (undefined === upsert) {
- doassert('assertWriteConflictForCollCreate called with undefined upsert');
- }
if (undefined === code) {
doassert('assertWriteConflictForCollCreate called with undefined code');
}
let sessionColl = sessionDB[collName];
if (explicitCreate) {
assert.commandFailedWithCode(sessionDB.createCollection(collName), code);
- } else if (upsert) {
+ } else if (command === "insert") {
+ assert.commandFailedWithCode(sessionColl.insert({a: 1}), code);
+ } else if (command === "update") {
assert.commandFailedWithCode(sessionColl.update({_id: 1}, {$inc: {a: 1}}, {upsert: true}),
code);
+ } else if (command === "findAndModify") {
+ assert.commandFailedWithCode(
+ sessionDB.runCommand(
+ {findAndModify: collName, query: {_id: 1}, update: {$inc: {a: 1}}, upsert: true}),
+ code);
} else {
- assert.commandFailedWithCode(sessionColl.insert({a: 1}), code);
+ doassert("assertCollCreateFailedWithCode called with invalid command. " +
+ "Must be 'insert', 'update', or 'findAndModify'.");
}
};
diff --git a/jstests/libs/override_methods/network_error_and_txn_override.js b/jstests/libs/override_methods/network_error_and_txn_override.js
index d1e6442bbca..2ea5a2badb2 100644
--- a/jstests/libs/override_methods/network_error_and_txn_override.js
+++ b/jstests/libs/override_methods/network_error_and_txn_override.js
@@ -695,19 +695,23 @@ function retryWithTxnOverride(res, conn, dbName, cmdName, cmdObj, lsid, logError
assert.gt(ops.length, 0);
abortTransaction(conn, lsid, txnOptions.txnNumber);
- // TODO(SERVER-45956) the below retry logic is necessary because findAndModify with upsert=
- // true is not presently permitted inside multi-document transactions.
- // If the command inserted data and is not supported in a transaction, we assume it
- // failed because the collection did not exist. We will create the collection and retry
- // the entire transaction. We should not receive this error in this override for any
- // other reason.
- // Tests that expect collections to not exist will have to be skipped.
- if (kCmdsThatInsert.has(cmdName) &&
- includesErrorCode(res, ErrorCodes.OperationNotSupportedInTransaction)) {
- const collName = cmdObj[cmdName];
- createCollectionExplicitly(conn, dbName, collName, lsid);
-
- return retryEntireTransaction(conn, lsid);
+ const fcv =
+ conn.getDB("admin").system.version.findOne({_id: 'featureCompatibilityVersion'});
+ if (fcv.version === "4.2" && !fcv.hasOwnProperty("targetVersion")) {
+ // With FCV 4.2, implicit collection creation via commands that insert are not
+ // permitted inside multi-document transactions.
+ // If the command inserted data and is not supported in a transaction, we assume it
+ // failed because the collection did not exist. We will create the collection and retry
+ // the entire transaction. We should not receive this error in this override for any
+ // other reason.
+ // Tests that expect collections to not exist will have to be skipped.
+ if (kCmdsThatInsert.has(cmdName) &&
+ includesErrorCode(res, ErrorCodes.OperationNotSupportedInTransaction)) {
+ const collName = cmdObj[cmdName];
+ createCollectionExplicitly(conn, dbName, collName, lsid);
+
+ return retryEntireTransaction(conn, lsid);
+ }
}
// Transaction statements cannot be retried, but retryable codes are expected to succeed
diff --git a/jstests/replsets/txn_override_unittests.js b/jstests/replsets/txn_override_unittests.js
index 65927310411..8fe114b7789 100644
--- a/jstests/replsets/txn_override_unittests.js
+++ b/jstests/replsets/txn_override_unittests.js
@@ -1149,66 +1149,6 @@ const txnOverrideTests = [
}
},
{
- name: "implicit collection creation with stepdown",
- test: function() {
- // We set a failpoint on "create" since an implicit collection creation via
- // findAndModify inside of a transaction will fail and this suite will attempt to
- // explicitly create the collection outside of a transaction, and then retry the
- // entire transaction.
- failCommandWithFailPoint(["create"], {errorCode: ErrorCodes.NotMaster});
- assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
- }
- },
- {
- name: "implicit collection creation with WriteConcernError",
- test: function() {
- failCommandWithFailPoint(
- ["create"],
- {writeConcernError: {code: ErrorCodes.NotMaster, codeName: "NotMaster"}});
- assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
- }
- },
- {
- name: "implicit collection creation with WriteConcernError and normal stepdown error",
- test: function() {
- failCommandWithErrorAndWCENoRun(
- "create", ErrorCodes.NotMaster, "NotMaster", ErrorCodes.NotMaster, "NotMaster");
- assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
- }
- },
- {
- name: "implicit collection creation with WriteConcernError and normal ordinary error",
- test: function() {
- failCommandWithErrorAndWCENoRun("create",
- ErrorCodes.OperationFailed,
- "OperationFailed",
- ErrorCodes.NotMaster,
- "NotMaster");
- assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
- }
- },
- {
- name: "implicit collection creation with ordinary error",
- test: function() {
- failCommandWithFailPoint(["create"], {errorCode: ErrorCodes.OperationFailed});
- assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
- }
- },
- {
- name: "implicit collection creation with network error",
- test: function() {
- failCommandWithFailPoint(["create"], {closeConnection: true});
- assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
- }
- },
- {
- name: "implicit collection creation with WriteConcernError no success",
- test: function() {
- failCommandWithWCENoRun("create", ErrorCodes.NotMaster, "NotMaster");
- assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
- }
- },
- {
name: "errors cause the override to abort transactions",
test: function() {
assert.commandWorked(testDB.createCollection(collName1));
@@ -1382,6 +1322,69 @@ const txnOverrideTests = [
},
];
+const txnOverrideTestsFcv42 = [
+ {
+ name: "implicit collection creation with stepdown",
+ test: function() {
+ // We set a failpoint on "create" since an implicit collection creation via
+ // findAndModify inside of a transaction will fail and this suite will attempt to
+ // explicitly create the collection outside of a transaction, and then retry the
+ // entire transaction.
+ failCommandWithFailPoint(["create"], {errorCode: ErrorCodes.NotMaster});
+ assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
+ }
+ },
+ {
+ name: "implicit collection creation with WriteConcernError",
+ test: function() {
+ failCommandWithFailPoint(
+ ["create"],
+ {writeConcernError: {code: ErrorCodes.NotMaster, codeName: "NotMaster"}});
+ assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
+ }
+ },
+ {
+ name: "implicit collection creation with WriteConcernError and normal stepdown error",
+ test: function() {
+ failCommandWithErrorAndWCENoRun(
+ "create", ErrorCodes.NotMaster, "NotMaster", ErrorCodes.NotMaster, "NotMaster");
+ assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
+ }
+ },
+ {
+ name: "implicit collection creation with WriteConcernError and normal ordinary error",
+ test: function() {
+ failCommandWithErrorAndWCENoRun("create",
+ ErrorCodes.OperationFailed,
+ "OperationFailed",
+ ErrorCodes.NotMaster,
+ "NotMaster");
+ assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
+ }
+ },
+ {
+ name: "implicit collection creation with ordinary error",
+ test: function() {
+ failCommandWithFailPoint(["create"], {errorCode: ErrorCodes.OperationFailed});
+ assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
+ }
+ },
+ {
+ name: "implicit collection creation with network error",
+ test: function() {
+ failCommandWithFailPoint(["create"], {closeConnection: true});
+ assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
+ }
+ },
+ {
+ name: "implicit collection creation with WriteConcernError no success",
+ test: function() {
+ failCommandWithWCENoRun("create", ErrorCodes.NotMaster, "NotMaster");
+ assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
+ }
+ }
+];
+
// Failpoints, overrides, and post-command functions are set by default to only run once, so
// commands should succeed on retry.
const txnOverridePlusRetryOnNetworkErrorTests = [
@@ -1459,112 +1462,6 @@ const txnOverridePlusRetryOnNetworkErrorTests = [
}
},
{
- name: "implicit collection creation with stepdown",
- test: function() {
- assert.commandWorked(testDB.createCollection(collName1));
- failCommandWithFailPoint(["create"], {errorCode: ErrorCodes.NotMaster});
- let resDoc1 = coll1.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc1.a, 1);
- let resDoc2 = coll2.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc2.a, 1);
- assert.eq(coll1.find().itcount(), 1);
- assert.eq(coll2.find().itcount(), 1);
-
- endCurrentTransactionIfOpen();
- assert.eq(coll1.find().itcount(), 1);
- assert.eq(coll2.find().itcount(), 1);
- }
- },
- {
- name: "implicit collection creation with WriteConcernError",
- test: function() {
- assert.commandWorked(testDB.createCollection(collName1));
- failCommandWithFailPoint(
- ["create"],
- {writeConcernError: {code: ErrorCodes.NotMaster, codeName: "NotMaster"}});
- let resDoc1 = coll1.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc1.a, 1);
- let resDoc2 = coll2.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc2.a, 1);
- assert.eq(coll1.find().itcount(), 1);
- assert.eq(coll2.find().itcount(), 1);
-
- endCurrentTransactionIfOpen();
- assert.eq(coll1.find().itcount(), 1);
- assert.eq(coll2.find().itcount(), 1);
- }
- },
- {
- name: "implicit collection creation with WriteConcernError and normal stepdown error",
- test: function() {
- assert.commandWorked(testDB.createCollection(collName1));
- failCommandWithErrorAndWCENoRun(
- "create", ErrorCodes.NotMaster, "NotMaster", ErrorCodes.NotMaster, "NotMaster");
- let resDoc1 = coll1.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc1.a, 1);
- let resDoc2 = coll2.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc2.a, 1);
- assert.eq(coll1.find().itcount(), 1);
- assert.eq(coll2.find().itcount(), 1);
-
- endCurrentTransactionIfOpen();
- assert.eq(coll1.find().itcount(), 1);
- assert.eq(coll2.find().itcount(), 1);
- }
- },
- {
- name: "implicit collection creation with WriteConcernError and normal ordinary error",
- test: function() {
- failCommandWithErrorAndWCENoRun("create",
- ErrorCodes.OperationFailed,
- "OperationFailed",
- ErrorCodes.NotMaster,
- "NotMaster");
- assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
- }
- },
- {
- name: "implicit collection creation with ordinary error",
- test: function() {
- failCommandWithFailPoint(["create"], {errorCode: ErrorCodes.OperationFailed});
- assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
- }
- },
- {
- name: "implicit collection creation with network error",
- test: function() {
- assert.commandWorked(testDB.createCollection(collName1));
- failCommandWithFailPoint(["create"], {closeConnection: true});
- let resDoc1 = coll1.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc1.a, 1);
- let resDoc2 = coll2.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc2.a, 1);
- assert.eq(coll1.find().itcount(), 1);
- assert.eq(coll2.find().itcount(), 1);
-
- endCurrentTransactionIfOpen();
- assert.eq(coll1.find().itcount(), 1);
- assert.eq(coll2.find().itcount(), 1);
- }
- },
- {
- name: "implicit collection creation with WriteConcernError no success",
- test: function() {
- assert.commandWorked(testDB.createCollection(collName1));
- failCommandWithWCENoRun("create", ErrorCodes.NotMaster, "NotMaster");
- let resDoc1 = coll1.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc1.a, 1);
- let resDoc2 = coll2.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc2.a, 1);
- assert.eq(coll1.find().itcount(), 1);
- assert.eq(coll2.find().itcount(), 1);
-
- endCurrentTransactionIfOpen();
- assert.eq(coll1.find().itcount(), 1);
- assert.eq(coll2.find().itcount(), 1);
- }
- },
- {
name: "update with stepdown",
test: function() {
assert.commandWorked(testDB.createCollection(collName1));
@@ -1821,33 +1718,6 @@ const txnOverridePlusRetryOnNetworkErrorTests = [
}
},
{
- name: "commitTransaction fails with SERVER-38856",
- test: function() {
- assert.commandWorked(testDB.createCollection(collName1));
- failCommandWithFailPoint(
- ["create"],
- {writeConcernError: {code: ErrorCodes.NotMaster, codeName: "NotMaster"}});
-
- // After commitTransaction fails, abort the transaction and drop the collection
- // as if the transaction were being retried on a different node.
- attachPostCmdFunction("commitTransaction", function() {
- abortCurrentTransaction();
- assert.commandWorked(mongoRunCommandOriginal.apply(testDB.getMongo(),
- [dbName, {drop: collName2}, 0]));
- });
- failCommandWithWCENoRun("commitTransaction", ErrorCodes.NotMaster, "NotMaster");
- assert.commandWorked(coll1.insert({_id: 1, x: 2}));
- let resDoc2 = coll2.findAndModify(({update: {_id: 1}, upsert: true, 'new': true}));
- assert.eq(resDoc2._id, 1);
- assert.commandWorked(coll1.update({_id: 1}, {$inc: {x: 4}}));
-
- endCurrentTransactionIfOpen();
-
- assert.docEq(coll1.find().toArray(), [{_id: 1, x: 6}]);
- assert.docEq(coll2.find().toArray(), [resDoc2]);
- }
- },
- {
name: 'Dates are copied correctly for SERVER-41917',
test: function() {
assert.commandWorked(testDB.createCollection(collName1));
@@ -1890,6 +1760,142 @@ const txnOverridePlusRetryOnNetworkErrorTests = [
}
];
+const txnOverridePlusRetryOnNetworkErrorTestsFcv42 = [
+ {
+ name: "implicit collection creation with stepdown",
+ test: function() {
+ assert.commandWorked(testDB.createCollection(collName1));
+ failCommandWithFailPoint(["create"], {errorCode: ErrorCodes.NotMaster});
+ let resDoc1 = coll1.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc1.a, 1);
+ let resDoc2 = coll2.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc2.a, 1);
+ assert.eq(coll1.find().itcount(), 1);
+ assert.eq(coll2.find().itcount(), 1);
+
+ endCurrentTransactionIfOpen();
+ assert.eq(coll1.find().itcount(), 1);
+ assert.eq(coll2.find().itcount(), 1);
+ }
+ },
+ {
+ name: "implicit collection creation with WriteConcernError",
+ test: function() {
+ assert.commandWorked(testDB.createCollection(collName1));
+ failCommandWithFailPoint(
+ ["create"],
+ {writeConcernError: {code: ErrorCodes.NotMaster, codeName: "NotMaster"}});
+ let resDoc1 = coll1.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc1.a, 1);
+ let resDoc2 = coll2.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc2.a, 1);
+ assert.eq(coll1.find().itcount(), 1);
+ assert.eq(coll2.find().itcount(), 1);
+
+ endCurrentTransactionIfOpen();
+ assert.eq(coll1.find().itcount(), 1);
+ assert.eq(coll2.find().itcount(), 1);
+ }
+ },
+ {
+ name: "implicit collection creation with WriteConcernError and normal stepdown error",
+ test: function() {
+ assert.commandWorked(testDB.createCollection(collName1));
+ failCommandWithErrorAndWCENoRun(
+ "create", ErrorCodes.NotMaster, "NotMaster", ErrorCodes.NotMaster, "NotMaster");
+ let resDoc1 = coll1.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc1.a, 1);
+ let resDoc2 = coll2.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc2.a, 1);
+ assert.eq(coll1.find().itcount(), 1);
+ assert.eq(coll2.find().itcount(), 1);
+
+ endCurrentTransactionIfOpen();
+ assert.eq(coll1.find().itcount(), 1);
+ assert.eq(coll2.find().itcount(), 1);
+ }
+ },
+ {
+ name: "implicit collection creation with WriteConcernError and normal ordinary error",
+ test: function() {
+ failCommandWithErrorAndWCENoRun("create",
+ ErrorCodes.OperationFailed,
+ "OperationFailed",
+ ErrorCodes.NotMaster,
+ "NotMaster");
+ assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
+ }
+ },
+ {
+ name: "implicit collection creation with ordinary error",
+ test: function() {
+ failCommandWithFailPoint(["create"], {errorCode: ErrorCodes.OperationFailed});
+ assert.throws(() => coll1.findAndModify(({update: {a: 1}, upsert: true})));
+ }
+ },
+ {
+ name: "implicit collection creation with network error",
+ test: function() {
+ assert.commandWorked(testDB.createCollection(collName1));
+ failCommandWithFailPoint(["create"], {closeConnection: true});
+ let resDoc1 = coll1.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc1.a, 1);
+ let resDoc2 = coll2.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc2.a, 1);
+ assert.eq(coll1.find().itcount(), 1);
+ assert.eq(coll2.find().itcount(), 1);
+
+ endCurrentTransactionIfOpen();
+ assert.eq(coll1.find().itcount(), 1);
+ assert.eq(coll2.find().itcount(), 1);
+ }
+ },
+ {
+ name: "implicit collection creation with WriteConcernError no success",
+ test: function() {
+ assert.commandWorked(testDB.createCollection(collName1));
+ failCommandWithWCENoRun("create", ErrorCodes.NotMaster, "NotMaster");
+ let resDoc1 = coll1.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc1.a, 1);
+ let resDoc2 = coll2.findAndModify(({update: {a: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc2.a, 1);
+ assert.eq(coll1.find().itcount(), 1);
+ assert.eq(coll2.find().itcount(), 1);
+
+ endCurrentTransactionIfOpen();
+ assert.eq(coll1.find().itcount(), 1);
+ assert.eq(coll2.find().itcount(), 1);
+ }
+ },
+ {
+ name: "commitTransaction fails with SERVER-38856",
+ test: function() {
+ assert.commandWorked(testDB.createCollection(collName1));
+ failCommandWithFailPoint(
+ ["create"],
+ {writeConcernError: {code: ErrorCodes.NotMaster, codeName: "NotMaster"}});
+
+ // After commitTransaction fails, abort the transaction and drop the collection
+ // as if the transaction were being retried on a different node.
+ attachPostCmdFunction("commitTransaction", function() {
+ abortCurrentTransaction();
+ assert.commandWorked(mongoRunCommandOriginal.apply(testDB.getMongo(),
+ [dbName, {drop: collName2}, 0]));
+ });
+ failCommandWithWCENoRun("commitTransaction", ErrorCodes.NotMaster, "NotMaster");
+ assert.commandWorked(coll1.insert({_id: 1, x: 2}));
+ let resDoc2 = coll2.findAndModify(({update: {_id: 1}, upsert: true, 'new': true}));
+ assert.eq(resDoc2._id, 1);
+ assert.commandWorked(coll1.update({_id: 1}, {$inc: {x: 4}}));
+
+ endCurrentTransactionIfOpen();
+
+ assert.docEq(coll1.find().toArray(), [{_id: 1, x: 6}]);
+ assert.docEq(coll2.find().toArray(), [resDoc2]);
+ }
+ }
+];
+
TestData.networkErrorAndTxnOverrideConfig = {};
TestData.sessionOptions = new SessionOptions();
TestData.overrideRetryAttempts = 3;
@@ -1897,6 +1903,9 @@ TestData.overrideRetryAttempts = 3;
let session = conn.startSession(TestData.sessionOptions);
let testDB = session.getDatabase(dbName);
+const fcv = conn.getDB("admin").system.version.findOne({_id: 'featureCompatibilityVersion'});
+const usingFcv42 = fcv.version === "4.2" && !fcv.hasOwnProperty("targetVersion");
+
load("jstests/libs/override_methods/network_error_and_txn_override.js");
jsTestLog("=-=-=-=-=-= Testing with 'retry on network error' by itself. =-=-=-=-=-=");
@@ -1922,6 +1931,9 @@ coll1 = testDB[collName1];
coll2 = testDB[collName2];
txnOverrideTests.forEach((testCase) => runTest("txnOverrideTests", testCase));
+if (usingFcv42) {
+ txnOverrideTestsFcv42.forEach((testCase) => runTest("txnOverrideTestsFcv42", testCase));
+}
jsTestLog("=-=-=-=-=-= Testing 'both txn override and retry on network error'. =-=-=-=-=-=");
TestData.sessionOptions = new SessionOptions({retryWrites: true});
@@ -1935,6 +1947,10 @@ coll2 = testDB[collName2];
txnOverridePlusRetryOnNetworkErrorTests.forEach(
(testCase) => runTest("txnOverridePlusRetryOnNetworkErrorTests", testCase));
+if (usingFcv42) {
+ txnOverridePlusRetryOnNetworkErrorTestsFcv42.forEach(
+ (testCase) => runTest("txnOverridePlusRetryOnNetworkErrorTestsFcv42", testCase));
+}
rst.stopSet();
})();
diff --git a/src/mongo/db/commands/find_and_modify.cpp b/src/mongo/db/commands/find_and_modify.cpp
index b95c29c1bb4..819e7138ae1 100644
--- a/src/mongo/db/commands/find_and_modify.cpp
+++ b/src/mongo/db/commands/find_and_modify.cpp
@@ -41,6 +41,7 @@
#include "mongo/db/client.h"
#include "mongo/db/commands.h"
#include "mongo/db/commands/find_and_modify_common.h"
+#include "mongo/db/commands_in_multi_doc_txn_params_gen.h"
#include "mongo/db/concurrency/write_conflict_exception.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/exec/delete.h"
@@ -454,13 +455,22 @@ public:
// Create the collection if it does not exist when performing an upsert because the
// update stage does not create its own collection
if (!collection && args.isUpsert()) {
- // We do not allow acquisition of exclusive locks inside multi-document
- // transactions, so fail early if we are inside of such a transaction.
- // TODO(SERVER-45956) remove below assertion.
+ auto isFullyUpgradedTo44 =
+ (serverGlobalParams.featureCompatibility.isVersionInitialized() &&
+ serverGlobalParams.featureCompatibility.getVersion() ==
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo44);
+
+ uassert(ErrorCodes::OperationNotSupportedInTransaction,
+ str::stream() << "Cannot create namespace " << nsString.ns()
+ << " in multi-document transaction unless "
+ "featureCompatibilityVersion is 4.4.",
+ isFullyUpgradedTo44 || !inTransaction);
+
uassert(ErrorCodes::OperationNotSupportedInTransaction,
str::stream() << "Cannot create namespace " << nsString.ns()
- << " in multi-document transaction.",
- !inTransaction);
+ << " because creation of collections and indexes inside "
+ "multi-document transactions is disabled.",
+ !inTransaction || gShouldMultiDocTxnCreateCollectionAndIndexes.load());
assertCanWrite(opCtx, nsString);