From fd49e57a16a45b2a87c12cf995954ff2103d7f7e Mon Sep 17 00:00:00 2001 From: Jamis Buck Date: Mon, 6 Jul 2026 15:03:41 -0600 Subject: [PATCH] RUBY-3820 Send afterClusterTime on writes in causally-consistent sessions Include CausalConsistencySupported in the write operation op_msg classes (insert, update, delete, create, create_index, drop, drop_database, drop_index, and write_command) so that writes attach afterClusterTime when running in a causally consistent session, preserving causal ordering across writes. Thread an explicit :session through Index::View#drop_by_name, add session support to the affected unified runner operations (including a new drop_database handler), and add the causal consistency unified spec suite. The clientBulkWrite fixture is skipped pending RUBY-2964, since the driver does not implement the bulkWrite command yet. Update existing transaction fixtures whose post-transaction writes now carry afterClusterTime. --- lib/mongo/index/view.rb | 4 +- lib/mongo/operation/create/op_msg.rb | 1 + lib/mongo/operation/create_index/op_msg.rb | 1 + lib/mongo/operation/delete/op_msg.rb | 1 + lib/mongo/operation/drop/op_msg.rb | 1 + lib/mongo/operation/drop_database/op_msg.rb | 1 + lib/mongo/operation/drop_index/op_msg.rb | 1 + lib/mongo/operation/insert/op_msg.rb | 1 + .../shared/causal_consistency_supported.rb | 6 +- lib/mongo/operation/update/op_msg.rb | 1 + lib/mongo/operation/write_command/op_msg.rb | 2 + spec/runners/unified/crud_operations.rb | 21 +- spec/runners/unified/ddl_operations.rb | 20 +- .../causal_consistency_unified_spec.rb | 12 + .../causal-consistency-clientBulkWrite.yml | 75 +++ .../causal-consistency-write-commands.yml | 472 ++++++++++++++++++ spec/spec_tests/data/transactions/commit.yml | 6 + .../data/transactions/retryable-writes.yml | 6 + .../data/transactions_api/callback-aborts.yml | 5 +- .../transactions_api/callback-commits.yml | 5 +- 20 files changed, 633 insertions(+), 9 deletions(-) create mode 100644 spec/spec_tests/causal_consistency_unified_spec.rb create mode 100644 spec/spec_tests/data/causal_consistency_unified/causal-consistency-clientBulkWrite.yml create mode 100644 spec/spec_tests/data/causal_consistency_unified/causal-consistency-write-commands.yml diff --git a/lib/mongo/index/view.rb b/lib/mongo/index/view.rb index bfce2202b3..29746aac40 100644 --- a/lib/mongo/index/view.rb +++ b/lib/mongo/index/view.rb @@ -350,7 +350,9 @@ def operation_timeouts(opts = {}) private def drop_by_name(name, opts = {}) - client.send(:with_session, @options) do |session| + session_options = @options.dup + session_options[:session] = opts[:session] if opts[:session] + client.send(:with_session, session_options) do |session| spec = { db_name: database.name, coll_name: collection.name, diff --git a/lib/mongo/operation/create/op_msg.rb b/lib/mongo/operation/create/op_msg.rb index 25212cfad1..f186261ba1 100644 --- a/lib/mongo/operation/create/op_msg.rb +++ b/lib/mongo/operation/create/op_msg.rb @@ -24,6 +24,7 @@ class Create # @since 2.5.2 class OpMsg < OpMsgBase include ExecutableTransactionLabel + include CausalConsistencySupported private diff --git a/lib/mongo/operation/create_index/op_msg.rb b/lib/mongo/operation/create_index/op_msg.rb index b230d66a01..108b30cef1 100644 --- a/lib/mongo/operation/create_index/op_msg.rb +++ b/lib/mongo/operation/create_index/op_msg.rb @@ -24,6 +24,7 @@ class CreateIndex # @since 2.5.2 class OpMsg < OpMsgBase include ExecutableTransactionLabel + include CausalConsistencySupported private diff --git a/lib/mongo/operation/delete/op_msg.rb b/lib/mongo/operation/delete/op_msg.rb index 3c70119bdf..6d94f6d288 100644 --- a/lib/mongo/operation/delete/op_msg.rb +++ b/lib/mongo/operation/delete/op_msg.rb @@ -27,6 +27,7 @@ class OpMsg < OpMsgBase include ExecutableNoValidate include ExecutableTransactionLabel include PolymorphicResult + include CausalConsistencySupported private diff --git a/lib/mongo/operation/drop/op_msg.rb b/lib/mongo/operation/drop/op_msg.rb index afeb8f9fba..6ef2aa3418 100644 --- a/lib/mongo/operation/drop/op_msg.rb +++ b/lib/mongo/operation/drop/op_msg.rb @@ -24,6 +24,7 @@ class Drop # @since 2.5.2 class OpMsg < OpMsgBase include ExecutableTransactionLabel + include CausalConsistencySupported end end end diff --git a/lib/mongo/operation/drop_database/op_msg.rb b/lib/mongo/operation/drop_database/op_msg.rb index 4a7736d91a..2937aed21e 100644 --- a/lib/mongo/operation/drop_database/op_msg.rb +++ b/lib/mongo/operation/drop_database/op_msg.rb @@ -24,6 +24,7 @@ class DropDatabase # @since 2.5.2 class OpMsg < OpMsgBase include ExecutableTransactionLabel + include CausalConsistencySupported end end end diff --git a/lib/mongo/operation/drop_index/op_msg.rb b/lib/mongo/operation/drop_index/op_msg.rb index b5bb048626..1eed7ab8a3 100644 --- a/lib/mongo/operation/drop_index/op_msg.rb +++ b/lib/mongo/operation/drop_index/op_msg.rb @@ -24,6 +24,7 @@ class DropIndex # @since 2.5.2 class OpMsg < OpMsgBase include ExecutableTransactionLabel + include CausalConsistencySupported private diff --git a/lib/mongo/operation/insert/op_msg.rb b/lib/mongo/operation/insert/op_msg.rb index d67744476f..d3c29051c1 100644 --- a/lib/mongo/operation/insert/op_msg.rb +++ b/lib/mongo/operation/insert/op_msg.rb @@ -28,6 +28,7 @@ class OpMsg < OpMsgBase include ExecutableNoValidate include ExecutableTransactionLabel include PolymorphicResult + include CausalConsistencySupported private diff --git a/lib/mongo/operation/shared/causal_consistency_supported.rb b/lib/mongo/operation/shared/causal_consistency_supported.rb index 3b1c1187be..ab15a67544 100644 --- a/lib/mongo/operation/shared/causal_consistency_supported.rb +++ b/lib/mongo/operation/shared/causal_consistency_supported.rb @@ -28,8 +28,10 @@ module CausalConsistencySupported # # This method overrides the causal consistency addition logic of # SessionsSupported and is meant to be used with operations classified - # as "read operations accepting a read concern", as these are defined - # in the causal consistency spec. + # as "read and write operations accepting a read concern", as these are + # defined in the causal consistency spec. For write operations this + # attaches only afterClusterTime (no read concern level) so that causal + # ordering is preserved across writes in a causally consistent session. # # In order for the override to work correctly the # CausalConsistencySupported module must be included after diff --git a/lib/mongo/operation/update/op_msg.rb b/lib/mongo/operation/update/op_msg.rb index e394164e27..59d00e89cf 100644 --- a/lib/mongo/operation/update/op_msg.rb +++ b/lib/mongo/operation/update/op_msg.rb @@ -27,6 +27,7 @@ class OpMsg < OpMsgBase include ExecutableNoValidate include ExecutableTransactionLabel include PolymorphicResult + include CausalConsistencySupported private diff --git a/lib/mongo/operation/write_command/op_msg.rb b/lib/mongo/operation/write_command/op_msg.rb index b7bd91cf62..81f2722040 100644 --- a/lib/mongo/operation/write_command/op_msg.rb +++ b/lib/mongo/operation/write_command/op_msg.rb @@ -21,6 +21,8 @@ class WriteCommand # # @api private class OpMsg < OpMsgBase + include CausalConsistencySupported + private def selector(connection) diff --git a/spec/runners/unified/crud_operations.rb b/spec/runners/unified/crud_operations.rb index 69f4f9c7d1..4179a6d189 100644 --- a/spec/runners/unified/crud_operations.rb +++ b/spec/runners/unified/crud_operations.rb @@ -216,6 +216,9 @@ def update_many(op) timeout_ms: args.use('timeoutMS'), max_time_ms: args.use('maxTimeMS') } + if session = args.use('session') + opts[:session] = entities.get(:session, session) + end collection.update_many(args.use!('filter'), args.use!('update'), **opts) end end @@ -223,9 +226,7 @@ def update_many(op) def replace_one(op) collection = entities.get(:collection, op.use!('object')) use_arguments(op) do |args| - collection.replace_one( - args.use!('filter'), - args.use!('replacement'), + opts = { comment: args.use('comment'), upsert: args.use('upsert'), let: args.use('let'), @@ -233,6 +234,14 @@ def replace_one(op) timeout_ms: args.use('timeoutMS'), max_time_ms: args.use('maxTimeMS'), sort: args.use('sort') + } + if session = args.use('session') + opts[:session] = entities.get(:session, session) + end + collection.replace_one( + args.use!('filter'), + args.use!('replacement'), + **opts ) end end @@ -264,6 +273,9 @@ def delete_many(op) timeout_ms: args.use('timeoutMS'), max_time_ms: args.use('maxTimeMS') } + if session = args.use('session') + opts[:session] = entities.get(:session, session) + end collection.delete_many(args.use!('filter'), **opts) end end @@ -276,6 +288,9 @@ def bulk_write(op) end opts = {} opts[:ordered] = args.use!('ordered') if args.key?('ordered') + if session = args.use('session') + opts[:session] = entities.get(:session, session) + end if comment = args.use('comment') opts[:comment] = comment end diff --git a/spec/runners/unified/ddl_operations.rb b/spec/runners/unified/ddl_operations.rb index bc95e4ca83..05a8c3273e 100644 --- a/spec/runners/unified/ddl_operations.rb +++ b/spec/runners/unified/ddl_operations.rb @@ -97,8 +97,23 @@ def list_colls(op, name_only: false) def drop_collection(op) database = entities.get(:database, op.use!('object')) use_arguments(op) do |args| + opts = {} + if session = args.use('session') + opts[:session] = entities.get(:session, session) + end collection = database[args.use!('collection')] - collection.drop + collection.drop(**opts) + end + end + + def drop_database(op) + client = entities.get(:client, op.use!('object')) + use_arguments(op) do |args| + opts = {} + if session = args.use('session') + opts[:session] = entities.get(:session, session) + end + client.use(args.use!('database')).database.drop(**opts) end end @@ -158,6 +173,9 @@ def drop_indexes(op) collection = entities.get(:collection, op.use!('object')) use_arguments(op) do |args| opts = extract_options(args, 'maxTimeMS', 'timeoutMS', allow_extra: true) + if session = args.use('session') + opts[:session] = entities.get(:session, session) + end collection.indexes.drop_all(**opts) end end diff --git a/spec/spec_tests/causal_consistency_unified_spec.rb b/spec/spec_tests/causal_consistency_unified_spec.rb new file mode 100644 index 0000000000..f205887d56 --- /dev/null +++ b/spec/spec_tests/causal_consistency_unified_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require 'runners/unified' + +base = "#{CURRENT_PATH}/spec_tests/data/causal_consistency_unified" +CAUSAL_CONSISTENCY_UNIFIED_TESTS = Dir.glob("#{base}/**/*.yml").sort + +describe 'Causal consistency unified spec tests' do + define_unified_spec_tests(base, CAUSAL_CONSISTENCY_UNIFIED_TESTS) +end diff --git a/spec/spec_tests/data/causal_consistency_unified/causal-consistency-clientBulkWrite.yml b/spec/spec_tests/data/causal_consistency_unified/causal-consistency-clientBulkWrite.yml new file mode 100644 index 0000000000..d1902e188e --- /dev/null +++ b/spec/spec_tests/data/causal_consistency_unified/causal-consistency-clientBulkWrite.yml @@ -0,0 +1,75 @@ +description: "causal consistency bulkWrite include afterClusterTime" + +schemaVersion: "1.3" + +runOnRequirements: + - minServerVersion: "8.0" + topologies: [replicaset, sharded, load-balanced] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + uriOptions: + retryWrites: false + observeEvents: [commandStartedEvent] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName causal-consistency-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + sessionOptions: + causalConsistency: true + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +# In a causally consistent session, once an operationTime has been established by a prior +# operation, subsequent write commands MUST include readConcern.afterClusterTime so the +# server can apply the write causally after the previously-observed data. + +tests: + - description: "clientBulkWrite includes afterClusterTime in causally consistent session" + operations: + - name: find + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: [{ _id: 1, x: 11 }] + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: causal-consistency-tests.test + document: { _id: 4 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: find + command: + find: *collectionName + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + - commandStartedEvent: + commandName: bulkWrite + command: + bulkWrite: 1 + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } diff --git a/spec/spec_tests/data/causal_consistency_unified/causal-consistency-write-commands.yml b/spec/spec_tests/data/causal_consistency_unified/causal-consistency-write-commands.yml new file mode 100644 index 0000000000..a33ca17436 --- /dev/null +++ b/spec/spec_tests/data/causal_consistency_unified/causal-consistency-write-commands.yml @@ -0,0 +1,472 @@ +description: "causal consistency write commands include afterClusterTime" + +schemaVersion: "1.3" + +runOnRequirements: + - topologies: [replicaset, sharded, load-balanced] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + uriOptions: + retryWrites: false + observeEvents: [commandStartedEvent] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName causal-consistency-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + sessionOptions: + causalConsistency: true + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +# In a causally consistent session, once an operationTime has been established by a prior +# operation, subsequent write commands MUST include readConcern.afterClusterTime so the +# server can apply the write causally after the previously-observed data. + +tests: + - description: "insertOne includes afterClusterTime in causally consistent session" + operations: + - &find + name: find + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: [{ _id: 1, x: 11 }] + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 4 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 4 } } + expectEvents: + - client: *client0 + events: + - &findEvent + commandStartedEvent: + commandName: find + command: + find: *collectionName + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + - commandStartedEvent: + commandName: insert + command: + insert: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "insertMany includes afterClusterTime in causally consistent session" + operations: + - *find + - name: insertMany + object: *collection0 + arguments: + session: *session0 + documents: + - { _id: 4 } + - { _id: 5 } + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: insert + command: + insert: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "updateOne includes afterClusterTime in causally consistent session" + operations: + - *find + - name: updateOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $set: { x: 100 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: update + command: + update: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "updateMany includes afterClusterTime in causally consistent session" + operations: + - *find + - name: updateMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gt: 0 } } + update: { $set: { updated: true } } + expectResult: + matchedCount: 3 + modifiedCount: 3 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: update + command: + update: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "replaceOne includes afterClusterTime in causally consistent session" + operations: + - *find + - name: replaceOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { x: 100 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: update + command: + update: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "deleteOne includes afterClusterTime in causally consistent session" + operations: + - *find + - name: deleteOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: + deletedCount: 1 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: delete + command: + delete: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "deleteMany includes afterClusterTime in causally consistent session" + operations: + - *find + - name: deleteMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gt: 0 } } + expectResult: + deletedCount: 3 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: delete + command: + delete: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "findOneAndUpdate includes afterClusterTime in causally consistent session" + operations: + - *find + - name: findOneAndUpdate + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $set: { x: 100 } } + expectResult: { _id: 1, x: 11 } + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: findAndModify + command: + findAndModify: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "findOneAndDelete includes afterClusterTime in causally consistent session" + operations: + - *find + - name: findOneAndDelete + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: { _id: 1, x: 11 } + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: findAndModify + command: + findAndModify: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "findOneAndReplace includes afterClusterTime in causally consistent session" + operations: + - *find + - name: findOneAndReplace + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { x: 100 } + expectResult: { _id: 1, x: 11 } + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: findAndModify + command: + findAndModify: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "bulkWrite includes afterClusterTime in causally consistent session" + operations: + - *find + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - insertOne: + document: { _id: 4 } + - updateOne: + filter: { _id: 2 } + update: { $set: { x: 100 } } + - deleteOne: + filter: { _id: 3 } + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: insert + command: + insert: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + - commandStartedEvent: + commandName: update + command: + update: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + - commandStartedEvent: + commandName: delete + command: + delete: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "create includes afterClusterTime in causally consistent session" + operations: + - *find + # Drop the collection first to make sure there's no name conflict during + # createCollection. + - name: dropCollection + object: *database0 + arguments: + session: *session0 + collection: &newCollectionName causal-consistency-createCollection-test + - name: createCollection + object: *database0 + arguments: + session: *session0 + collection: *newCollectionName + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: drop + command: + drop: *newCollectionName + lsid: { $$sessionLsid: *session0 } + - commandStartedEvent: + commandName: create + command: + create: *newCollectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "createIndexes includes afterClusterTime in causally consistent session" + operations: + - *find + - name: createIndex + object: *collection0 + arguments: + session: *session0 + keys: { x: 1 } + name: x_1 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: createIndexes + command: + createIndexes: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "drop includes afterClusterTime in causally consistent session" + operations: + - *find + - name: dropCollection + object: *database0 + arguments: + session: *session0 + collection: *collectionName + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: drop + command: + drop: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "dropDatabase includes afterClusterTime in causally consistent session" + operations: + - *find + - name: dropDatabase + object: *client0 + arguments: + session: *session0 + database: causal-consistency-dropDatabase-test + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: dropDatabase + command: + dropDatabase: 1 + $db: causal-consistency-dropDatabase-test + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + - description: "dropIndexes includes afterClusterTime in causally consistent session" + operations: + - *find + - name: dropIndexes + object: *collection0 + arguments: + session: *session0 + expectEvents: + - client: *client0 + events: + - *findEvent + - commandStartedEvent: + commandName: dropIndexes + command: + dropIndexes: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: + afterClusterTime: { $$exists: true } + level: { $$exists: false } + + # Covers the same condition as Causal Consistency prose test #2, but for a write operation. + - description: "first write command in a causally consistent session does not include afterClusterTime" + operations: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 4 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 4 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: insert + command: + insert: *collectionName + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } diff --git a/spec/spec_tests/data/transactions/commit.yml b/spec/spec_tests/data/transactions/commit.yml index 3758bf57da..513e637be6 100644 --- a/spec/spec_tests/data/transactions/commit.yml +++ b/spec/spec_tests/data/transactions/commit.yml @@ -509,7 +509,10 @@ tests: documents: - _id: 2 ordered: true + # This write follows an ended transaction in the same causally + # consistent session, so it carries afterClusterTime. readConcern: + afterClusterTime: 42 lsid: session0 txnNumber: startTransaction: @@ -589,7 +592,10 @@ tests: documents: - _id: 2 ordered: true + # This write follows an ended transaction in the same causally + # consistent session, so it carries afterClusterTime. readConcern: + afterClusterTime: 42 lsid: session0 txnNumber: startTransaction: diff --git a/spec/spec_tests/data/transactions/retryable-writes.yml b/spec/spec_tests/data/transactions/retryable-writes.yml index 37eaaa962d..89f6adc7f2 100644 --- a/spec/spec_tests/data/transactions/retryable-writes.yml +++ b/spec/spec_tests/data/transactions/retryable-writes.yml @@ -96,7 +96,10 @@ tests: documents: - _id: 2 ordered: true + # This retryable write follows an ended transaction in the same + # causally consistent session, so it carries afterClusterTime. readConcern: + afterClusterTime: 42 lsid: session0 txnNumber: $numberLong: "2" @@ -139,7 +142,10 @@ tests: - _id: 4 - _id: 5 ordered: true + # This retryable write follows an ended transaction in the same + # causally consistent session, so it carries afterClusterTime. readConcern: + afterClusterTime: 42 lsid: session0 txnNumber: $numberLong: "4" diff --git a/spec/spec_tests/data/transactions_api/callback-aborts.yml b/spec/spec_tests/data/transactions_api/callback-aborts.yml index fcb4f39163..a11a248918 100644 --- a/spec/spec_tests/data/transactions_api/callback-aborts.yml +++ b/spec/spec_tests/data/transactions_api/callback-aborts.yml @@ -159,7 +159,10 @@ tests: lsid: session0 # omitted fields autocommit: ~ - readConcern: ~ + # This write follows a committed/aborted transaction in the same + # causally consistent session, so it carries afterClusterTime. + readConcern: + afterClusterTime: 42 startTransaction: ~ writeConcern: ~ command_name: insert diff --git a/spec/spec_tests/data/transactions_api/callback-commits.yml b/spec/spec_tests/data/transactions_api/callback-commits.yml index 6eb6bc6fcc..dda8209019 100644 --- a/spec/spec_tests/data/transactions_api/callback-commits.yml +++ b/spec/spec_tests/data/transactions_api/callback-commits.yml @@ -191,7 +191,10 @@ tests: lsid: session0 # omitted fields autocommit: ~ - readConcern: ~ + # This write follows a committed/aborted transaction in the same + # causally consistent session, so it carries afterClusterTime. + readConcern: + afterClusterTime: 42 startTransaction: ~ writeConcern: ~ command_name: insert