|
| 1 | +// Confirms that change streams correctly handle prepared transactions with an empty applyOps entry. |
| 2 | +// This test creats a multi-shard transaction in which one of the participating shards has only a |
| 3 | +// no-op write, resulting in the empty applyOps scenario we wish to test. Exercises the fix for |
| 4 | +// SERVER-50769. |
| 5 | +// @tags: [ |
| 6 | +// requires_sharding, |
| 7 | +// uses_change_streams, |
| 8 | +// uses_multi_shard_transaction, |
| 9 | +// uses_transactions, |
| 10 | +// ] |
| 11 | +(function() { |
| 12 | +"use strict"; |
| 13 | + |
| 14 | +const dbName = "test"; |
| 15 | +const collName = "change_stream_empty_apply_ops"; |
| 16 | +const namespace = dbName + "." + collName; |
| 17 | + |
| 18 | +const st = new ShardingTest({ |
| 19 | + shards: 2, |
| 20 | + rs: {nodes: 1, setParameter: {writePeriodicNoops: true, periodicNoopIntervalSecs: 1}} |
| 21 | +}); |
| 22 | + |
| 23 | +const mongosConn = st.s; |
| 24 | +const db = mongosConn.getDB(dbName); |
| 25 | +const coll = db.getCollection(collName); |
| 26 | + |
| 27 | +assert.commandWorked(coll.createIndex({shard: 1})); |
| 28 | +st.ensurePrimaryShard(dbName, st.shard0.shardName); |
| 29 | +// Shard the test collection and split it into two chunks: one that contains all {shard: 1} |
| 30 | +// documents and one that contains all {shard: 2} documents. |
| 31 | +st.shardColl(collName, |
| 32 | + {shard: 1} /* shard key */, |
| 33 | + {shard: 2} /* split at */, |
| 34 | + {shard: 2} /* move the chunk containing {shard: 2} to its own shard */, |
| 35 | + dbName, |
| 36 | + true); |
| 37 | +// Seed each chunk with an initial document. |
| 38 | +assert.commandWorked(coll.insert({shard: 1}, {writeConcern: {w: "majority"}})); |
| 39 | +assert.commandWorked(coll.insert({shard: 2}, {writeConcern: {w: "majority"}})); |
| 40 | + |
| 41 | +// Open up change streams. |
| 42 | +const changeStreamCursorColl = coll.watch(); |
| 43 | +const changeStreamCursorDB = db.watch(); |
| 44 | +const changeStreamCursorCluster = mongosConn.watch(); |
| 45 | + |
| 46 | +// Start a transaction, which will include both shards. |
| 47 | +const sesion = db.getMongo().startSession({causalConsistency: true}); |
| 48 | +const sessionDb = sesion.getDatabase(dbName); |
| 49 | +const sessionColl = sessionDb[collName]; |
| 50 | + |
| 51 | +sesion.startTransaction({readConcern: {level: "majority"}}); |
| 52 | + |
| 53 | +// This no-op will make one of the shards a transaction participant without generating an actual |
| 54 | +// write. The transaction will send an empty prepared transaction to the shard, in the form of an |
| 55 | +// applyOps command with no operations. |
| 56 | +sessionColl.findAndModify({query: {shard: 1}, update: {$setOnInsert: {a: 1}}}); |
| 57 | + |
| 58 | +// This write, which is not a no-op, occurs on the other shard. |
| 59 | +sessionColl.findAndModify({query: {shard: 2}, update: {$set: {a: 1}}}); |
| 60 | + |
| 61 | +assert.commandWorked(sesion.commitTransaction_forTesting()); |
| 62 | + |
| 63 | +// Each change stream should see exactly one update, resulting from the valid write on shard 2. |
| 64 | +[changeStreamCursorColl, changeStreamCursorDB, changeStreamCursorCluster].forEach(function( |
| 65 | + changeStreamCursor) { |
| 66 | + assert.soon(() => changeStreamCursor.hasNext()); |
| 67 | + const changeDoc = changeStreamCursor.next(); |
| 68 | + assert.eq(changeDoc.documentKey.shard, 2); |
| 69 | + assert.eq(changeDoc.operationType, "update"); |
| 70 | + |
| 71 | + assert(!changeStreamCursor.hasNext()); |
| 72 | +}); |
| 73 | + |
| 74 | +st.stop(); |
| 75 | +})(); |
0 commit comments