google-cloud-spanner 2.17.0 → 2.18.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.
- 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
|