diff options
author | Judah Schvimer <judah@mongodb.com> | 2018-07-20 13:11:12 -0400 |
---|---|---|
committer | Judah Schvimer <judah@mongodb.com> | 2018-07-20 13:17:19 -0400 |
commit | 5b6fbcf0dc4065d725b23c6fd3911a24e078e34d (patch) | |
tree | f5da305a6d3dfb072146fa75f592c67099a03bd9 /jstests/core | |
parent | 4cdaee88d7122f3ccba152ae37d3b5b69b3b398f (diff) | |
download | mongo-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.js | 76 | ||||
-rw-r--r-- | jstests/core/txns/commit_prepared_transaction_errors.js | 63 | ||||
-rw-r--r-- | jstests/core/txns/libs/prepare_helpers.js | 54 |
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, + }; +})(); |