google-cloud-spanner 2.17.0 → 2.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/google/cloud/spanner/batch_update_results.rb +50 -0
- data/lib/google/cloud/spanner/client.rb +31 -18
- data/lib/google/cloud/spanner/pool.rb +28 -94
- data/lib/google/cloud/spanner/project.rb +3 -7
- data/lib/google/cloud/spanner/results.rb +20 -37
- data/lib/google/cloud/spanner/service.rb +1 -11
- data/lib/google/cloud/spanner/session.rb +36 -17
- data/lib/google/cloud/spanner/transaction.rb +127 -19
- data/lib/google/cloud/spanner/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37f9332aedaf9d53b1eb13596dafa4b3571393449b85ce5e63750b5c57381e3f
|
4
|
+
data.tar.gz: b137394c7819e4795a98727e626772ece2404ef40bc7ef84153014768ddbfb89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ea41265627415904e066549390d59ad72a2be577c3b6f7ac67d8a47197e4f2df4a79f9c2c14735bcd383989b2f3bfa602fe68e3ebde1d7cc055252d8644f36f
|
7
|
+
data.tar.gz: 2cc9bf3bbe521e85c72ad2dd255eedf09225c0bbcc67bc4c319a4036aa7beac01c18f73a0fb6ff1fd203b96b15ff2b3bcbb82f2f8ddfd0f2aa72e1d2f31f03e9
|
data/CHANGELOG.md
CHANGED
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright 2023 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
|
+
|
16
|
+
module Google
|
17
|
+
module Cloud
|
18
|
+
module Spanner
|
19
|
+
##
|
20
|
+
# @private Helper class to process BatchDML response
|
21
|
+
class BatchUpdateResults
|
22
|
+
## Object of type
|
23
|
+
# Google::Cloud::Spanner::V1::ExecuteBatchDmlResponse
|
24
|
+
attr_reader :grpc
|
25
|
+
|
26
|
+
def initialize grpc
|
27
|
+
@grpc = grpc
|
28
|
+
end
|
29
|
+
|
30
|
+
def row_counts
|
31
|
+
if @grpc.status.code.zero?
|
32
|
+
@grpc.result_sets.map { |rs| rs.stats.row_count_exact }
|
33
|
+
else
|
34
|
+
begin
|
35
|
+
raise Google::Cloud::Error.from_error @grpc.status
|
36
|
+
rescue Google::Cloud::Error
|
37
|
+
raise Google::Cloud::Spanner::BatchUpdateError.from_grpc @grpc
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Returns transaction if available. Otherwise returns nil
|
44
|
+
def transaction
|
45
|
+
@grpc&.result_sets&.first&.metadata&.transaction
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -49,6 +49,10 @@ module Google
|
|
49
49
|
# end
|
50
50
|
#
|
51
51
|
class Client
|
52
|
+
##
|
53
|
+
# @private
|
54
|
+
IS_TRANSACTION_RUNNING_KEY = "ruby_spanner_is_transaction_running".freeze
|
55
|
+
|
52
56
|
##
|
53
57
|
# @private Creates a new Spanner Client instance.
|
54
58
|
def initialize project, instance_id, database_id, session_labels: nil,
|
@@ -1634,6 +1638,7 @@ module Google
|
|
1634
1638
|
# rubocop:disable Metrics/MethodLength
|
1635
1639
|
# rubocop:disable Metrics/BlockLength
|
1636
1640
|
|
1641
|
+
|
1637
1642
|
##
|
1638
1643
|
# Creates a transaction for reads and writes that execute atomically at
|
1639
1644
|
# a single logical point in time across columns, rows, and tables in a
|
@@ -1788,7 +1793,7 @@ module Google
|
|
1788
1793
|
def transaction deadline: 120, commit_options: nil,
|
1789
1794
|
request_options: nil, call_options: nil
|
1790
1795
|
ensure_service!
|
1791
|
-
unless Thread.current[
|
1796
|
+
unless Thread.current[IS_TRANSACTION_RUNNING_KEY].nil?
|
1792
1797
|
raise "Nested transactions are not allowed"
|
1793
1798
|
end
|
1794
1799
|
|
@@ -1799,17 +1804,20 @@ module Google
|
|
1799
1804
|
request_options = Convert.to_request_options \
|
1800
1805
|
request_options, tag_type: :transaction_tag
|
1801
1806
|
|
1802
|
-
@pool.
|
1807
|
+
@pool.with_session do |session|
|
1808
|
+
tx = session.create_empty_transaction
|
1803
1809
|
if request_options
|
1804
1810
|
tx.transaction_tag = request_options[:transaction_tag]
|
1805
1811
|
end
|
1806
1812
|
|
1807
1813
|
begin
|
1808
|
-
Thread.current[
|
1814
|
+
Thread.current[IS_TRANSACTION_RUNNING_KEY] = true
|
1809
1815
|
yield tx
|
1816
|
+
transaction_id = nil
|
1817
|
+
transaction_id = tx.transaction_id if tx.existing_transaction?
|
1810
1818
|
commit_resp = @project.service.commit \
|
1811
1819
|
tx.session.path, tx.mutations,
|
1812
|
-
transaction_id:
|
1820
|
+
transaction_id: transaction_id,
|
1813
1821
|
commit_options: commit_options,
|
1814
1822
|
request_options: request_options,
|
1815
1823
|
call_options: call_options
|
@@ -1819,28 +1827,21 @@ module Google
|
|
1819
1827
|
Google::Cloud::AbortedError,
|
1820
1828
|
GRPC::Internal,
|
1821
1829
|
Google::Cloud::InternalError => e
|
1822
|
-
|
1823
|
-
# Re-raise if deadline has passed
|
1824
|
-
if current_time - start_time > deadline
|
1825
|
-
if e.is_a? GRPC::BadStatus
|
1826
|
-
e = Google::Cloud::Error.from_error e
|
1827
|
-
end
|
1828
|
-
raise e
|
1829
|
-
end
|
1830
|
+
check_and_propagate_err! e, (current_time - start_time > deadline)
|
1830
1831
|
# Sleep the amount from RetryDelay, or incremental backoff
|
1831
1832
|
sleep(delay_from_aborted(e) || backoff *= 1.3)
|
1832
1833
|
# Create new transaction on the session and retry the block
|
1833
|
-
tx =
|
1834
|
+
tx = session.create_transaction
|
1834
1835
|
retry
|
1835
1836
|
rescue StandardError => e
|
1836
1837
|
# Rollback transaction when handling unexpected error
|
1837
|
-
tx.session.rollback tx.transaction_id
|
1838
|
+
tx.session.rollback tx.transaction_id if tx.existing_transaction?
|
1838
1839
|
# Return nil if raised with rollback.
|
1839
1840
|
return nil if e.is_a? Rollback
|
1840
1841
|
# Re-raise error.
|
1841
1842
|
raise e
|
1842
1843
|
ensure
|
1843
|
-
Thread.current[
|
1844
|
+
Thread.current[IS_TRANSACTION_RUNNING_KEY] = nil
|
1844
1845
|
end
|
1845
1846
|
end
|
1846
1847
|
end
|
@@ -1923,7 +1924,7 @@ module Google
|
|
1923
1924
|
exact_staleness: exact_staleness
|
1924
1925
|
|
1925
1926
|
ensure_service!
|
1926
|
-
unless Thread.current[
|
1927
|
+
unless Thread.current[IS_TRANSACTION_RUNNING_KEY].nil?
|
1927
1928
|
raise "Nested snapshots are not allowed"
|
1928
1929
|
end
|
1929
1930
|
|
@@ -1933,11 +1934,11 @@ module Google
|
|
1933
1934
|
timestamp: (timestamp || read_timestamp),
|
1934
1935
|
staleness: (staleness || exact_staleness),
|
1935
1936
|
call_options: call_options
|
1936
|
-
Thread.current[
|
1937
|
+
Thread.current[IS_TRANSACTION_RUNNING_KEY] = true
|
1937
1938
|
snp = Snapshot.from_grpc snp_grpc, session
|
1938
1939
|
yield snp if block_given?
|
1939
1940
|
ensure
|
1940
|
-
Thread.current[
|
1941
|
+
Thread.current[IS_TRANSACTION_RUNNING_KEY] = nil
|
1941
1942
|
end
|
1942
1943
|
nil
|
1943
1944
|
end
|
@@ -2272,6 +2273,18 @@ module Google
|
|
2272
2273
|
nil
|
2273
2274
|
end
|
2274
2275
|
|
2276
|
+
##
|
2277
|
+
# Determines if a transaction error should be propagated to the user.
|
2278
|
+
# And re-raises the error accordingly
|
2279
|
+
def check_and_propagate_err! err, deadline_passed
|
2280
|
+
raise err if internal_error_and_not_retryable? err
|
2281
|
+
return unless deadline_passed
|
2282
|
+
if err.is_a? GRPC::BadStatus
|
2283
|
+
raise Google::Cloud::Error.from_error err
|
2284
|
+
end
|
2285
|
+
raise err
|
2286
|
+
end
|
2287
|
+
|
2275
2288
|
def internal_error_and_not_retryable? error
|
2276
2289
|
(error.instance_of?(Google::Cloud::InternalError) ||
|
2277
2290
|
error.instance_of?(GRPC::Internal)) &&
|
@@ -29,19 +29,15 @@ module Google
|
|
29
29
|
# {Google::Cloud::Spanner::Session} instances.
|
30
30
|
#
|
31
31
|
class Pool
|
32
|
-
attr_accessor :
|
33
|
-
attr_accessor :
|
34
|
-
attr_accessor :transaction_stack
|
32
|
+
attr_accessor :sessions_available
|
33
|
+
attr_accessor :sessions_in_use
|
35
34
|
|
36
35
|
def initialize client, min: 10, max: 100, keepalive: 1800,
|
37
|
-
|
36
|
+
fail: true, threads: nil
|
38
37
|
@client = client
|
39
38
|
@min = min
|
40
39
|
@max = max
|
41
40
|
@keepalive = keepalive
|
42
|
-
@write_ratio = write_ratio
|
43
|
-
@write_ratio = 0 if write_ratio.negative?
|
44
|
-
@write_ratio = 1 if write_ratio > 1
|
45
41
|
@fail = fail
|
46
42
|
@threads = threads || [2, Concurrent.processor_count * 2].max
|
47
43
|
|
@@ -69,10 +65,11 @@ module Google
|
|
69
65
|
|
70
66
|
# Use LIFO to ensure sessions are used from backend caches, which
|
71
67
|
# will reduce the read / write latencies on user requests.
|
72
|
-
read_session =
|
73
|
-
|
74
|
-
|
75
|
-
|
68
|
+
read_session = sessions_available.pop # LIFO
|
69
|
+
if read_session
|
70
|
+
sessions_in_use << read_session
|
71
|
+
return read_session
|
72
|
+
end
|
76
73
|
|
77
74
|
if can_allocate_more_sessions?
|
78
75
|
action = :new
|
@@ -85,73 +82,25 @@ module Google
|
|
85
82
|
end
|
86
83
|
end
|
87
84
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
@mutex.synchronize do
|
93
|
-
unless all_sessions.include? session
|
94
|
-
raise ArgumentError, "Cannot checkin session"
|
85
|
+
if action == :new
|
86
|
+
session = new_session!
|
87
|
+
@mutex.synchronize do
|
88
|
+
sessions_in_use << session
|
95
89
|
end
|
96
|
-
|
97
|
-
session_stack.push session
|
98
|
-
|
99
|
-
@resource.signal
|
90
|
+
return session
|
100
91
|
end
|
101
92
|
|
102
93
|
nil
|
103
94
|
end
|
104
95
|
|
105
|
-
def
|
106
|
-
tx = checkout_transaction
|
107
|
-
begin
|
108
|
-
yield tx
|
109
|
-
ensure
|
110
|
-
future do
|
111
|
-
# Create and checkin a new transaction
|
112
|
-
tx = tx.session.create_transaction
|
113
|
-
checkin_transaction tx
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
def checkout_transaction
|
119
|
-
action = nil
|
120
|
-
@mutex.synchronize do
|
121
|
-
loop do
|
122
|
-
raise ClientClosedError if @closed
|
123
|
-
|
124
|
-
write_transaction = transaction_stack.pop # LIFO
|
125
|
-
return write_transaction if write_transaction
|
126
|
-
read_session = session_stack.pop
|
127
|
-
if read_session
|
128
|
-
action = read_session
|
129
|
-
break
|
130
|
-
end
|
131
|
-
|
132
|
-
if can_allocate_more_sessions?
|
133
|
-
action = :new
|
134
|
-
break
|
135
|
-
end
|
136
|
-
|
137
|
-
raise SessionLimitError if @fail
|
138
|
-
|
139
|
-
@resource.wait @mutex
|
140
|
-
end
|
141
|
-
end
|
142
|
-
if action.is_a? Google::Cloud::Spanner::Session
|
143
|
-
return action.create_transaction
|
144
|
-
end
|
145
|
-
return new_transaction! if action == :new
|
146
|
-
end
|
147
|
-
|
148
|
-
def checkin_transaction txn
|
96
|
+
def checkin_session session
|
149
97
|
@mutex.synchronize do
|
150
|
-
unless
|
98
|
+
unless sessions_in_use.include? session
|
151
99
|
raise ArgumentError, "Cannot checkin session"
|
152
100
|
end
|
153
101
|
|
154
|
-
|
102
|
+
sessions_available.push session
|
103
|
+
sessions_in_use.delete_if { |s| s.session_id == session.session_id }
|
155
104
|
|
156
105
|
@resource.signal
|
157
106
|
end
|
@@ -182,22 +131,20 @@ module Google
|
|
182
131
|
to_release = []
|
183
132
|
|
184
133
|
@mutex.synchronize do
|
185
|
-
available_count =
|
134
|
+
available_count = sessions_available.count
|
186
135
|
release_count = @min - available_count
|
187
136
|
release_count = 0 if release_count.negative?
|
188
137
|
|
189
|
-
to_keepalive +=
|
138
|
+
to_keepalive += sessions_available.select do |x|
|
190
139
|
x.idle_since? @keepalive
|
191
140
|
end
|
192
141
|
|
193
|
-
# Remove a random portion of the sessions
|
142
|
+
# Remove a random portion of the sessions
|
194
143
|
to_release = to_keepalive.sample release_count
|
195
144
|
to_keepalive -= to_release
|
196
145
|
|
197
146
|
# Remove those to be released from circulation
|
198
|
-
@
|
199
|
-
@session_stack -= to_release
|
200
|
-
@transaction_stack -= to_release
|
147
|
+
@sessions_available -= to_release
|
201
148
|
end
|
202
149
|
|
203
150
|
to_release.each { |x| future { x.release! } }
|
@@ -212,19 +159,11 @@ module Google
|
|
212
159
|
max_threads: @threads
|
213
160
|
# init the stacks
|
214
161
|
@new_sessions_in_process = 0
|
215
|
-
@transaction_stack = []
|
216
162
|
# init the keepalive task
|
217
163
|
create_keepalive_task!
|
218
164
|
# init session stack
|
219
|
-
@
|
220
|
-
|
221
|
-
num_transactions = (@min * @write_ratio).round
|
222
|
-
pending_transactions = sessions.shift num_transactions
|
223
|
-
# init transaction stack
|
224
|
-
pending_transactions.each do |transaction|
|
225
|
-
future { checkin_transaction transaction.create_transaction }
|
226
|
-
end
|
227
|
-
@session_stack = sessions
|
165
|
+
@sessions_available = @client.batch_create_new_sessions @min
|
166
|
+
@sessions_in_use = []
|
228
167
|
end
|
229
168
|
|
230
169
|
def shutdown
|
@@ -236,10 +175,10 @@ module Google
|
|
236
175
|
@resource.broadcast
|
237
176
|
# Delete all sessions
|
238
177
|
@mutex.synchronize do
|
239
|
-
|
240
|
-
|
241
|
-
@
|
242
|
-
@
|
178
|
+
sessions_available.each { |s| future { s.release! } }
|
179
|
+
sessions_in_use.each { |s| future { s.release! } }
|
180
|
+
@sessions_available = []
|
181
|
+
@sessions_in_use = []
|
243
182
|
end
|
244
183
|
# shutdown existing thread pool
|
245
184
|
@thread_pool.shutdown
|
@@ -261,19 +200,14 @@ module Google
|
|
261
200
|
|
262
201
|
@mutex.synchronize do
|
263
202
|
@new_sessions_in_process -= 1
|
264
|
-
all_sessions << session
|
265
203
|
end
|
266
204
|
|
267
205
|
session
|
268
206
|
end
|
269
207
|
|
270
|
-
def new_transaction!
|
271
|
-
new_session!.create_transaction
|
272
|
-
end
|
273
|
-
|
274
208
|
def can_allocate_more_sessions?
|
275
209
|
# This is expected to be called from within a synchronize block
|
276
|
-
|
210
|
+
sessions_available.size + sessions_in_use.size + @new_sessions_in_process < @max
|
277
211
|
end
|
278
212
|
|
279
213
|
def create_keepalive_task!
|
@@ -515,17 +515,14 @@ module Google
|
|
515
515
|
# before an attempt is made to prevent the idle sessions from being
|
516
516
|
# closed by the Cloud Spanner service. The default is 1800 (30
|
517
517
|
# minutes).
|
518
|
-
# * `:write_ratio` (Float) The ratio of sessions with pre-allocated
|
519
|
-
# transactions to those without. Pre-allocating transactions
|
520
|
-
# improves the performance of writes made by the client. The higher
|
521
|
-
# the value, the more transactions are pre-allocated. The value must
|
522
|
-
# be >= 0 and <= 1. The default is 0.3.
|
523
518
|
# * `:fail` (true/false) When `true` the client raises a
|
524
519
|
# {SessionLimitError} when the client has allocated the `max` number
|
525
520
|
# of sessions. When `false` the client blocks until a session
|
526
521
|
# becomes available. The default is `true`.
|
527
522
|
# * `:threads` (Integer) The number of threads in the thread pool. The
|
528
523
|
# default is twice the number of available CPUs.
|
524
|
+
# * `:write_ratio` (Float) Deprecated. This field is no longer needed
|
525
|
+
# and will be removed in a future release.
|
529
526
|
# @param [Hash] labels The labels to be applied to all sessions
|
530
527
|
# created by the client. Cloud Labels are a flexible and lightweight
|
531
528
|
# mechanism for organizing cloud resources into groups that reflect a
|
@@ -674,8 +671,7 @@ module Google
|
|
674
671
|
def valid_session_pool_options opts = {}
|
675
672
|
{
|
676
673
|
min: opts[:min], max: opts[:max], keepalive: opts[:keepalive],
|
677
|
-
|
678
|
-
threads: opts[:threads]
|
674
|
+
fail: opts[:fail], threads: opts[:threads]
|
679
675
|
}.compact
|
680
676
|
end
|
681
677
|
end
|
@@ -41,6 +41,11 @@ module Google
|
|
41
41
|
# end
|
42
42
|
#
|
43
43
|
class Results
|
44
|
+
##
|
45
|
+
# @private Object of type
|
46
|
+
# Google::Cloud::Spanner::V1::ResultSetMetadata
|
47
|
+
attr_reader :metadata
|
48
|
+
|
44
49
|
##
|
45
50
|
# The read timestamp chosen for single-use snapshots (read-only
|
46
51
|
# transactions).
|
@@ -73,6 +78,13 @@ module Google
|
|
73
78
|
@fields ||= Fields.from_grpc @metadata.row_type.fields
|
74
79
|
end
|
75
80
|
|
81
|
+
##
|
82
|
+
# @private
|
83
|
+
# Returns a transaction if available
|
84
|
+
def transaction
|
85
|
+
@metadata&.transaction
|
86
|
+
end
|
87
|
+
|
76
88
|
# rubocop:disable all
|
77
89
|
|
78
90
|
##
|
@@ -297,49 +309,20 @@ module Google
|
|
297
309
|
end
|
298
310
|
|
299
311
|
# @private
|
300
|
-
|
301
|
-
|
302
|
-
types: nil, transaction: nil,
|
303
|
-
partition_token: nil, seqno: nil,
|
304
|
-
query_options: nil, request_options: nil,
|
305
|
-
call_options: nil, data_boost_enabled: nil
|
306
|
-
execute_query_options = {
|
307
|
-
transaction: transaction, params: params, types: types,
|
308
|
-
partition_token: partition_token, seqno: seqno,
|
309
|
-
query_options: query_options, request_options: request_options,
|
310
|
-
call_options: call_options
|
311
|
-
}
|
312
|
-
execute_query_options[:data_boost_enabled] = data_boost_enabled unless data_boost_enabled.nil?
|
313
|
-
enum = service.execute_streaming_sql session_path, sql,
|
314
|
-
**execute_query_options
|
315
|
-
from_enum(enum, service).tap do |results|
|
312
|
+
def self.from_execute_query_response response, service, session_path, sql, execute_query_options
|
313
|
+
from_enum(response, service).tap do |results|
|
316
314
|
results.instance_variable_set :@session_path, session_path
|
317
|
-
results.instance_variable_set :@sql,
|
318
|
-
results.instance_variable_set :@execute_query_options,
|
319
|
-
execute_query_options
|
315
|
+
results.instance_variable_set :@sql, sql
|
316
|
+
results.instance_variable_set :@execute_query_options, execute_query_options
|
320
317
|
end
|
321
318
|
end
|
322
319
|
|
323
320
|
# @private
|
324
|
-
def self.
|
325
|
-
|
326
|
-
partition_token: nil, request_options: nil,
|
327
|
-
call_options: nil, data_boost_enabled: nil
|
328
|
-
read_options = {
|
329
|
-
keys: keys, index: index, limit: limit,
|
330
|
-
transaction: transaction,
|
331
|
-
partition_token: partition_token,
|
332
|
-
request_options: request_options,
|
333
|
-
call_options: call_options,
|
334
|
-
data_boost_enabled: data_boost_enabled
|
335
|
-
}
|
336
|
-
read_options[:data_boost_enabled] = data_boost_enabled unless data_boost_enabled.nil?
|
337
|
-
enum = service.streaming_read_table \
|
338
|
-
session_path, table, columns, **read_options
|
339
|
-
from_enum(enum, service).tap do |results|
|
321
|
+
def self.from_read_response response, service, session_path, table, columns, read_options
|
322
|
+
from_enum(response, service).tap do |results|
|
340
323
|
results.instance_variable_set :@session_path, session_path
|
341
|
-
results.instance_variable_set :@table,
|
342
|
-
results.instance_variable_set :@columns,
|
324
|
+
results.instance_variable_set :@table, table
|
325
|
+
results.instance_variable_set :@columns, columns
|
343
326
|
results.instance_variable_set :@read_options, read_options
|
344
327
|
end
|
345
328
|
end
|
@@ -360,17 +360,7 @@ module Google
|
|
360
360
|
seqno: seqno,
|
361
361
|
request_options: request_options
|
362
362
|
}
|
363
|
-
|
364
|
-
|
365
|
-
if results.status.code.zero?
|
366
|
-
results.result_sets.map { |rs| rs.stats.row_count_exact }
|
367
|
-
else
|
368
|
-
begin
|
369
|
-
raise Google::Cloud::Error.from_error results.status
|
370
|
-
rescue Google::Cloud::Error
|
371
|
-
raise Google::Cloud::Spanner::BatchUpdateError.from_grpc results
|
372
|
-
end
|
373
|
-
end
|
363
|
+
service.execute_batch_dml request, opts
|
374
364
|
end
|
375
365
|
|
376
366
|
def streaming_read_table session_name, table_name, columns, keys: nil,
|
@@ -345,16 +345,18 @@ module Google
|
|
345
345
|
else
|
346
346
|
query_options = @query_options.merge query_options unless @query_options.nil?
|
347
347
|
end
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
348
|
+
|
349
|
+
execute_query_options = {
|
350
|
+
transaction: transaction, params: params, types: types,
|
351
|
+
partition_token: partition_token, seqno: seqno,
|
352
|
+
query_options: query_options, request_options: request_options,
|
353
|
+
call_options: call_options
|
354
|
+
}
|
355
|
+
execute_query_options[:data_boost_enabled] = data_boost_enabled unless data_boost_enabled.nil?
|
356
|
+
|
357
|
+
response = service.execute_streaming_sql path, sql, **execute_query_options
|
358
|
+
|
359
|
+
results = Results.from_execute_query_response response, service, path, sql, execute_query_options
|
358
360
|
@last_updated_at = Time.now
|
359
361
|
results
|
360
362
|
end
|
@@ -500,14 +502,23 @@ module Google
|
|
500
502
|
call_options: nil, data_boost_enabled: nil
|
501
503
|
ensure_service!
|
502
504
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
505
|
+
read_options = {
|
506
|
+
keys: keys, index: index, limit: limit,
|
507
|
+
transaction: transaction,
|
508
|
+
partition_token: partition_token,
|
509
|
+
request_options: request_options,
|
510
|
+
call_options: call_options,
|
511
|
+
data_boost_enabled: data_boost_enabled
|
512
|
+
}
|
513
|
+
read_options[:data_boost_enabled] = data_boost_enabled unless data_boost_enabled.nil?
|
514
|
+
|
515
|
+
response = service.streaming_read_table \
|
516
|
+
path, table, columns, **read_options
|
517
|
+
|
518
|
+
results = Results.from_read_response response, service, path, table, columns, read_options
|
519
|
+
|
510
520
|
@last_updated_at = Time.now
|
521
|
+
|
511
522
|
results
|
512
523
|
end
|
513
524
|
|
@@ -1173,6 +1184,14 @@ module Google
|
|
1173
1184
|
Transaction.from_grpc tx_grpc, self
|
1174
1185
|
end
|
1175
1186
|
|
1187
|
+
##
|
1188
|
+
# @private
|
1189
|
+
# Creates a new transaction object without the grpc object
|
1190
|
+
# within it. Use it for inline-begin of a transaction.
|
1191
|
+
def create_empty_transaction
|
1192
|
+
Transaction.from_grpc nil, self
|
1193
|
+
end
|
1194
|
+
|
1176
1195
|
##
|
1177
1196
|
# Reloads the session resource. Useful for determining if the session is
|
1178
1197
|
# still valid on the Spanner API.
|
@@ -17,6 +17,7 @@ require "google/cloud/spanner/errors"
|
|
17
17
|
require "google/cloud/spanner/convert"
|
18
18
|
require "google/cloud/spanner/results"
|
19
19
|
require "google/cloud/spanner/commit"
|
20
|
+
require "google/cloud/spanner/batch_update_results"
|
20
21
|
|
21
22
|
module Google
|
22
23
|
module Cloud
|
@@ -74,6 +75,9 @@ module Google
|
|
74
75
|
# end
|
75
76
|
#
|
76
77
|
class Transaction
|
78
|
+
# @private The `Google::Cloud::Spanner::V1::Transaction` object.
|
79
|
+
attr_reader :grpc
|
80
|
+
|
77
81
|
# @private The Session object.
|
78
82
|
attr_accessor :session
|
79
83
|
|
@@ -83,13 +87,31 @@ module Google
|
|
83
87
|
def initialize
|
84
88
|
@commit = Commit.new
|
85
89
|
@seqno = 0
|
90
|
+
|
91
|
+
# Mutex to enfore thread safety for transaction creation and query executions.
|
92
|
+
#
|
93
|
+
# This mutex protects two things:
|
94
|
+
# (1) the generation of sequence numbers
|
95
|
+
# (2) the creation of transactions.
|
96
|
+
#
|
97
|
+
# Specifically, @seqno is protected so it always reflects the last sequence number
|
98
|
+
# generated and provided to an operation in any thread. Any acquisition of a
|
99
|
+
# sequence number must be synchronized.
|
100
|
+
#
|
101
|
+
# Furthermore, @grpc is protected such that it either is nil if the
|
102
|
+
# transaction has not yet been created, or references the transaction
|
103
|
+
# resource if the transaction has been created. Any operation that could
|
104
|
+
# create a transaction must be synchronized, and any logic that depends on
|
105
|
+
# the state of transaction creation must also be synchronized.
|
106
|
+
@mutex = Mutex.new
|
86
107
|
end
|
87
108
|
|
88
109
|
##
|
89
110
|
# Identifier of the transaction results were run in.
|
90
111
|
# @return [String] The transaction id.
|
91
112
|
def transaction_id
|
92
|
-
return
|
113
|
+
return @grpc.id if existing_transaction?
|
114
|
+
safe_begin_transaction
|
93
115
|
@grpc.id
|
94
116
|
end
|
95
117
|
|
@@ -337,15 +359,18 @@ module Google
|
|
337
359
|
request_options: nil, call_options: nil
|
338
360
|
ensure_session!
|
339
361
|
|
340
|
-
@seqno += 1
|
341
|
-
|
342
362
|
params, types = Convert.to_input_params_and_types params, types
|
343
363
|
request_options = build_request_options request_options
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
364
|
+
|
365
|
+
safe_execute do |seqno|
|
366
|
+
results = session.execute_query sql, params: params, types: types,
|
367
|
+
transaction: tx_selector, seqno: seqno,
|
368
|
+
query_options: query_options,
|
369
|
+
request_options: request_options,
|
370
|
+
call_options: call_options
|
371
|
+
@grpc ||= results.transaction
|
372
|
+
results
|
373
|
+
end
|
349
374
|
end
|
350
375
|
alias execute execute_query
|
351
376
|
alias query execute_query
|
@@ -612,12 +637,24 @@ module Google
|
|
612
637
|
#
|
613
638
|
def batch_update request_options: nil, call_options: nil, &block
|
614
639
|
ensure_session!
|
615
|
-
@seqno += 1
|
616
640
|
|
617
641
|
request_options = build_request_options request_options
|
618
|
-
|
619
|
-
|
620
|
-
|
642
|
+
safe_execute do |seqno|
|
643
|
+
batch_update_results = nil
|
644
|
+
begin
|
645
|
+
response = session.batch_update tx_selector, seqno,
|
646
|
+
request_options: request_options,
|
647
|
+
call_options: call_options, &block
|
648
|
+
batch_update_results = BatchUpdateResults.new response
|
649
|
+
row_counts = batch_update_results.row_counts
|
650
|
+
@grpc ||= batch_update_results.transaction
|
651
|
+
return row_counts
|
652
|
+
rescue Google::Cloud::Spanner::BatchUpdateError
|
653
|
+
@grpc ||= batch_update_results.transaction
|
654
|
+
# Re-raise after extracting transaction
|
655
|
+
raise
|
656
|
+
end
|
657
|
+
end
|
621
658
|
end
|
622
659
|
|
623
660
|
##
|
@@ -686,10 +723,15 @@ module Google
|
|
686
723
|
columns = Array(columns).map(&:to_s)
|
687
724
|
keys = Convert.to_key_set keys
|
688
725
|
request_options = build_request_options request_options
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
726
|
+
|
727
|
+
safe_execute do
|
728
|
+
results = session.read table, columns, keys: keys, index: index, limit: limit,
|
729
|
+
transaction: tx_selector,
|
730
|
+
request_options: request_options,
|
731
|
+
call_options: call_options
|
732
|
+
@grpc ||= results.transaction
|
733
|
+
results
|
734
|
+
end
|
693
735
|
end
|
694
736
|
|
695
737
|
##
|
@@ -1111,12 +1153,69 @@ module Google
|
|
1111
1153
|
end
|
1112
1154
|
end
|
1113
1155
|
|
1156
|
+
##
|
1157
|
+
# @private Checks if a transaction is already created.
|
1158
|
+
def existing_transaction?
|
1159
|
+
!no_existing_transaction?
|
1160
|
+
end
|
1161
|
+
|
1162
|
+
##
|
1163
|
+
# @private Checks if transaction is not already created.
|
1164
|
+
def no_existing_transaction?
|
1165
|
+
@grpc.nil?
|
1166
|
+
end
|
1167
|
+
|
1114
1168
|
protected
|
1115
1169
|
|
1116
|
-
|
1170
|
+
##
|
1171
|
+
# @private Facilitates a thread-safe execution of an rpc
|
1172
|
+
# for inline-begin of a transaction. This method is optimised to
|
1173
|
+
# use mutexes only when necessary, while still acheiving thread-safety.
|
1174
|
+
#
|
1175
|
+
# Note: Do not use @seqno directly while using this method. Instead, use
|
1176
|
+
# the seqno variable passed to the block.
|
1177
|
+
def safe_execute
|
1178
|
+
loop do
|
1179
|
+
if existing_transaction?
|
1180
|
+
# Create a local copy of @seqno to avoid concurrent
|
1181
|
+
# operations overriding the incremented value.
|
1182
|
+
seqno = safe_next_seqno
|
1183
|
+
# If a transaction already exists, execute rpc without mutex
|
1184
|
+
return yield seqno
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
@mutex.synchronize do
|
1188
|
+
next if existing_transaction?
|
1189
|
+
@seqno += 1
|
1190
|
+
return yield @seqno
|
1191
|
+
end
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
##
|
1196
|
+
# Create a new transaction in a thread-safe manner.
|
1197
|
+
def safe_begin_transaction
|
1198
|
+
@mutex.synchronize do
|
1199
|
+
return if existing_transaction?
|
1200
|
+
ensure_session!
|
1201
|
+
@grpc = service.begin_transaction session.path
|
1202
|
+
end
|
1203
|
+
end
|
1204
|
+
|
1205
|
+
##
|
1206
|
+
# @private The TransactionSelector to be used for queries. This method must
|
1207
|
+
# be called from within a synchronized block, since the value returned
|
1208
|
+
# depends on the state of @grpc field.
|
1209
|
+
#
|
1210
|
+
# This method is expected to be called from within `safe_execute()` method's block,
|
1211
|
+
# since it provides synchronization and gurantees thread safety.
|
1117
1212
|
def tx_selector
|
1118
|
-
return
|
1119
|
-
V1::TransactionSelector.new
|
1213
|
+
return V1::TransactionSelector.new id: transaction_id if existing_transaction?
|
1214
|
+
V1::TransactionSelector.new(
|
1215
|
+
begin: V1::TransactionOptions.new(
|
1216
|
+
read_write: V1::TransactionOptions::ReadWrite.new
|
1217
|
+
)
|
1218
|
+
)
|
1120
1219
|
end
|
1121
1220
|
|
1122
1221
|
##
|
@@ -1133,6 +1232,15 @@ module Google
|
|
1133
1232
|
options
|
1134
1233
|
end
|
1135
1234
|
|
1235
|
+
##
|
1236
|
+
# @private Generates the next seqno in a thread-safe manner.
|
1237
|
+
def safe_next_seqno
|
1238
|
+
@mutex.synchronize do
|
1239
|
+
@seqno += 1
|
1240
|
+
return @seqno
|
1241
|
+
end
|
1242
|
+
end
|
1243
|
+
|
1136
1244
|
##
|
1137
1245
|
# @private Raise an error unless an active connection to the service is
|
1138
1246
|
# available.
|
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.
|
4
|
+
version: 2.18.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Moore
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-06
|
12
|
+
date: 2023-09-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: google-cloud-core
|
@@ -295,6 +295,7 @@ files:
|
|
295
295
|
- lib/google/cloud/spanner/batch_client.rb
|
296
296
|
- lib/google/cloud/spanner/batch_snapshot.rb
|
297
297
|
- lib/google/cloud/spanner/batch_update.rb
|
298
|
+
- lib/google/cloud/spanner/batch_update_results.rb
|
298
299
|
- lib/google/cloud/spanner/client.rb
|
299
300
|
- lib/google/cloud/spanner/column_value.rb
|
300
301
|
- lib/google/cloud/spanner/commit.rb
|
@@ -347,7 +348,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
347
348
|
- !ruby/object:Gem::Version
|
348
349
|
version: '0'
|
349
350
|
requirements: []
|
350
|
-
rubygems_version: 3.4.
|
351
|
+
rubygems_version: 3.4.19
|
351
352
|
signing_key:
|
352
353
|
specification_version: 4
|
353
354
|
summary: API Client library for Google Cloud Spanner API
|