diff options
author | Ruoxin Xu <ruoxin.xu@mongodb.com> | 2020-03-05 23:46:22 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-03-25 13:30:47 +0000 |
commit | caa29e31ae529b23c4dabb6878de58e2a7f0eda9 (patch) | |
tree | ea9a6047bc1d0922642833d56af5ed6555a754f0 | |
parent | 4c4e1d0055ccfd3ca8f137d834a7b194a0961160 (diff) | |
download | mongo-caa29e31ae529b23c4dabb6878de58e2a7f0eda9.tar.gz |
SERVER-45987 Ensure apply op entries for deletes in multi-document transactions only include the document key
(cherry picked from commit ed0c0365b4baf8939d39edb766c8ddbbc33187d3)
-rw-r--r-- | jstests/change_streams/delete_in_txn_produces_correct_doc_key.js | 122 | ||||
-rw-r--r-- | src/mongo/db/op_observer_impl.cpp | 3 |
2 files changed, 123 insertions, 2 deletions
diff --git a/jstests/change_streams/delete_in_txn_produces_correct_doc_key.js b/jstests/change_streams/delete_in_txn_produces_correct_doc_key.js new file mode 100644 index 00000000000..89f6faa6f54 --- /dev/null +++ b/jstests/change_streams/delete_in_txn_produces_correct_doc_key.js @@ -0,0 +1,122 @@ +// Tests that 'delete' events within a multi-document transaction do not include the full document +// in the 'documentKey' field. Exercises the fix for SERVER-45987. +// @tags: [ +// uses_transactions, +// assumes_against_mongod_not_mongos +// ] + +(function() { + "use strict"; + + load("jstests/libs/collection_drop_recreate.js"); // For assert[Drop|Create]Collection. + load("jstests/libs/change_stream_util.js"); // For ChangeStreamTest. + + const collName = "delete_in_txn_produces_correct_doc_key"; + + /** + * Test function which is used to test three types of delete-related commands, deleteOne(), + * deleteMany() and findAndModify(). Ensure only documentKey instead of a full document + * will be logged on entries for deletes in multi-document transactions, and also ensure that + * we can resume the change stream from these delete events. + */ + function testDeleteInMultiDocTxn({collName, deleteCommand, expectedChanges}) { + // Initialize the collection. + const coll = assertDropAndRecreateCollection(db, collName); + + assert.commandWorked(coll.insertMany([ + {_id: 1, a: 0, fullDoc: "It's a full document!"}, + {_id: 2, a: 0}, + {_id: 3, a: 0}, + {_id: 4, a: 1} + ])); + + // Open a change stream on the test collection. + const cst = new ChangeStreamTest(db); + let cursor = cst.startWatchingChanges({ + pipeline: [{$changeStream: {}}, {$project: {"lsid": 0, "txnNumber": 0}}], + collection: coll + }); + + // Start a transaction in which to perform the delete operation(s). + const session = db.getMongo().startSession(); + const sessionDb = session.getDatabase(db.getName()); + const sessionColl = sessionDb[collName]; + session.startTransaction(); + + // Run the given 'deleteCommand' function to perform the delete(s). + deleteCommand(sessionColl); + + // Commit the transaction so that the events become visible to the change stream. + assert.commandWorked(session.commitTransaction_forTesting()); + + // Verify that the stream returns the expected sequence of changes. + const changes = + cst.assertNextChangesEqual({cursor: cursor, expectedChanges: expectedChanges}); + + // Test the change stream can be resumed after a delete event from within the transaction. + cursor = cst.startWatchingChanges({ + pipeline: [{$changeStream: {resumeAfter: changes[changes.length - 1]._id}}], + collection: coll + }); + assert.commandWorked(coll.insert({_id: 5})); + assert.docEq(cst.getOneChange(cursor).documentKey, {_id: 5}); + + cst.cleanUp(); + } + + jsTestLog("Testing deleteOne() in a transaction."); + testDeleteInMultiDocTxn({ + collName: collName, + deleteCommand: function(sessionColl) { + assert.commandWorked(sessionColl.deleteOne({_id: 1})); + }, + expectedChanges: [ + { + documentKey: {_id: 1}, + ns: {db: db.getName(), coll: collName}, + operationType: "delete", + }, + ], + }); + + jsTestLog("Testing deleteMany() in a transaction."); + testDeleteInMultiDocTxn({ + collName: collName, + deleteCommand: function(sessionColl) { + assert.commandWorked(sessionColl.deleteMany({a: 0, _id: {$gt: 0}})); + }, + expectedChanges: [ + { + documentKey: {_id: 1}, + ns: {db: db.getName(), coll: collName}, + operationType: "delete", + }, + { + documentKey: {_id: 2}, + ns: {db: db.getName(), coll: collName}, + operationType: "delete", + }, + { + documentKey: {_id: 3}, + ns: {db: db.getName(), coll: collName}, + operationType: "delete", + }, + ], + }); + + // Note that findAndModify will delete only the *first* matching document. + jsTestLog("Testing findAndModify() in a transaction."); + testDeleteInMultiDocTxn({ + collName: collName, + deleteCommand: function(sessionColl) { + sessionColl.findAndModify({query: {a: 0}, sort: {_id: 1}, remove: true}); + }, + expectedChanges: [ + { + documentKey: {_id: 1}, + ns: {db: db.getName(), coll: collName}, + operationType: "delete", + }, + ], + }); +}()); diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp index 62a6d9699cf..23e0f7427a4 100644 --- a/src/mongo/db/op_observer_impl.cpp +++ b/src/mongo/db/op_observer_impl.cpp @@ -518,8 +518,7 @@ void OpObserverImpl::onDelete(OperationContext* opCtx, session->inActiveOrKilledMultiDocumentTransaction(); OpTimeBundle opTime; if (inMultiDocumentTransaction) { - auto operation = - OplogEntry::makeDeleteOperation(nss, uuid, deletedDoc ? deletedDoc.get() : documentKey); + auto operation = OplogEntry::makeDeleteOperation(nss, uuid, documentKey); session->addTransactionOperation(opCtx, operation); } else { opTime = replLogDelete(opCtx, nss, uuid, session, stmtId, fromMigrate, deletedDoc); |