diff options
author | Gregory Noma <gregory.noma@gmail.com> | 2020-04-10 17:16:51 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-04-10 21:28:00 +0000 |
commit | 7a4da2ea31f9c699e9458853d9029bd3a1f7de75 (patch) | |
tree | 11ffb9da88a4116bc52da77b5d67f2d7b019f73f | |
parent | 880dd3e91f5b38105d909072d8d036378575d93d (diff) | |
download | mongo-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.js | 4 | ||||
-rw-r--r-- | jstests/core/find_and_modify.js | 65 | ||||
-rw-r--r-- | jstests/core/txns/commands_in_txns_read_concern.js | 4 | ||||
-rw-r--r-- | jstests/core/txns/concurrent_drops_and_creates.js | 26 | ||||
-rw-r--r-- | jstests/core/txns/create_collection.js | 30 | ||||
-rw-r--r-- | jstests/core/txns/create_collection_parallel.js | 38 | ||||
-rw-r--r-- | jstests/core/txns/implicit_collection_creation_in_txn.js | 34 | ||||
-rw-r--r-- | jstests/libs/create_collection_txn_helpers.js | 39 | ||||
-rw-r--r-- | jstests/libs/override_methods/network_error_and_txn_override.js | 30 | ||||
-rw-r--r-- | jstests/replsets/txn_override_unittests.js | 402 | ||||
-rw-r--r-- | src/mongo/db/commands/find_and_modify.cpp | 20 |
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); |