summaryrefslogtreecommitdiff
path: root/jstests/core
diff options
context:
space:
mode:
authorJudah Schvimer <judah@mongodb.com>2018-07-20 13:11:12 -0400
committerJudah Schvimer <judah@mongodb.com>2018-07-20 13:17:19 -0400
commit5b6fbcf0dc4065d725b23c6fd3911a24e078e34d (patch)
treef5da305a6d3dfb072146fa75f592c67099a03bd9 /jstests/core
parent4cdaee88d7122f3ccba152ae37d3b5b69b3b398f (diff)
downloadmongo-5b6fbcf0dc4065d725b23c6fd3911a24e078e34d.tar.gz
SERVER-35597 SERVER-35598 Ensure prepared transactions can be committed
Diffstat (limited to 'jstests/core')
-rw-r--r--jstests/core/txns/abort_prepared_transaction.js (renamed from jstests/core/txns/prepare_transaction.js)35
-rw-r--r--jstests/core/txns/commit_prepared_transaction.js76
-rw-r--r--jstests/core/txns/commit_prepared_transaction_errors.js63
-rw-r--r--jstests/core/txns/libs/prepare_helpers.js54
4 files changed, 204 insertions, 24 deletions
diff --git a/jstests/core/txns/prepare_transaction.js b/jstests/core/txns/abort_prepared_transaction.js
index 2427f9e8c94..f5b5b98d2fe 100644
--- a/jstests/core/txns/prepare_transaction.js
+++ b/jstests/core/txns/abort_prepared_transaction.js
@@ -1,13 +1,14 @@
/**
- * Tests prepared transaction support.
+ * Tests prepared transaction abort support.
*
* @tags: [uses_transactions]
*/
(function() {
"use strict";
+ load("jstests/core/txns/libs/prepare_helpers.js");
const dbName = "test";
- const collName = "prepare_transaction";
+ const collName = "abort_prepared_transaction";
const testDB = db.getSiblingDB(dbName);
const testColl = testDB.getCollection(collName);
@@ -23,10 +24,7 @@
// ---- Test 1. Insert a single document and run prepare. ----
session.startTransaction();
- assert.commandWorked(sessionDB.runCommand({
- insert: collName,
- documents: [doc1],
- }));
+ assert.commandWorked(sessionColl.insert(doc1));
// Insert should not be visible outside the session.
assert.eq(null, testColl.findOne(doc1));
@@ -34,7 +32,7 @@
// Insert should be visible in this session.
assert.eq(doc1, sessionColl.findOne(doc1));
- assert.commandWorked(sessionDB.adminCommand({prepareTransaction: 1}));
+ PrepareHelpers.prepareTransaction(session);
session.abortTransaction();
// After abort the insert is rolled back.
@@ -43,14 +41,10 @@
// ---- Test 2. Update a document and run prepare. ----
// Insert a document to update.
- assert.commandWorked(
- testDB.runCommand({insert: collName, documents: [doc1], writeConcern: {w: "majority"}}));
+ assert.commandWorked(sessionColl.insert(doc1, {writeConcern: {w: "majority"}}));
session.startTransaction();
- assert.commandWorked(sessionDB.runCommand({
- update: collName,
- updates: [{q: doc1, u: {$inc: {x: 1}}}],
- }));
+ assert.commandWorked(sessionColl.update(doc1, {$inc: {x: 1}}));
const doc2 = {_id: 1, x: 2};
@@ -60,7 +54,7 @@
// Update should be visible in this session.
assert.eq(doc2, sessionColl.findOne(doc2));
- assert.commandWorked(sessionDB.adminCommand({prepareTransaction: 1}));
+ PrepareHelpers.prepareTransaction(session);
session.abortTransaction();
// After abort the update is rolled back.
@@ -69,17 +63,10 @@
// ---- Test 3. Delete a document and run prepare. ----
// Update the document.
- assert.commandWorked(testDB.runCommand({
- update: collName,
- updates: [{q: doc1, u: {$inc: {x: 1}}}],
- writeConcern: {w: "majority"}
- }));
+ assert.commandWorked(sessionColl.update(doc1, {$inc: {x: 1}}, {writeConcern: {w: "majority"}}));
session.startTransaction();
- assert.commandWorked(sessionDB.runCommand({
- delete: collName,
- deletes: [{q: doc2, limit: 1}],
- }));
+ assert.commandWorked(sessionColl.remove(doc2, {justOne: true}));
// Delete should not be visible outside the session, so the document should be.
assert.eq(doc2, testColl.findOne(doc2));
@@ -87,7 +74,7 @@
// Document should not be visible in this session, since the delete should be visible.
assert.eq(null, sessionColl.findOne(doc2));
- assert.commandWorked(sessionDB.adminCommand({prepareTransaction: 1}));
+ PrepareHelpers.prepareTransaction(session);
session.abortTransaction();
// After abort the delete is rolled back.
diff --git a/jstests/core/txns/commit_prepared_transaction.js b/jstests/core/txns/commit_prepared_transaction.js
new file mode 100644
index 00000000000..27aecd4c6b5
--- /dev/null
+++ b/jstests/core/txns/commit_prepared_transaction.js
@@ -0,0 +1,76 @@
+/**
+ * Tests prepared transaction commit support.
+ *
+ * @tags: [uses_transactions]
+ */
+(function() {
+ "use strict";
+ load("jstests/core/txns/libs/prepare_helpers.js");
+
+ const dbName = "test";
+ const collName = "commit_prepared_transaction";
+ const testDB = db.getSiblingDB(dbName);
+ const testColl = testDB.getCollection(collName);
+
+ testColl.drop();
+ assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}}));
+
+ const session = db.getMongo().startSession({causalConsistency: false});
+ const sessionDB = session.getDatabase(dbName);
+ const sessionColl = sessionDB.getCollection(collName);
+
+ const doc1 = {_id: 1, x: 1};
+
+ // ---- Test 1. Insert a single document and run prepare. ----
+
+ session.startTransaction();
+ assert.commandWorked(sessionColl.insert(doc1));
+
+ // Insert should not be visible outside the session.
+ assert.eq(null, testColl.findOne(doc1));
+
+ // Insert should be visible in this session.
+ assert.eq(doc1, sessionColl.findOne(doc1));
+
+ let prepareTimestamp = PrepareHelpers.prepareTransaction(session);
+ assert.commandWorked(PrepareHelpers.commitTransaction(session, prepareTimestamp));
+
+ // After commit the insert persists.
+ assert.eq(doc1, testColl.findOne(doc1));
+
+ // ---- Test 2. Update a document and run prepare. ----
+
+ session.startTransaction();
+ assert.commandWorked(sessionColl.update(doc1, {$inc: {x: 1}}));
+
+ const doc2 = {_id: 1, x: 2};
+
+ // Update should not be visible outside the session.
+ assert.eq(null, testColl.findOne(doc2));
+
+ // Update should be visible in this session.
+ assert.eq(doc2, sessionColl.findOne(doc2));
+
+ prepareTimestamp = PrepareHelpers.prepareTransaction(session);
+ assert.commandWorked(PrepareHelpers.commitTransaction(session, prepareTimestamp));
+
+ // After commit the update persists.
+ assert.eq(doc2, testColl.findOne({_id: 1}));
+
+ // ---- Test 3. Delete a document and run prepare. ----
+
+ session.startTransaction();
+ assert.commandWorked(sessionColl.remove(doc2, {justOne: true}));
+
+ // Delete should not be visible outside the session, so the document should be.
+ assert.eq(doc2, testColl.findOne(doc2));
+
+ // Document should not be visible in this session, since the delete should be visible.
+ assert.eq(null, sessionColl.findOne(doc2));
+
+ prepareTimestamp = PrepareHelpers.prepareTransaction(session);
+ assert.commandWorked(PrepareHelpers.commitTransaction(session, prepareTimestamp));
+
+ // After commit the delete persists.
+ assert.eq(null, testColl.findOne(doc2));
+}());
diff --git a/jstests/core/txns/commit_prepared_transaction_errors.js b/jstests/core/txns/commit_prepared_transaction_errors.js
new file mode 100644
index 00000000000..cf5dfdf65a8
--- /dev/null
+++ b/jstests/core/txns/commit_prepared_transaction_errors.js
@@ -0,0 +1,63 @@
+/**
+ * Test error cases for committing prepared transactions.
+ *
+ * @tags: [uses_transactions]
+ */
+(function() {
+ "use strict";
+ load("jstests/core/txns/libs/prepare_helpers.js");
+
+ const dbName = "test";
+ const collName = "commit_prepared_transaction_errors";
+ const testDB = db.getSiblingDB(dbName);
+ const testColl = testDB.getCollection(collName);
+
+ testColl.drop();
+ assert.commandWorked(testDB.runCommand({create: collName, writeConcern: {w: "majority"}}));
+
+ const session = db.getMongo().startSession({causalConsistency: false});
+ const sessionDB = session.getDatabase(dbName);
+ const sessionColl = sessionDB.getCollection(collName);
+
+ const doc = {x: 1};
+
+ jsTestLog("Test committing a prepared transaction with no 'commitTimestamp'.");
+ session.startTransaction();
+ assert.commandWorked(sessionColl.insert(doc));
+ PrepareHelpers.prepareTransaction(session);
+ assert.commandFailedWithCode(sessionDB.adminCommand({commitTransaction: 1}),
+ ErrorCodes.InvalidOptions);
+ session.abortTransaction();
+
+ jsTestLog("Test committing a prepared transaction with an invalid 'commitTimestamp'.");
+ session.startTransaction();
+ assert.commandWorked(sessionColl.insert(doc));
+ PrepareHelpers.prepareTransaction(session);
+ assert.commandFailedWithCode(PrepareHelpers.commitTransaction(session, 5),
+ ErrorCodes.TypeMismatch);
+
+ jsTestLog("Test committing a prepared transaction with a null 'commitTimestamp'.");
+ session.startTransaction();
+ assert.commandWorked(sessionColl.insert(doc));
+ PrepareHelpers.prepareTransaction(session);
+ assert.commandFailedWithCode(PrepareHelpers.commitTransaction(session, Timestamp(0, 0)),
+ ErrorCodes.InvalidOptions);
+
+ jsTestLog("Test committing an unprepared transaction with a 'commitTimestamp'.");
+ session.startTransaction();
+ assert.commandWorked(sessionColl.insert(doc));
+ assert.commandFailedWithCode(PrepareHelpers.commitTransaction(session, Timestamp(3, 3)),
+ ErrorCodes.InvalidOptions);
+
+ jsTestLog("Test committing an unprepared transaction with a null 'commitTimestamp'.");
+ session.startTransaction();
+ assert.commandWorked(sessionColl.insert(doc));
+ assert.commandFailedWithCode(PrepareHelpers.commitTransaction(session, Timestamp(0, 0)),
+ ErrorCodes.InvalidOptions);
+
+ jsTestLog("Test committing an unprepared transaction with an invalid 'commitTimestamp'.");
+ session.startTransaction();
+ assert.commandWorked(sessionColl.insert(doc));
+ assert.commandFailedWithCode(PrepareHelpers.commitTransaction(session, 5),
+ ErrorCodes.TypeMismatch);
+}());
diff --git a/jstests/core/txns/libs/prepare_helpers.js b/jstests/core/txns/libs/prepare_helpers.js
new file mode 100644
index 00000000000..c823d3b8e97
--- /dev/null
+++ b/jstests/core/txns/libs/prepare_helpers.js
@@ -0,0 +1,54 @@
+/**
+ * Helper functions for testing prepared transactions.
+ *
+ * @tags: [uses_transactions]
+ *
+ */
+const PrepareHelpers = (function() {
+
+ /**
+ * Prepares the active transaction on the session. This expects the 'prepareTransaction' command
+ * to succeed and return a non-null 'prepareTimestamp'.
+ *
+ * @return {Timestamp} the transaction's prepareTimestamp
+ */
+ function prepareTransaction(session) {
+ assert(session);
+
+ const res = assert.commandWorked(
+ session.getDatabase('admin').adminCommand({prepareTransaction: 1}));
+ assert(res.prepareTimestamp,
+ "prepareTransaction did not return a 'prepareTimestamp': " + tojson(res));
+ const prepareTimestamp = res.prepareTimestamp;
+ assert(prepareTimestamp instanceof Timestamp,
+ 'prepareTimestamp was not a Timestamp: ' + tojson(res));
+ assert.neq(
+ prepareTimestamp, Timestamp(0, 0), "prepareTimestamp cannot be null: " + tojson(res));
+ return prepareTimestamp;
+ }
+
+ /**
+ * Commits the active transaction on the session.
+ *
+ * @return {object} the response to the 'commitTransaction' command.
+ */
+ function commitTransaction(session, commitTimestamp) {
+ assert(session);
+
+ const res = session.getDatabase('admin').adminCommand(
+ {commitTransaction: 1, commitTimestamp: commitTimestamp});
+
+ // End the transaction on the shell session.
+ if (res.ok) {
+ session.commitTransaction();
+ } else {
+ session.abortTransaction();
+ }
+ return res;
+ }
+
+ return {
+ prepareTransaction: prepareTransaction,
+ commitTransaction: commitTransaction,
+ };
+})();