diff options
author | Vesselina Ratcheva <vesselina.ratcheva@10gen.com> | 2019-07-24 17:27:07 -0400 |
---|---|---|
committer | Vesselina Ratcheva <vesselina.ratcheva@10gen.com> | 2019-07-26 12:02:34 -0400 |
commit | accbdf6fb4846639ce55de7f910ead365f748945 (patch) | |
tree | ed418d292ea69006e33ebc68ec6bce16e59f3242 | |
parent | edcd0b9a2254cbac3d843be28f373a4f0f3024b4 (diff) | |
download | mongo-accbdf6fb4846639ce55de7f910ead365f748945.tar.gz |
SERVER-42372 Ban findAndModify against capped collections in transactions
(cherry picked from commit 5f7471631d238fb3269fa0f2c3689ac62e93b61f)
-rw-r--r-- | jstests/core/txns/transaction_ops_against_capped_collection.js | 104 | ||||
-rw-r--r-- | jstests/core/txns/transaction_ops_fail_against_capped_collection.js | 49 | ||||
-rw-r--r-- | src/mongo/db/commands/find_and_modify.cpp | 15 | ||||
-rw-r--r-- | src/mongo/db/ops/write_ops_exec.cpp | 10 |
4 files changed, 123 insertions, 55 deletions
diff --git a/jstests/core/txns/transaction_ops_against_capped_collection.js b/jstests/core/txns/transaction_ops_against_capped_collection.js new file mode 100644 index 00000000000..b7a0720a875 --- /dev/null +++ b/jstests/core/txns/transaction_ops_against_capped_collection.js @@ -0,0 +1,104 @@ +/** + * Tests that transactional writes are prohibited on capped collections, but transactional reads are + * still allowed. + * + * 'requires_capped' tagged tests are excluded from txn passthrough suites. + * @tags: [requires_capped, uses_transactions] + */ +(function() { + "use strict"; + + const dbName = "test"; + const cappedCollName = "transaction_ops_against_capped_collection"; + const testDB = db.getSiblingDB(dbName); + const cappedTestColl = testDB.getCollection(cappedCollName); + const testDocument = {"a": 1}; + + cappedTestColl.drop({writeConcern: {w: "majority"}}); + + jsTest.log("Creating a capped collection '" + dbName + "." + cappedCollName + "'."); + assert.commandWorked(testDB.createCollection(cappedCollName, {capped: true, size: 500})); + + jsTest.log("Adding a document to the capped collection so that the update op can be tested " + + "in the subsequent transaction attempts"); + assert.commandWorked(cappedTestColl.insert(testDocument)); + + jsTest.log("Setting up a transaction in which to execute transaction ops."); + const session = db.getMongo().startSession(); + const sessionDB = session.getDatabase(dbName); + const sessionCappedColl = sessionDB.getCollection(cappedCollName); + + jsTest.log( + "Starting individual transactions for writes against capped collections that should " + + " fail."); + + /* + * Write ops (should fail): + */ + + jsTest.log("About to try: insert"); + session.startTransaction(); + assert.commandFailedWithCode(sessionCappedColl.insert({"x": 55}), + ErrorCodes.OperationNotSupportedInTransaction); + assert.commandFailedWithCode(session.abortTransaction_forTesting(), + ErrorCodes.NoSuchTransaction); + + jsTest.log("About to try: update"); + session.startTransaction(); + assert.commandFailedWithCode(sessionCappedColl.update(testDocument, {"a": 1000}), + ErrorCodes.OperationNotSupportedInTransaction); + assert.commandFailedWithCode(session.abortTransaction_forTesting(), + ErrorCodes.NoSuchTransaction); + + jsTest.log("About to try: findAndModify (update version)"); + session.startTransaction(); + assert.commandFailedWithCode( + sessionDB.runCommand( + {findAndModify: cappedCollName, query: testDocument, update: {"$set": {"a": 1000}}}), + ErrorCodes.OperationNotSupportedInTransaction); + assert.commandFailedWithCode(session.abortTransaction_forTesting(), + ErrorCodes.NoSuchTransaction); + + jsTest.log("About to try: findAndModify (remove version)"); + session.startTransaction(); + assert.commandFailedWithCode( + sessionDB.runCommand({findAndModify: cappedCollName, query: testDocument, remove: true}), + ErrorCodes.OperationNotSupportedInTransaction); + assert.commandFailedWithCode(session.abortTransaction_forTesting(), + ErrorCodes.NoSuchTransaction); + + // Deletes do not work against capped collections so we will not test them in transactions. + + jsTest.log( + "Starting individual transactions for reads against capped collections that should " + + " succeed."); + + /* + * Read ops (should succeed): + */ + + jsTest.log("About to try: find"); + session.startTransaction(); + let findRes = assert.commandWorked(sessionDB.runCommand({"find": cappedCollName})); + assert.eq(1, findRes.cursor.firstBatch[0].a); + assert.commandWorked(session.abortTransaction_forTesting()); + + jsTest.log("About to try: distinct"); + session.startTransaction(); + let distinctRes = + assert.commandWorked(sessionDB.runCommand({"distinct": cappedCollName, "key": "a"})); + assert.eq(1, distinctRes.values); + assert.commandWorked(session.abortTransaction_forTesting()); + + jsTest.log("About to try: aggregate"); + session.startTransaction(); + let aggRes = assert.commandWorked(sessionDB.runCommand({ + aggregate: cappedCollName, + pipeline: [{$match: {"a": 1}}], + cursor: {}, + })); + assert.eq(1, aggRes.cursor.firstBatch[0].a); + assert.commandWorked(session.abortTransaction_forTesting()); + + session.endSession(); +})(); diff --git a/jstests/core/txns/transaction_ops_fail_against_capped_collection.js b/jstests/core/txns/transaction_ops_fail_against_capped_collection.js deleted file mode 100644 index ee93813215e..00000000000 --- a/jstests/core/txns/transaction_ops_fail_against_capped_collection.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Tests that transaction CRUD operations are not allowed on capped collections. - * - * 'requires_capped' tagged tests are excluded from txn passthrough suites. - * @tags: [requires_capped, uses_transactions] - */ -(function() { - "use strict"; - - const dbName = "test"; - const cappedCollName = "transaction_ops_fail_against_capped_collection"; - const testDB = db.getSiblingDB(dbName); - const cappedTestColl = testDB.getCollection(cappedCollName); - const testDocument = {"a": 1}; - - cappedTestColl.drop({writeConcern: {w: "majority"}}); - - jsTest.log("Creating a capped collection '" + dbName + "." + cappedCollName + "'."); - assert.commandWorked(testDB.createCollection(cappedCollName, {capped: true, size: 500})); - - jsTest.log("Adding a document to the capped collection so that the update op can be tested " + - "in the subsequent transaction attempts"); - assert.commandWorked(cappedTestColl.insert(testDocument)); - - jsTest.log("Setting up a transaction in which to execute transaction ops."); - const session = db.getMongo().startSession(); - const sessionDB = session.getDatabase(dbName); - const sessionCappedColl = sessionDB.getCollection(cappedCollName); - - jsTest.log("Starting a transaction for an insert op against a capped collection that should " + - "fail"); - session.startTransaction(); - assert.commandFailedWithCode(sessionCappedColl.insert({"x": 55}), - ErrorCodes.OperationNotSupportedInTransaction); - assert.commandFailedWithCode(session.abortTransaction_forTesting(), - ErrorCodes.NoSuchTransaction); - - jsTest.log("Starting a transaction for an update op against a capped collection that should " + - "fail"); - session.startTransaction(); - assert.commandFailedWithCode(sessionCappedColl.update(testDocument, {"a": 1000}), - ErrorCodes.OperationNotSupportedInTransaction); - assert.commandFailedWithCode(session.abortTransaction_forTesting(), - ErrorCodes.NoSuchTransaction); - - // Deletes do not work against capped collections so we will not test it in a transaction. - - session.endSession(); -})(); diff --git a/src/mongo/db/commands/find_and_modify.cpp b/src/mongo/db/commands/find_and_modify.cpp index 3f6d65e5426..a52ad5fc952 100644 --- a/src/mongo/db/commands/find_and_modify.cpp +++ b/src/mongo/db/commands/find_and_modify.cpp @@ -194,6 +194,17 @@ void recordStatsForTopCommand(OperationContext* opCtx) { curOp->getReadWriteType()); } +void checkIfTransactionOnCappedColl(Collection* coll, bool inTransaction) { + if (coll && coll->isCapped()) { + uassert( + ErrorCodes::OperationNotSupportedInTransaction, + str::stream() << "Collection '" << coll->ns() + << "' is a capped collection. Writes in transactions are not allowed on " + "capped collections.", + !inTransaction); + } +} + class CmdFindAndModify : public BasicCommand { public: CmdFindAndModify() : BasicCommand("findAndModify", "findandmodify") {} @@ -377,6 +388,8 @@ public: assertCanWrite(opCtx, nsString); Collection* const collection = autoColl.getCollection(); + checkIfTransactionOnCappedColl(collection, inTransaction); + const auto exec = uassertStatusOK(getExecutorDelete(opCtx, opDebug, collection, &parsedDelete)); @@ -476,6 +489,8 @@ public: invariant(collection); } + checkIfTransactionOnCappedColl(collection, inTransaction); + const auto exec = uassertStatusOK(getExecutorUpdate(opCtx, opDebug, collection, &parsedUpdate)); diff --git a/src/mongo/db/ops/write_ops_exec.cpp b/src/mongo/db/ops/write_ops_exec.cpp index 13d303bdf95..fc39b35d0c9 100644 --- a/src/mongo/db/ops/write_ops_exec.cpp +++ b/src/mongo/db/ops/write_ops_exec.cpp @@ -337,12 +337,10 @@ void insertDocuments(OperationContext* opCtx, Status checkIfTransactionOnCappedColl(OperationContext* opCtx, Collection* collection) { auto txnParticipant = TransactionParticipant::get(opCtx); if (txnParticipant && txnParticipant.inMultiDocumentTransaction() && collection->isCapped()) { - return { - ErrorCodes::OperationNotSupportedInTransaction, - str::stream() - << "Collection '" - << collection->ns() - << "' is a capped collection. Transactions are not allowed on capped collections."}; + return {ErrorCodes::OperationNotSupportedInTransaction, + str::stream() << "Collection '" << collection->ns() + << "' is a capped collection. Writes in transactions are not allowed " + "on capped collections."}; } return Status::OK(); } |