summaryrefslogtreecommitdiff
path: root/jstests
diff options
context:
space:
mode:
authorMaria van Keulen <maria@mongodb.com>2020-03-09 11:31:34 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-03-12 16:12:40 +0000
commit90c3f10389519068eb685db72b7ff13f93c71dc6 (patch)
treec99bc77ca9b713b73270deeabd4c0c47dfac3d84 /jstests
parentdb3a17bbfe2e265722ed88df961e79f3e1a68067 (diff)
downloadmongo-90c3f10389519068eb685db72b7ff13f93c71dc6.tar.gz
SERVER-44409 Add FSM and nice-to-have collection and index test cases
Diffstat (limited to 'jstests')
-rw-r--r--jstests/concurrency/fsm_workloads/CRUD_and_commands.js265
-rw-r--r--jstests/core/txns/create_collection.js29
-rw-r--r--jstests/core/txns/create_collection_parallel.js56
-rw-r--r--jstests/core/txns/create_indexes_parallel.js32
-rw-r--r--jstests/libs/create_collection_txn_helpers.js19
-rw-r--r--jstests/libs/create_index_txn_helpers.js3
-rw-r--r--jstests/sharding/create_existing_indexes_prepared_transactions.js112
-rw-r--r--jstests/sharding/create_new_collections_prepared_transactions.js64
8 files changed, 556 insertions, 24 deletions
diff --git a/jstests/concurrency/fsm_workloads/CRUD_and_commands.js b/jstests/concurrency/fsm_workloads/CRUD_and_commands.js
new file mode 100644
index 00000000000..5e7b24c9c0a
--- /dev/null
+++ b/jstests/concurrency/fsm_workloads/CRUD_and_commands.js
@@ -0,0 +1,265 @@
+'use strict';
+
+/**
+ * Perform CRUD operations, some of which may implicitly create collections. Also perform index
+ * creations which may implicitly create collections. Performs these in parallel with collection-
+ * dropping operations.
+ */
+
+var $config = (function() {
+ const data = {numIds: 10};
+
+ const states = {
+ init: function init(db, collName) {
+ this.session = db.getMongo().startSession({causalConsistency: true});
+ this.sessionDb = this.session.getDatabase(db.getName());
+ this.docValue = "mydoc";
+ },
+
+ insertDocs: function insertDocs(db, collName) {
+ try {
+ for (let i = 0; i < 5; i++) {
+ const res = db[collName].insert({value: this.docValue, num: 1});
+ assertWhenOwnColl.commandWorked(res);
+ assertWhenOwnColl.eq(1, res.nInserted);
+ }
+ } catch (e) {
+ if (e.code == ErrorCodes.ConflictingOperationInProgress) {
+ // dropCollection in sharding can disrupt routing cache refreshes.
+ if (TestData.runInsideTransaction) {
+ e["errorLabels"] = ["TransientTransactionError"];
+ throw e;
+ }
+ }
+ }
+ },
+
+ updateDocs: function updateDocs(db, collName) {
+ for (let i = 0; i < 5; ++i) {
+ let indexToUpdate = Math.floor(Math.random() * this.numIds);
+ let res;
+ try {
+ res =
+ db[collName].update({_id: indexToUpdate}, {$inc: {num: 1}}, {upsert: true});
+ assertWhenOwnColl.commandWorked(res);
+ } catch (e) {
+ // We propagate TransientTransactionErrors to allow the state function to
+ // automatically be retried when TestData.runInsideTransaction=true
+ if (e.hasOwnProperty('errorLabels') &&
+ e.errorLabels.includes('TransientTransactionError')) {
+ throw e;
+ } else if (e.code == ErrorCodes.ConflictingOperationInProgress) {
+ // dropCollection in sharding can disrupt routing cache refreshes.
+ if (TestData.runInsideTransaction) {
+ e["errorLabels"] = ["TransientTransactionError"];
+ throw e;
+ }
+ } else if (e.code == ErrorCodes.QueryPlanKilled ||
+ e.code == ErrorCodes.OperationFailed) {
+ // dropIndex can cause queries to throw if these queries
+ // yield.
+ } else {
+ // TODO(SERVER-46651) upsert with concurrent dropCollection can result in
+ // writeErrors if queries yield.
+ assertAlways.writeError(res, "unexpected error: " + tojsononeline(e));
+ }
+ }
+ }
+ },
+
+ readDocs: function readDocs(db, collName) {
+ for (let i = 0; i < 5; ++i) {
+ try {
+ let res = db[collName].findOne({value: this.docValue});
+ if (res !== null) {
+ assertAlways.eq(this.docValue, res.value);
+ }
+ } catch (e) {
+ // We propagate TransientTransactionErrors to allow the state function to
+ // automatically be retried when TestData.runInsideTransaction=true
+ if (e.hasOwnProperty('errorLabels') &&
+ e.errorLabels.includes('TransientTransactionError')) {
+ throw e;
+ } else if (e.code == ErrorCodes.ConflictingOperationInProgress) {
+ // dropCollection in sharding can disrupt routing cache refreshes.
+ if (TestData.runInsideTransaction) {
+ e["errorLabels"] = ["TransientTransactionError"];
+ throw e;
+ }
+ } else {
+ // dropIndex can cause queries to throw if these queries yield.
+ assertAlways.contains(
+ e.code,
+ [
+ ErrorCodes.QueryPlanKilled,
+ ErrorCodes.OperationFailed,
+ ],
+ 'unexpected error code: ' + e.code + ': ' + e.message);
+ }
+ }
+ }
+ },
+
+ deleteDocs: function deleteDocs(db, collName) {
+ let indexToDelete = Math.floor(Math.random() * this.numIds);
+ try {
+ db[collName].deleteOne({_id: indexToDelete});
+ } catch (e) {
+ // We propagate TransientTransactionErrors to allow the state function to
+ // automatically be retried when TestData.runInsideTransaction=true
+ if (e.hasOwnProperty('errorLabels') &&
+ e.errorLabels.includes('TransientTransactionError')) {
+ throw e;
+ } else if (e.code == ErrorCodes.ConflictingOperationInProgress) {
+ if (TestData.runInsideTransaction) {
+ e["errorLabels"] = ["TransientTransactionError"];
+ throw e;
+ }
+ } else {
+ // dropIndex can cause queries to throw if these queries yield.
+ assertAlways.contains(e.code,
+ [ErrorCodes.QueryPlanKilled, ErrorCodes.OperationFailed],
+ 'unexpected error code: ' + e.code + ': ' + e.message);
+ }
+ }
+ },
+
+ createIndex: function createIndex(db, collName) {
+ db[collName].createIndex({value: 1});
+ },
+
+ createIdIndex: function createIdIndex(db, collName) {
+ try {
+ assertWhenOwnColl.commandWorked(db[collName].createIndex({_id: 1}));
+ } catch (e) {
+ if (e.code == ErrorCodes.ConflictingOperationInProgress) {
+ // createIndex concurrently with dropCollection can throw.
+ if (TestData.runInsideTransaction) {
+ e["errorLabels"] = ["TransientTransactionError"];
+ throw e;
+ }
+ }
+ }
+ },
+
+ dropIndex: function dropIndex(db, collName) {
+ db[collName].dropIndex({value: 1});
+ },
+
+ dropCollection: function dropCollection(db, collName) {
+ db[collName].drop();
+ }
+ };
+
+ const transitions = {
+ init: {
+ insertDocs: 0.10,
+ updateDocs: 0.10,
+ readDocs: 0.10,
+ deleteDocs: 0.10,
+ createIndex: 0.10,
+ createIdIndex: 0.10,
+ dropIndex: 0.10,
+ dropCollection: 0.10,
+ },
+ insertDocs: {
+ insertDocs: 0.10,
+ updateDocs: 0.10,
+ readDocs: 0.10,
+ deleteDocs: 0.10,
+ createIndex: 0.10,
+ createIdIndex: 0.10,
+ dropIndex: 0.10,
+ dropCollection: 0.30,
+ },
+ updateDocs: {
+ insertDocs: 0.10,
+ updateDocs: 0.10,
+ readDocs: 0.10,
+ deleteDocs: 0.10,
+ createIndex: 0.10,
+ createIdIndex: 0.10,
+ dropIndex: 0.10,
+ dropCollection: 0.30,
+ },
+ readDocs: {
+ insertDocs: 0.10,
+ updateDocs: 0.10,
+ readDocs: 0.10,
+ deleteDocs: 0.10,
+ createIndex: 0.10,
+ createIdIndex: 0.10,
+ dropIndex: 0.10,
+ dropCollection: 0.30,
+ },
+ deleteDocs: {
+ insertDocs: 0.10,
+ updateDocs: 0.10,
+ readDocs: 0.10,
+ deleteDocs: 0.10,
+ createIndex: 0.10,
+ createIdIndex: 0.10,
+ dropIndex: 0.10,
+ dropCollection: 0.30,
+ },
+ createIndex: {
+ insertDocs: 0.10,
+ updateDocs: 0.10,
+ readDocs: 0.10,
+ deleteDocs: 0.10,
+ createIndex: 0.10,
+ createIdIndex: 0.10,
+ dropIndex: 0.10,
+ dropCollection: 0.30,
+ },
+ createIdIndex: {
+ insertDocs: 0.10,
+ updateDocs: 0.10,
+ readDocs: 0.10,
+ deleteDocs: 0.10,
+ createIndex: 0.10,
+ createIdIndex: 0.10,
+ dropIndex: 0.10,
+ dropCollection: 0.30,
+ },
+ dropIndex: {
+ insertDocs: 0.10,
+ updateDocs: 0.10,
+ readDocs: 0.10,
+ deleteDocs: 0.10,
+ createIndex: 0.10,
+ createIdIndex: 0.10,
+ dropIndex: 0.10,
+ dropCollection: 0.30,
+ },
+ dropCollection: {
+ insertDocs: 0.10,
+ updateDocs: 0.10,
+ readDocs: 0.10,
+ deleteDocs: 0.10,
+ createIndex: 0.10,
+ createIdIndex: 0.10,
+ dropIndex: 0.10,
+ dropCollection: 0.10,
+ }
+ };
+
+ function setup(db, collName, cluster) {
+ assertAlways.commandWorked(db.runCommand({create: collName}));
+ for (let i = 0; i < this.numIds; i++) {
+ const res = db[collName].insert({_id: i, value: this.docValue, num: 1});
+ assertAlways.commandWorked(res);
+ assert.eq(1, res.nInserted);
+ }
+ }
+
+ return {
+ threadCount: 5,
+ iterations: 20,
+ startState: 'init',
+ states: states,
+ transitions: transitions,
+ setup: setup,
+ data: data,
+ };
+})();
diff --git a/jstests/core/txns/create_collection.js b/jstests/core/txns/create_collection.js
index 4f49e1d4ad9..be96ebdb740 100644
--- a/jstests/core/txns/create_collection.js
+++ b/jstests/core/txns/create_collection.js
@@ -11,7 +11,7 @@
load("jstests/libs/create_collection_txn_helpers.js");
-function runCollectionCreateTest(explicitCreate) {
+function runCollectionCreateTest(explicitCreate, upsert) {
const session = db.getMongo().startSession({causalConsistency: false});
const collName = "create_new_collection";
const secondCollName = collName + "_second";
@@ -24,7 +24,14 @@ function runCollectionCreateTest(explicitCreate) {
jsTest.log("Testing createCollection in a transaction");
session.startTransaction({writeConcern: {w: "majority"}});
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate);
+ createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ 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);
session.commitTransaction();
assert.eq(sessionColl.find({}).itcount(), 1);
@@ -32,8 +39,8 @@ function runCollectionCreateTest(explicitCreate) {
jsTest.log("Testing multiple createCollections in a transaction");
session.startTransaction({writeConcern: {w: "majority"}});
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate);
- createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate);
+ createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate, upsert);
session.commitTransaction();
assert.eq(sessionColl.find({}).itcount(), 1);
assert.eq(secondSessionColl.find({}).itcount(), 1);
@@ -43,15 +50,15 @@ function runCollectionCreateTest(explicitCreate) {
jsTest.log("Testing createCollection in a transaction that aborts");
session.startTransaction({writeConcern: {w: "majority"}});
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate);
+ createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
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);
- createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate);
+ createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate, upsert);
session.abortTransaction();
assert.eq(sessionColl.find({}).itcount(), 0);
assert.eq(secondSessionColl.find({}).itcount(), 0);
@@ -63,7 +70,7 @@ function runCollectionCreateTest(explicitCreate) {
"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);
+ createCollAndCRUDInTxn(sessionDB, secondCollName, explicitCreate, upsert);
assert.commandFailedWithCode(sessionDB.runCommand({create: collName}),
ErrorCodes.NamespaceExists);
assert.commandFailedWithCode(session.abortTransaction_forTesting(),
@@ -75,6 +82,8 @@ function runCollectionCreateTest(explicitCreate) {
session.endSession();
}
-runCollectionCreateTest(true /*explicitCreate*/);
-runCollectionCreateTest(false /*explicitCreate*/);
+runCollectionCreateTest(true /*explicitCreate*/, true /*upsert*/);
+runCollectionCreateTest(false /*explicitCreate*/, false /*upsert*/);
+runCollectionCreateTest(true /*explicitCreate*/, false /*upsert*/);
+runCollectionCreateTest(false /*explicitCreate*/, true /*upsert*/);
}());
diff --git a/jstests/core/txns/create_collection_parallel.js b/jstests/core/txns/create_collection_parallel.js
index 0f034f8e713..5ff28a06001 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) {
+function runParallelCollectionCreateTest(explicitCreate, upsert) {
const dbName = "test";
const collName = "create_new_collection";
const distinctCollName = collName + "_second";
@@ -30,7 +30,7 @@ function runParallelCollectionCreateTest(explicitCreate) {
session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate);
+ createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
jsTest.log("Committing transaction 1");
session.commitTransaction();
assert.eq(sessionColl.find({}).itcount(), 1);
@@ -43,14 +43,26 @@ function runParallelCollectionCreateTest(explicitCreate) {
sessionColl.drop({writeConcern: {w: "majority"}});
+ jsTest.log("Testing duplicate createCollections, one inside and one outside a txn");
+ session.startTransaction({writeConcern: {w: "majority"}});
+ createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+ assert.commandWorked(secondSessionDB.runCommand({create: collName})); // outside txn
+ assert.commandWorked(secondSessionDB.getCollection(collName).insert({a: 1}));
+
+ jsTest.log("Committing transaction (SHOULD FAIL)");
+ assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.WriteConflict);
+ assert.eq(sessionColl.find({}).itcount(), 1);
+
+ sessionColl.drop({writeConcern: {w: "majority"}});
+
jsTest.log(
"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);
+ createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate, upsert);
session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate);
+ createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
jsTest.log("Committing transaction 2");
secondSession.commitTransaction();
@@ -59,17 +71,35 @@ function runParallelCollectionCreateTest(explicitCreate) {
assert.eq(sessionColl.find({}).itcount(), 1);
sessionColl.drop({writeConcern: {w: "majority"}});
+
+ jsTest.log("Testing duplicate createCollections which implicitly create databases in parallel" +
+ ", both attempt to commit, second to commit fails");
+
+ assert.commandWorked(sessionDB.dropDatabase());
+ secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
+ createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate, upsert);
+
+ session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
+ createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
+
+ jsTest.log("Committing transaction 2");
+ secondSession.commitTransaction();
+ jsTest.log("Committing transaction 1 (SHOULD FAIL)");
+ assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.WriteConflict);
+ assert.eq(sessionColl.find({}).itcount(), 1);
+ sessionColl.drop({writeConcern: {w: "majority"}});
distinctSessionColl.drop({writeConcern: {w: "majority"}});
jsTest.log("Testing createCollection conflict during commit, where the conflict rolls back a " +
"previously committed collection.");
secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
- createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate);
+ createCollAndCRUDInTxn(secondSession.getDatabase("test"), collName, explicitCreate, upsert);
- session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
- createCollAndCRUDInTxn(sessionDB, distinctCollName, explicitCreate); // does not conflict
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate); // conflicts
+ session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
+ createCollAndCRUDInTxn(
+ sessionDB, distinctCollName, explicitCreate, upsert); // does not conflict
+ createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert); // conflicts
jsTest.log("Committing transaction 2");
secondSession.commitTransaction();
@@ -83,10 +113,10 @@ function runParallelCollectionCreateTest(explicitCreate) {
jsTest.log("Testing distinct createCollections in parallel, both successfully commit.");
session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
- createCollAndCRUDInTxn(sessionDB, collName, explicitCreate);
+ createCollAndCRUDInTxn(sessionDB, collName, explicitCreate, upsert);
secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
- createCollAndCRUDInTxn(secondSessionDB, distinctCollName, explicitCreate);
+ createCollAndCRUDInTxn(secondSessionDB, distinctCollName, explicitCreate, upsert);
session.commitTransaction();
secondSession.commitTransaction();
@@ -94,6 +124,8 @@ function runParallelCollectionCreateTest(explicitCreate) {
secondSession.endSession();
session.endSession();
}
-runParallelCollectionCreateTest(true /*explicitCreate*/);
-runParallelCollectionCreateTest(false /*explicitCreate*/);
+runParallelCollectionCreateTest(true /*explicitCreate*/, true /*upsert*/);
+runParallelCollectionCreateTest(false /*explicitCreate*/, true /*upsert*/);
+runParallelCollectionCreateTest(true /*explicitCreate*/, false /*upsert*/);
+runParallelCollectionCreateTest(false /*explicitCreate*/, false /*upsert*/);
}());
diff --git a/jstests/core/txns/create_indexes_parallel.js b/jstests/core/txns/create_indexes_parallel.js
index 80d5d3dbe31..6741fd80632 100644
--- a/jstests/core/txns/create_indexes_parallel.js
+++ b/jstests/core/txns/create_indexes_parallel.js
@@ -88,6 +88,38 @@ let doParallelCreateIndexesTest = function(explicitCollectionCreate, multikeyInd
assert.eq(sessionColl.getIndexes().length, 2);
sessionColl.drop({writeConcern: {w: "majority"}});
+
+ jsTest.log("Testing createIndexes inside txn and createCollection on conflicting collection " +
+ "in parallel.");
+ session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
+ createIndexAndCRUDInTxn(sessionDB, collName, explicitCollectionCreate, multikeyIndex);
+ assert.commandWorked(secondSessionDB.createCollection(collName));
+ assert.commandWorked(secondSessionDB.getCollection(collName).insert({a: 1}));
+
+ jsTest.log("Committing transaction (SHOULD FAIL)");
+ assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.WriteConflict);
+ assert.eq(sessionColl.find({}).itcount(), 1);
+ assert.eq(sessionColl.getIndexes().length, 1);
+
+ assert.commandWorked(sessionDB.dropDatabase());
+ jsTest.log("Testing duplicate createIndexes which implicitly create a database in parallel" +
+ ", both attempt to commit, second to commit fails");
+
+ secondSession.startTransaction({writeConcern: {w: "majority"}}); // txn 2
+ createIndexAndCRUDInTxn(secondSessionDB, collName, explicitCollectionCreate, multikeyIndex);
+
+ session.startTransaction({writeConcern: {w: "majority"}}); // txn 1
+ createIndexAndCRUDInTxn(sessionDB, collName, explicitCollectionCreate, multikeyIndex);
+
+ jsTest.log("Committing transaction 2");
+ secondSession.commitTransaction();
+
+ jsTest.log("Committing transaction 1 (SHOULD FAIL)");
+ assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.WriteConflict);
+ assert.eq(sessionColl.find({}).itcount(), 1);
+ assert.eq(sessionColl.getIndexes().length, 2);
+
+ sessionColl.drop({writeConcern: {w: "majority"}});
secondSessionColl.drop({writeConcern: {w: "majority"}});
distinctSessionColl.drop({writeConcern: {w: "majority"}});
diff --git a/jstests/libs/create_collection_txn_helpers.js b/jstests/libs/create_collection_txn_helpers.js
index 9b6ba15eac8..08d7093a5c7 100644
--- a/jstests/libs/create_collection_txn_helpers.js
+++ b/jstests/libs/create_collection_txn_helpers.js
@@ -1,12 +1,27 @@
/**
* Helper function shared by createCollection inside txns tests.
*/
-const createCollAndCRUDInTxn = function(sessionDB, collName, explicitCreate) {
+const createCollAndCRUDInTxn = function(sessionDB, collName, explicitCreate, upsert) {
+ 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];
- assert.commandWorked(sessionColl.insert({a: 1}));
+ if (upsert) {
+ assert.commandWorked(sessionColl.update({_id: 1}, {$inc: {a: 1}}, {upsert: true}));
+ } else {
+ assert.commandWorked(sessionColl.insert({a: 1}));
+ }
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);
+ assert.commandWorked(sessionColl.update({_id: 2}, {$inc: {a: 1}}));
+ assert.commandWorked(sessionColl.deleteOne({_id: 2}));
assert.eq(sessionColl.find({}).itcount(), 1);
};
diff --git a/jstests/libs/create_index_txn_helpers.js b/jstests/libs/create_index_txn_helpers.js
index fdc2863a675..73e1ee94fe2 100644
--- a/jstests/libs/create_index_txn_helpers.js
+++ b/jstests/libs/create_index_txn_helpers.js
@@ -23,11 +23,14 @@ const createIndexAndCRUDInTxn = function(sessionDB, collName, explicitCollCreate
}
let sessionColl = sessionDB[collName];
assert.commandWorked(sessionColl.runCommand({createIndexes: collName, indexes: [indexSpecs]}));
+ assert.commandWorked(sessionColl.createIndex({_id: 1}));
if (multikeyIndex) {
assert.commandWorked(sessionColl.insert({a: [1, 2, 3]}));
} else {
assert.commandWorked(sessionColl.insert({a: 1}));
}
assert.eq(sessionColl.find({a: 1}).itcount(), 1);
+ assert.commandWorked(sessionColl.insert({_id: 1}));
+ assert.commandWorked(sessionColl.deleteOne({_id: 1}));
assert.eq(sessionColl.find({}).itcount(), 1);
};
diff --git a/jstests/sharding/create_existing_indexes_prepared_transactions.js b/jstests/sharding/create_existing_indexes_prepared_transactions.js
new file mode 100644
index 00000000000..7dda83c7158
--- /dev/null
+++ b/jstests/sharding/create_existing_indexes_prepared_transactions.js
@@ -0,0 +1,112 @@
+// Test that ensuring index existence (createIndexes on an existing index) successfully runs in a
+// cross-shard transaction and that attempting to do createIndexes when only a subset of shards is
+// aware of the existing index fails.
+//
+// @tags: [
+// requires_find_command,
+// requires_fcv_44,
+// requires_sharding,
+// uses_multi_shard_transaction,
+// uses_transactions,
+// ]
+(function() {
+"use strict";
+
+function expectChunks(st, ns, chunks) {
+ for (let i = 0; i < chunks.length; i++) {
+ assert.eq(chunks[i],
+ st.s.getDB("config").chunks.count({ns: ns, shard: st["shard" + i].shardName}),
+ "unexpected number of chunks on shard " + i);
+ }
+}
+
+const dbName = "test";
+const dbNameShard2 = "testOther";
+const collName = "foo";
+const ns = dbName + '.' + collName;
+
+const st = new ShardingTest({
+ shards: 3,
+ mongos: 1,
+});
+
+// Set up one sharded collection with 2 chunks, both on the primary shard.
+
+assert.commandWorked(
+ st.s.getDB(dbName)[collName].insert({_id: -5}, {writeConcern: {w: "majority"}}));
+assert.commandWorked(
+ st.s.getDB(dbName)[collName].insert({_id: 5}, {writeConcern: {w: "majority"}}));
+
+assert.commandWorked(st.s.adminCommand({enableSharding: dbName}));
+st.ensurePrimaryShard(dbName, st.shard0.shardName);
+assert.commandWorked(st.s.getDB(dbName).runCommand({
+ createIndexes: collName,
+ indexes: [{key: {a: 1}, name: "a_1"}],
+ writeConcern: {w: "majority"}
+}));
+
+// Set up another collection with a different shard (shard 2) as its primary shard.
+assert.commandWorked(
+ st.s.getDB(dbNameShard2)[collName].insert({_id: 4}, {writeConcern: {w: "majority"}}));
+st.ensurePrimaryShard(dbNameShard2, st.shard2.shardName);
+
+const session = st.s.getDB(dbName).getMongo().startSession({causalConsistency: false});
+
+let sessionDB = session.getDatabase(dbName);
+let sessionColl = sessionDB[collName];
+let sessionDBShard2 = session.getDatabase(dbNameShard2);
+let sessionCollShard2 = sessionDBShard2[collName];
+
+assert.commandWorked(st.s.adminCommand({shardCollection: ns, key: {_id: 1}}));
+assert.commandWorked(st.s.adminCommand({split: ns, middle: {_id: 0}}));
+
+expectChunks(st, ns, [2, 0, 0]);
+
+st.stopBalancer();
+
+// Ensure collection `ns` has chunks distributed across two shards.
+assert.commandWorked(st.s.adminCommand({moveChunk: ns, find: {_id: 5}, to: st.shard1.shardName}));
+expectChunks(st, ns, [1, 1, 0]);
+
+// Ensure no stale version errors occur.
+let doc = st.s.getDB(dbName).getCollection(collName).findOne({_id: 5});
+assert.eq(doc._id, 5);
+let doc2 = st.s.getDB(dbNameShard2).getCollection(collName).findOne({_id: 4});
+assert.eq(doc2._id, 4);
+
+jsTest.log("Testing createIndexes on an existing index in a transaction");
+session.startTransaction({writeConcern: {w: "majority"}});
+
+assert.commandWorked(
+ sessionColl.runCommand({createIndexes: collName, indexes: [{key: {a: 1}, name: "a_1"}]}));
+// Perform cross-shard writes to execute prepare path.
+assert.commandWorked(sessionColl.insert({n: 2}));
+assert.commandWorked(sessionCollShard2.insert({m: 1}));
+assert.eq(sessionCollShard2.findOne({m: 1}).m, 1);
+assert.eq(sessionColl.findOne({n: 2}).n, 2);
+
+session.commitTransaction();
+
+jsTest.log("Testing createIndexes on an existing index in a transaction when not all shards are" +
+ " aware of that index (should abort)");
+
+// Simulate a scenario where one shard with chunks for a collection is unaware of one of the
+// collection's indexes.
+st.shard1.getDB(dbName).getCollection(collName).dropIndexes("a_1");
+
+session.startTransaction({writeConcern: {w: "majority"}});
+
+assert.commandFailedWithCode(
+ sessionColl.runCommand({createIndexes: collName, indexes: [{key: {a: 1}, name: "a_1"}]}),
+ ErrorCodes.OperationNotSupportedInTransaction);
+
+assert.commandFailedWithCode(session.abortTransaction_forTesting(), ErrorCodes.NoSuchTransaction);
+
+// Resolve index inconsistency to pass consistency checks.
+st.shard1.getDB(dbName).getCollection(collName).runCommand(
+ {createIndexes: collName, indexes: [{key: {a: 1}, name: "a_1"}]});
+
+st.startBalancer();
+
+st.stop();
+})();
diff --git a/jstests/sharding/create_new_collections_prepared_transactions.js b/jstests/sharding/create_new_collections_prepared_transactions.js
new file mode 100644
index 00000000000..7c56dcd87ba
--- /dev/null
+++ b/jstests/sharding/create_new_collections_prepared_transactions.js
@@ -0,0 +1,64 @@
+// Test that new collection creation fails in a cross-shard write transaction, but succeeds in a
+// single-shard write transaction.
+//
+// @tags: [
+// requires_find_command,
+// requires_fcv_44,
+// requires_sharding,
+// uses_multi_shard_transaction,
+// uses_transactions,
+// ]
+(function() {
+"use strict";
+
+const dbNameShard0 = "test";
+const dbNameShard2 = "testOther";
+const collName = "foo";
+
+const st = new ShardingTest({
+ shards: 3,
+ mongos: 1,
+});
+
+// Create two databases with different shards as their primaries.
+
+assert.commandWorked(
+ st.s.getDB(dbNameShard0)[collName].insert({_id: 5}, {writeConcern: {w: "majority"}}));
+
+assert.commandWorked(st.s.adminCommand({enableSharding: dbNameShard0}));
+st.ensurePrimaryShard(dbNameShard0, st.shard0.shardName);
+
+// Set up another collection with a different shard (shard2) as its primary shard.
+assert.commandWorked(
+ st.s.getDB(dbNameShard2)[collName].insert({_id: 4}, {writeConcern: {w: "majority"}}));
+st.ensurePrimaryShard(dbNameShard2, st.shard2.shardName);
+
+const session = st.s.getDB(dbNameShard0).getMongo().startSession({causalConsistency: false});
+
+let sessionDBShard0 = session.getDatabase(dbNameShard0);
+let sessionDBShard2 = session.getDatabase(dbNameShard2);
+let newCollName = "newColl";
+
+// Ensure no stale version errors occur.
+let doc = st.s.getDB(dbNameShard0).getCollection(collName).findOne({_id: 5});
+assert.eq(doc._id, 5);
+let doc2 = st.s.getDB(dbNameShard2).getCollection(collName).findOne({_id: 4});
+assert.eq(doc2._id, 4);
+
+jsTest.log("Testing collection creation in a cross-shard write transaction.");
+session.startTransaction({writeConcern: {w: "majority"}});
+assert.commandWorked(sessionDBShard0.createCollection(newCollName));
+assert.commandWorked(sessionDBShard2.createCollection(newCollName));
+
+// TODO(SERVER-46796) Replace NoSuchTransaction with OperationNotSupportedInTransaction
+assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.NoSuchTransaction);
+
+jsTest.log("Testing collection creation in a single-shard write transaction.");
+session.startTransaction({writeConcern: {w: "majority"}});
+assert.commandWorked(sessionDBShard0.createCollection(newCollName));
+doc2 = sessionDBShard2.getCollection(collName).findOne({_id: 4});
+assert.eq(doc2._id, 4);
+assert.commandWorked(session.commitTransaction_forTesting());
+
+st.stop();
+})();