summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVesselina Ratcheva <vesselina.ratcheva@10gen.com>2019-07-24 17:27:07 -0400
committerVesselina Ratcheva <vesselina.ratcheva@10gen.com>2019-07-26 12:02:34 -0400
commitaccbdf6fb4846639ce55de7f910ead365f748945 (patch)
treeed418d292ea69006e33ebc68ec6bce16e59f3242
parentedcd0b9a2254cbac3d843be28f373a4f0f3024b4 (diff)
downloadmongo-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.js104
-rw-r--r--jstests/core/txns/transaction_ops_fail_against_capped_collection.js49
-rw-r--r--src/mongo/db/commands/find_and_modify.cpp15
-rw-r--r--src/mongo/db/ops/write_ops_exec.cpp10
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();
}