google-cloud-spanner 2.28.0 → 2.30.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.
@@ -0,0 +1,125 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "concurrent"
16
+ require "google/cloud/spanner/session"
17
+ require "google/cloud/spanner/session_creation_options"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Spanner
22
+ # Cache for the multiplex `{Google::Cloud::Spanner::Session}` instance.
23
+ # @private
24
+ class SessionCache
25
+ # Time in seconds before this SessionCache will refresh the session.
26
+ # Counted from the session creation time (not from last usage).
27
+ # This is specific to multiplex sessions.
28
+ # The backend can keep sessions alive for quite a bit longer (28 days) but
29
+ # we perform refresh after 7 days.
30
+ # @private
31
+ SESSION_REFRESH_SEC = 7 * 24 * 3600
32
+
33
+ # Create a single-session "cache" for multiplex sessions.
34
+ # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` reference.
35
+ # @param session_creation_options [::Google::Cloud::Spanner::SessionCreationOptions] Required.
36
+ # @private
37
+ def initialize service, session_creation_options
38
+ @service = service
39
+ @session_creation_options = session_creation_options
40
+ @mutex = Mutex.new
41
+ @session = nil
42
+ end
43
+
44
+ # Yields the current session to run an operation (or a series of operations) on.
45
+ # @yield session A session to run requests on
46
+ # @yieldparam session [::Google::Cloud::Spanner::Session]
47
+ # @private
48
+ # @yieldreturn [::Object] The result of the operation that ran on a session.
49
+ # @return [::Object] The value returned by the yielded block.
50
+ def with_session
51
+ ensure_session!
52
+ yield @session
53
+ end
54
+
55
+ # Re-initializes the session in the session cache.
56
+ # @private
57
+ # @return [::Boolean]
58
+ def reset!
59
+ @mutex.synchronize do
60
+ @session = create_new_session
61
+ end
62
+
63
+ true
64
+ end
65
+
66
+ # Closes the pool. This is a NOP for Multiplex Session Cache since
67
+ # multiplex sessions don't require cleanup.
68
+ # @private
69
+ # @return [::Boolean]
70
+ def close
71
+ true
72
+ end
73
+
74
+ # Returns the current session. For use in the `{Spanner::BatchClient}`
75
+ # where usage pattern is incompatible with `with_session`.
76
+ # For other uses please use `with_session` instead.
77
+ # @private
78
+ # @return [::Google::Cloud::Spanner::Session]
79
+ def session
80
+ ensure_session!
81
+ @session
82
+ end
83
+
84
+ private
85
+
86
+ # Ensures that a single session exists and is current.
87
+ # @private
88
+ # @return [nil]
89
+ def ensure_session!
90
+ return unless @session.nil? || @session.existed_since?(SESSION_REFRESH_SEC)
91
+
92
+ @mutex.synchronize do
93
+ return unless @session.nil? || @session.existed_since?(SESSION_REFRESH_SEC)
94
+ @session = create_new_session
95
+ end
96
+
97
+ nil
98
+ end
99
+
100
+ # Creates a new multiplexed `Spanner::Session`.
101
+ # @private
102
+ # @return [::Google::Cloud::Spanner::Session]
103
+ def create_new_session
104
+ ensure_service!
105
+ grpc = @service.create_session(
106
+ @session_creation_options.database_path,
107
+ labels: @session_creation_options.session_labels,
108
+ database_role: @session_creation_options.session_creator_role,
109
+ multiplexed: true
110
+ )
111
+
112
+ Session.from_grpc grpc, @service, query_options: @session_creation_options.query_options
113
+ end
114
+
115
+ # Raise an error unless an active connection to the service is available.
116
+ # @private
117
+ # @raise [::StandardError]
118
+ # @return [void]
119
+ def ensure_service!
120
+ raise "Must have active connection to service" unless @service
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,70 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Google
16
+ module Cloud
17
+ module Spanner
18
+ # Options for creating new sessions that clients use
19
+ # to parametrize Pool and SessionCache.
20
+ # Example: session labels.
21
+ # @private
22
+ class SessionCreationOptions
23
+ # The full path to the Spanner database. Values are of the form:
24
+ # `projects/<project_id>/instances/<instance_id>/databases/<database_id>.
25
+ # @private
26
+ # @return [::String]
27
+ attr_reader :database_path
28
+
29
+ # The labels to be applied to all sessions created by the client.
30
+ # Optional. Example: `"team" => "billing-service"`.
31
+ # @private
32
+ # @return [::Hash, nil]
33
+ attr_reader :session_labels
34
+
35
+ # The Spanner session creator role.
36
+ # Optional. Example: `analyst`.
37
+ # @return [::String, nil]
38
+ attr_reader :session_creator_role
39
+
40
+ # A hash of values to specify the custom query options for executing SQL query.
41
+ # Optional. Example option: `:optimizer_version`.
42
+ # @private
43
+ # @return [::Hash, nil]
44
+ attr_reader :query_options
45
+
46
+ # Creates a new SessionCreationOptions object.
47
+ # @param database_path [::String]
48
+ # The full path to the Spanner database. Values are of the form:
49
+ # `projects/<project_id>/instances/<instance_id>/databases/<database_id>.
50
+ # @param session_labels [::Hash, nil] Optional. The labels to be applied to all sessions
51
+ # created by the client. Example: `"team" => "billing-service"`.
52
+ # @param session_creator_role [::String, nil] Optional. The Spanner session creator role.
53
+ # Example: `analyst`.
54
+ # @param query_options [::Hash, nil] Optional. A hash of values to specify the custom
55
+ # query options for executing SQL query. Example option: `:optimizer_version`.
56
+ # @private
57
+ def initialize database_path:, session_labels: nil, session_creator_role: nil, query_options: nil
58
+ if database_path.nil? || database_path.empty?
59
+ raise ArgumentError, "database_path is required for session creation options"
60
+ end
61
+
62
+ @database_path = database_path
63
+ @session_labels = session_labels
64
+ @session_creator_role = session_creator_role
65
+ @query_options = query_options
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -41,9 +41,26 @@ module Google
41
41
  # end
42
42
  #
43
43
  class Snapshot
44
- # @private The Session object.
44
+ # A `V1::Session` reference.
45
+ # @private
46
+ # @return [::Google::Cloud::Spanner::V1::Session]
45
47
  attr_accessor :session
46
48
 
49
+ # Creates a new `Spanner::Snapshot` instance.
50
+ # @param grpc [::Google::Cloud::Spanner::V1::Transaction]
51
+ # Underlying `V1::Transaction` object.
52
+ # @param session [::Google::Cloud::Spanner::Session] A `Spanner::Session` reference.
53
+ # @param directed_read_options [::Hash, nil] Optional. Client options used to set
54
+ # the `directed_read_options` for all ReadRequests and ExecuteSqlRequests.
55
+ # Converts to `V1::DirectedReadOptions`. Example option: `:exclude_replicas`.
56
+ # @private
57
+ # @return [::Google::Cloud::Spanner::Snapshot]
58
+ def initialize grpc, session, directed_read_options: nil
59
+ @grpc = grpc
60
+ @session = session
61
+ @directed_read_options = directed_read_options
62
+ end
63
+
47
64
  ##
48
65
  # Identifier of the transaction results were run in.
49
66
  # @return [String] The transaction id.
@@ -131,6 +148,18 @@ module Google
131
148
  # available optimizer version.
132
149
  # * `:optimizer_statistics_package` (String) Statistics package to
133
150
  # use. Empty to use the database default.
151
+ # @param [Hash] request_options Common request options.
152
+ #
153
+ # * `:priority` (Symbol) The relative priority for requests.
154
+ # The priority acts as a hint to the Cloud Spanner scheduler
155
+ # and does not guarantee priority or order of execution.
156
+ # Valid values are `:PRIORITY_LOW`, `:PRIORITY_MEDIUM`,
157
+ # `:PRIORITY_HIGH`. If priority not set then default is
158
+ # `PRIORITY_UNSPECIFIED` is equivalent to `:PRIORITY_HIGH`.
159
+ # * `:tag` (String) A per-request tag which can be applied to
160
+ # queries or reads, used for statistics collection. Tag must be a
161
+ # valid identifier of the form: `[a-zA-Z][a-zA-Z0-9_\-]` between 2
162
+ # and 64 characters in length.
134
163
  # @param [Hash] call_options A hash of values to specify the custom
135
164
  # call options, e.g., timeout, retries, etc. Call options are
136
165
  # optional. The following settings can be provided:
@@ -304,13 +333,17 @@ module Google
304
333
  # end
305
334
  #
306
335
  def execute_query sql, params: nil, types: nil, query_options: nil,
307
- call_options: nil, directed_read_options: nil
336
+ request_options: nil, call_options: nil,
337
+ directed_read_options: nil
308
338
  ensure_session!
309
339
 
310
340
  params, types = Convert.to_input_params_and_types params, types
341
+ request_options = Convert.to_request_options request_options,
342
+ tag_type: :request_tag
311
343
  session.execute_query sql, params: params, types: types,
312
344
  transaction: tx_selector,
313
345
  query_options: query_options,
346
+ request_options: request_options,
314
347
  call_options: call_options,
315
348
  directed_read_options: directed_read_options || @directed_read_options
316
349
  end
@@ -334,6 +367,18 @@ module Google
334
367
  # Optional.
335
368
  # @param [Integer] limit If greater than zero, no more than this number
336
369
  # of rows will be returned. The default is no limit.
370
+ # @param [Hash] request_options Common request options.
371
+ #
372
+ # * `:priority` (Symbol) The relative priority for requests.
373
+ # The priority acts as a hint to the Cloud Spanner scheduler
374
+ # and does not guarantee priority or order of execution.
375
+ # Valid values are `:PRIORITY_LOW`, `:PRIORITY_MEDIUM`,
376
+ # `:PRIORITY_HIGH`. If priority not set then default is
377
+ # `PRIORITY_UNSPECIFIED` is equivalent to `:PRIORITY_HIGH`.
378
+ # * `:tag` (String) A per-request tag which can be applied to
379
+ # queries or reads, used for statistics collection. Tag must be a
380
+ # valid identifier of the form: `[a-zA-Z][a-zA-Z0-9_\-]` between 2
381
+ # and 64 characters in length.
337
382
  # @param [Hash] call_options A hash of values to specify the custom
338
383
  # call options, e.g., timeout, retries, etc. Call options are
339
384
  # optional. The following settings can be provided:
@@ -380,7 +425,7 @@ module Google
380
425
  # end
381
426
  #
382
427
  def read table, columns, keys: nil, index: nil, limit: nil,
383
- call_options: nil, directed_read_options: nil
428
+ request_options: nil, call_options: nil, directed_read_options: nil
384
429
  ensure_session!
385
430
 
386
431
  columns = Array(columns).map(&:to_s)
@@ -388,6 +433,7 @@ module Google
388
433
 
389
434
  session.read table, columns, keys: keys, index: index, limit: limit,
390
435
  transaction: tx_selector,
436
+ request_options: request_options,
391
437
  call_options: call_options,
392
438
  directed_read_options: directed_read_options || @directed_read_options
393
439
  end
@@ -507,15 +553,18 @@ module Google
507
553
  exclude_end: exclude_end
508
554
  end
509
555
 
510
- ##
511
- # @private Creates a new Snapshot instance from a
556
+ # Creates a new `Spanner::Snapshot` instance from a
512
557
  # `Google::Cloud::Spanner::V1::Transaction`.
513
- def self.from_grpc grpc, session, directed_read_options
514
- new.tap do |s|
515
- s.instance_variable_set :@grpc, grpc
516
- s.instance_variable_set :@session, session
517
- s.instance_variable_set :@directed_read_options, directed_read_options
518
- end
558
+ # @param grpc [::Google::Cloud::Spanner::V1::Transaction]
559
+ # Underlying `V1::Transaction` object.
560
+ # @param session [::Google::Cloud::Spanner::Session] A `Spanner::Session` reference.
561
+ # @param directed_read_options [::Hash, nil] Optional. Client options used to set
562
+ # the `directed_read_options` for all ReadRequests and ExecuteSqlRequests.
563
+ # Converts to `V1::DirectedReadOptions`. Example option: `:exclude_replicas`.
564
+ # @private
565
+ # @return [::Google::Cloud::Spanner::Snapshot]
566
+ def self.from_grpc grpc, session, directed_read_options: nil
567
+ new grpc, session, directed_read_options: directed_read_options
519
568
  end
520
569
 
521
570
  protected
@@ -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.28.0".freeze
19
+ VERSION = "2.30.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.28.0
4
+ version: 2.30.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