google-cloud-spanner 2.29.0 → 2.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -97,19 +97,42 @@ module Google
97
97
  # @return [::Boolean]
98
98
  attr_accessor :exclude_txn_from_change_streams
99
99
 
100
+ # A token that is required when committing an RW transaction over a multiplexed session
101
+ # It can be read when transaction is created (either by BeginTransaction or by inlined begin),
102
+ # or from a previous operation within existing transaction.
103
+ # @private
104
+ # @return [::Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken, nil]
105
+ attr_accessor :precommit_token
106
+
107
+ # An id of the previous transaction, if this new transaction wrapper is being created
108
+ # as a part of a retry. Previous transaction id should be added to TransactionOptions
109
+ # of a new ReadWrite transaction when retry is attempted.
110
+ # @private
111
+ # @return [::String, nil]
112
+ attr_reader :previous_transaction_id
113
+
100
114
  # Creates a new `Spanner::Transaction` instance from a `V1::Transaction` object.
101
115
  # @param grpc [::Google::Cloud::Spanner::V1::Transaction] Underlying `V1::Transaction` object.
102
116
  # @param session [::Google::Cloud::Spanner::Session] The session this transaction is running in.
103
117
  # @param exclude_txn_from_change_streams [::Boolean]
104
118
  # When `exclude_txn_from_change_streams` is set to `true`, it prevents read
105
119
  # or write transactions from being tracked in change streams.
120
+ # @param previous_transaction_id [::String, nil] Optional.
121
+ # An id of the previous transaction, if this new transaction wrapper is being created
122
+ # as a part of a retry. Previous transaction id should be added to TransactionOptions
123
+ # of a new ReadWrite transaction when retry is attempted.
106
124
  # @private
107
125
  # @return [::Google::Cloud::Spanner::Transaction]
108
- def initialize grpc, session, exclude_txn_from_change_streams
126
+ def initialize grpc, session, exclude_txn_from_change_streams, previous_transaction_id: nil
109
127
  @grpc = grpc
110
128
  @session = session
111
129
  @exclude_txn_from_change_streams = exclude_txn_from_change_streams
112
130
 
131
+ # throwing away empty strings for simplicity
132
+ unless previous_transaction_id.nil? || previous_transaction_id.empty?
133
+ @previous_transaction_id = previous_transaction_id
134
+ end
135
+
113
136
  @commit = Commit.new
114
137
  @seqno = 0
115
138
  @exclude_txn_from_change_streams = false
@@ -130,6 +153,14 @@ module Google
130
153
  # create a transaction must be synchronized, and any logic that depends on
131
154
  # the state of transaction creation must also be synchronized.
132
155
  @mutex = Mutex.new
156
+
157
+ # Precommit token is a piece of server-side bookkeeping pushed onto client-side
158
+ # as a part of MultiplexedSession update. Briefly, for a given read-write transaction on a
159
+ # Multiplexed session the client library must:
160
+ # 1. From all read operations, store the most recently received token.
161
+ # 2. Include this final token in the CommitRequest.
162
+ # @type [::Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken, nil]
163
+ @precommit_token = nil
133
164
  end
134
165
 
135
166
  ##
@@ -397,8 +428,11 @@ module Google
397
428
  query_options: query_options,
398
429
  request_options: request_options,
399
430
  call_options: call_options,
400
- route_to_leader: route_to_leader
401
- @grpc ||= results.transaction
431
+ route_to_leader: route_to_leader,
432
+ precommit_token_notify: method(:update_precommit_token!)
433
+
434
+ update_wrapped_transaction! results.transaction
435
+
402
436
  results
403
437
  end
404
438
  end
@@ -573,6 +607,11 @@ module Google
573
607
  query_options: query_options,
574
608
  request_options: request_options,
575
609
  call_options: call_options
610
+
611
+ # Since this method is calling `execute_query`, the transaction is going to be updated,
612
+ # and the `results` object is going to be set up with precommit token notification reference,
613
+ # so we don't need to do anything special here.
614
+
576
615
  # Stream all PartialResultSet to get ResultSetStats
577
616
  results.rows.to_a
578
617
  # Raise an error if there is not a row count returned
@@ -676,11 +715,59 @@ module Google
676
715
  request_options: request_options,
677
716
  call_options: call_options, &block
678
717
  batch_update_results = BatchUpdateResults.new response
679
- @grpc ||= batch_update_results.transaction
718
+ update_wrapped_transaction! batch_update_results.transaction
719
+ response.result_sets.each do |result_set|
720
+ update_precommit_token! result_set.precommit_token if result_set.precommit_token
721
+ end
680
722
  batch_update_results.row_counts
681
723
  end
682
724
  end
683
725
 
726
+ # Updates this `Spanner::Transaction` with a new underlying `V1::Transaction` object.
727
+ # This happens when this `Spanner::Transaction` is in a empty-wrapper mode
728
+ # (it was created by `Google::Cloud::Spanner::Session#create_empty_transaction`).
729
+ # In that mode the inner wrapped `grpc` object representing the `V1::Transaction` is nil,
730
+ # and (almost all) service request run using the "inline-begin" transactions.
731
+ # As part of "inline-begin", a new `V1::Transaction` is created server-side, returned with the
732
+ # results, and in turn should be saved as the new `grpc` object.
733
+ #
734
+ # ! This method is expected to be called from within `safe_execute()` method's block!
735
+ #
736
+ # This method also updates the precommit token, if the new underlying `V1::Transaction` has it.
737
+ #
738
+ # This is a mutator method.
739
+ # @param new_transaction [::Google::Cloud::Spanner::V1::Transaction]
740
+ # `V1::Transaction` object that was created on the server-side.
741
+ # @private
742
+ # @return [void]
743
+ def update_wrapped_transaction! new_transaction
744
+ return unless @grpc.nil?
745
+ return if new_transaction.nil?
746
+
747
+ @grpc = new_transaction
748
+ update_precommit_token! new_transaction.precommit_token
749
+ end
750
+
751
+ # Updates this transaction's precommit token but only if:
752
+ # * new token exists
753
+ # * new token's seq_num is greater.
754
+ #
755
+ # ! This method is expected to be called from within `safe_execute()` method's block!
756
+ #
757
+ # This is a mutator method.
758
+ # @param new_precommit_token [::Google::Cloud::Spanner::V1::MultiplexedSessionPrecommitToken, nil]
759
+ # the new precommit token, if any, from the latest service operation (e.g. from a ResultSet from a read).
760
+ # @private
761
+ # @return [void]
762
+ def update_precommit_token! new_precommit_token
763
+ if !new_precommit_token.nil? && (
764
+ @precommit_token.nil? ||
765
+ new_precommit_token.seq_num > @precommit_token.seq_num
766
+ )
767
+ @precommit_token = new_precommit_token
768
+ end
769
+ end
770
+
684
771
  ##
685
772
  # Read rows from a database table, as a simple alternative to
686
773
  # {#execute_query}.
@@ -754,8 +841,9 @@ module Google
754
841
  transaction: tx_selector,
755
842
  request_options: request_options,
756
843
  call_options: call_options,
757
- route_to_leader: route_to_leader
758
- @grpc ||= results.transaction
844
+ route_to_leader: route_to_leader,
845
+ precommit_token_notify: method(:update_precommit_token!)
846
+ update_wrapped_transaction! results.transaction
759
847
  results
760
848
  end
761
849
  end
@@ -1143,33 +1231,9 @@ module Google
1143
1231
  ColumnValue.commit_timestamp
1144
1232
  end
1145
1233
 
1146
- ##
1147
- # @private
1148
- # Keeps the transaction current by creating a new transaction.
1149
- def keepalive!
1150
- ensure_session!
1151
- @grpc = session.create_transaction.instance_variable_get :@grpc
1152
- end
1153
-
1154
- ##
1155
- # @private
1156
- # Permanently deletes the transaction and session.
1157
- def release!
1158
- ensure_session!
1159
- session.release!
1160
- end
1161
-
1162
- ##
1163
- # @private
1164
- # Determines if the transaction has been idle longer than the given
1165
- # duration.
1166
- def idle_since? duration
1167
- session.idle_since? duration
1168
- end
1169
-
1170
- ##
1171
- # @private
1172
1234
  # All of the mutations created in the transaction block.
1235
+ # @private
1236
+ # @return [Array<Google::Cloud::Spanner::V1::Mutation>]
1173
1237
  def mutations
1174
1238
  @commit.mutations
1175
1239
  end
@@ -1180,10 +1244,14 @@ module Google
1180
1244
  # @param exclude_txn_from_change_streams [::Boolean] Optional. Defaults to `false`.
1181
1245
  # When `exclude_txn_from_change_streams` is set to `true`, it prevents read
1182
1246
  # or write transactions from being tracked in change streams.
1247
+ # @param previous_transaction_id [::String, nil] Optional.
1248
+ # An id of the previous transaction, if this new transaction wrapper is being created
1249
+ # as a part of a retry. Previous transaction id should be added to TransactionOptions
1250
+ # of a new ReadWrite transaction when retry is attempted.
1183
1251
  # @private
1184
1252
  # @return [::Google::Cloud::Spanner::Transaction]
1185
- def self.from_grpc grpc, session, exclude_txn_from_change_streams: false
1186
- new grpc, session, exclude_txn_from_change_streams
1253
+ def self.from_grpc grpc, session, exclude_txn_from_change_streams: false, previous_transaction_id: nil
1254
+ new grpc, session, exclude_txn_from_change_streams, previous_transaction_id: previous_transaction_id
1187
1255
  end
1188
1256
 
1189
1257
  ##
@@ -1211,16 +1279,27 @@ module Google
1211
1279
  # @return [::Google::Cloud::Spanner::V1::Transaction, nil] The new transaction
1212
1280
  # object, or `nil` if a transaction already exists.
1213
1281
  def safe_begin_transaction! exclude_from_change_streams: false, request_options: nil, call_options: nil
1282
+ # If a read-write transaction on a multiplexed session commit mutations
1283
+ # without performing any reads or queries, one of the mutations from the mutation set
1284
+ # must be sent as a mutation key for `BeginTransaction`.
1285
+ # @type [::Google::Cloud::Spanner::V1::Mutation, nil]
1286
+ mutation_key = mutations[0] if mutations.any?
1287
+
1214
1288
  @mutex.synchronize do
1215
1289
  return if existing_transaction?
1216
1290
  ensure_session!
1217
1291
  route_to_leader = LARHeaders.begin_transaction true
1292
+
1293
+ # TODO: [virost@, 2025-10] fix this so it uses tx_selector
1294
+ # instead of re-creating it within `Service#begin_transaction`
1218
1295
  @grpc = service.begin_transaction(
1219
1296
  session.path,
1220
1297
  exclude_txn_from_change_streams: exclude_from_change_streams,
1221
1298
  request_options: request_options,
1222
1299
  call_options: call_options,
1223
- route_to_leader: route_to_leader
1300
+ route_to_leader: route_to_leader,
1301
+ mutation_key: mutation_key,
1302
+ previous_transaction_id: previous_transaction_id
1224
1303
  )
1225
1304
  end
1226
1305
  end
@@ -1266,9 +1345,18 @@ module Google
1266
1345
  # @return [::Google::Cloud::Spanner::V1::TransactionSelector]
1267
1346
  def tx_selector exclude_txn_from_change_streams: false
1268
1347
  return V1::TransactionSelector.new id: transaction_id if existing_transaction?
1348
+
1349
+ read_write = if @previous_transaction_id.nil?
1350
+ V1::TransactionOptions::ReadWrite.new
1351
+ else
1352
+ V1::TransactionOptions::ReadWrite.new(
1353
+ multiplexed_session_previous_transaction_id: @previous_transaction_id
1354
+ )
1355
+ end
1356
+
1269
1357
  V1::TransactionSelector.new(
1270
1358
  begin: V1::TransactionOptions.new(
1271
- read_write: V1::TransactionOptions::ReadWrite.new,
1359
+ read_write: read_write,
1272
1360
  exclude_txn_from_change_streams: exclude_txn_from_change_streams
1273
1361
  )
1274
1362
  )
@@ -16,7 +16,7 @@
16
16
  module Google
17
17
  module Cloud
18
18
  module Spanner
19
- VERSION = "2.29.0".freeze
19
+ VERSION = "2.31.0".freeze
20
20
  end
21
21
  end
22
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-cloud-spanner
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.29.0
4
+ version: 2.31.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Moore
@@ -161,6 +161,8 @@ files:
161
161
  - lib/google/cloud/spanner/results.rb
162
162
  - lib/google/cloud/spanner/service.rb
163
163
  - lib/google/cloud/spanner/session.rb
164
+ - lib/google/cloud/spanner/session_cache.rb
165
+ - lib/google/cloud/spanner/session_creation_options.rb
164
166
  - lib/google/cloud/spanner/snapshot.rb
165
167
  - lib/google/cloud/spanner/status.rb
166
168
  - lib/google/cloud/spanner/transaction.rb