google-cloud-spanner 1.6.4 → 1.7.1
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 +9 -0
- data/lib/google/cloud/spanner/batch_snapshot.rb +43 -32
- data/lib/google/cloud/spanner/client.rb +221 -27
- data/lib/google/cloud/spanner/data.rb +1 -1
- data/lib/google/cloud/spanner/fields.rb +1 -1
- data/lib/google/cloud/spanner/partition.rb +16 -9
- data/lib/google/cloud/spanner/project.rb +2 -2
- data/lib/google/cloud/spanner/results.rb +73 -42
- data/lib/google/cloud/spanner/service.rb +18 -4
- data/lib/google/cloud/spanner/session.rb +39 -27
- data/lib/google/cloud/spanner/snapshot.rb +32 -24
- data/lib/google/cloud/spanner/transaction.rb +135 -25
- data/lib/google/cloud/spanner/version.rb +1 -1
- metadata +2 -2
@@ -34,7 +34,7 @@ module Google
|
|
34
34
|
#
|
35
35
|
# db = spanner.client "my-instance", "my-database"
|
36
36
|
#
|
37
|
-
# results = db.
|
37
|
+
# results = db.execute_query "SELECT * FROM users"
|
38
38
|
#
|
39
39
|
# results.rows.each do |row|
|
40
40
|
# puts "User #{row[:id]} is #{row[:name]}"
|
@@ -33,7 +33,7 @@ module Google
|
|
33
33
|
#
|
34
34
|
# db = spanner.client "my-instance", "my-database"
|
35
35
|
#
|
36
|
-
# results = db.
|
36
|
+
# results = db.execute_query "SELECT * FROM users"
|
37
37
|
#
|
38
38
|
# results.fields.pairs.each do |name, type|
|
39
39
|
# puts "Column #{name} is type #{type}"
|
@@ -52,11 +52,15 @@ module Google
|
|
52
52
|
def initialize; end
|
53
53
|
|
54
54
|
##
|
55
|
-
# Whether the partition was created for an
|
55
|
+
# Whether the partition was created for an execute_query/query
|
56
|
+
# operation.
|
56
57
|
# @return [Boolean]
|
57
|
-
def
|
58
|
+
def execute_query?
|
58
59
|
!@execute.nil?
|
59
60
|
end
|
61
|
+
alias execute? execute_query?
|
62
|
+
alias execute_sql? execute_query?
|
63
|
+
alias query? execute_query?
|
60
64
|
|
61
65
|
##
|
62
66
|
# Whether the partition was created for a read operation.
|
@@ -67,7 +71,8 @@ module Google
|
|
67
71
|
|
68
72
|
##
|
69
73
|
# @private
|
70
|
-
# Whether the partition does not have an
|
74
|
+
# Whether the partition does not have an execute_query or read
|
75
|
+
# operation.
|
71
76
|
# @return [Boolean]
|
72
77
|
def empty?
|
73
78
|
@execute.nil? && @read.nil?
|
@@ -84,7 +89,7 @@ module Google
|
|
84
89
|
def to_h
|
85
90
|
{}.tap do |h|
|
86
91
|
h[:execute] = Base64.strict_encode64(@execute.to_proto) if @execute
|
87
|
-
h[:read]
|
92
|
+
h[:read] = Base64.strict_encode64(@read.to_proto) if @read
|
88
93
|
end
|
89
94
|
end
|
90
95
|
|
@@ -163,12 +168,14 @@ module Google
|
|
163
168
|
def self.load data
|
164
169
|
data = JSON.parse data, symbolize_names: true unless data.is_a? Hash
|
165
170
|
|
166
|
-
# TODO: raise if hash[:
|
171
|
+
# TODO: raise if hash[:execute_query].nil? && hash[:read].nil?
|
167
172
|
new.tap do |p|
|
168
173
|
if data[:execute]
|
169
|
-
|
170
|
-
|
171
|
-
|
174
|
+
execute_sql_grpc = \
|
175
|
+
Google::Spanner::V1::ExecuteSqlRequest.decode(
|
176
|
+
Base64.decode64(data[:execute])
|
177
|
+
)
|
178
|
+
p.instance_variable_set :@execute, execute_sql_grpc
|
172
179
|
end
|
173
180
|
if data[:read]
|
174
181
|
read_grpc = Google::Spanner::V1::ReadRequest.decode \
|
@@ -189,7 +196,7 @@ module Google
|
|
189
196
|
##
|
190
197
|
# @private New Partition from a Google::Spanner::V1::ExecuteSqlRequest
|
191
198
|
# object.
|
192
|
-
def self.
|
199
|
+
def self.from_execute_sql_grpc grpc
|
193
200
|
new.tap do |p|
|
194
201
|
p.instance_variable_set :@execute, grpc
|
195
202
|
end
|
@@ -58,7 +58,7 @@ module Google
|
|
58
58
|
# db = spanner.client "my-instance", "my-database"
|
59
59
|
#
|
60
60
|
# db.transaction do |tx|
|
61
|
-
# results = tx.
|
61
|
+
# results = tx.execute_query "SELECT * FROM users"
|
62
62
|
#
|
63
63
|
# results.rows.each do |row|
|
64
64
|
# puts "User #{row[:id]} is #{row[:name]}"
|
@@ -463,7 +463,7 @@ module Google
|
|
463
463
|
# db = spanner.client "my-instance", "my-database"
|
464
464
|
#
|
465
465
|
# db.transaction do |tx|
|
466
|
-
# results = tx.
|
466
|
+
# results = tx.execute_query "SELECT * FROM users"
|
467
467
|
#
|
468
468
|
# results.rows.each do |row|
|
469
469
|
# puts "User #{row[:id]} is #{row[:name]}"
|
@@ -24,7 +24,7 @@ module Google
|
|
24
24
|
#
|
25
25
|
# Represents the result set from an operation returning data.
|
26
26
|
#
|
27
|
-
# See {Google::Cloud::Spanner::Client#
|
27
|
+
# See {Google::Cloud::Spanner::Client#execute_query} and
|
28
28
|
# {Google::Cloud::Spanner::Client#read}.
|
29
29
|
#
|
30
30
|
# @example
|
@@ -34,7 +34,7 @@ module Google
|
|
34
34
|
#
|
35
35
|
# db = spanner.client "my-instance", "my-database"
|
36
36
|
#
|
37
|
-
# results = db.
|
37
|
+
# results = db.execute_query "SELECT * FROM users"
|
38
38
|
#
|
39
39
|
# results.fields.pairs.each do |name, type|
|
40
40
|
# puts "Column #{name} is type #{type}"
|
@@ -63,7 +63,7 @@ module Google
|
|
63
63
|
#
|
64
64
|
# db = spanner.client "my-instance", "my-database"
|
65
65
|
#
|
66
|
-
# results = db.
|
66
|
+
# results = db.execute_query "SELECT * FROM users"
|
67
67
|
#
|
68
68
|
# results.fields.pairs.each do |name, type|
|
69
69
|
# puts "Column #{name} is type #{type}"
|
@@ -88,7 +88,7 @@ module Google
|
|
88
88
|
#
|
89
89
|
# db = spanner.client "my-instance", "my-database"
|
90
90
|
#
|
91
|
-
# results = db.
|
91
|
+
# results = db.execute_query "SELECT * FROM users"
|
92
92
|
#
|
93
93
|
# results.rows.each do |row|
|
94
94
|
# puts "User #{row[:id]} is #{row[:name]}"
|
@@ -102,11 +102,6 @@ module Google
|
|
102
102
|
end
|
103
103
|
|
104
104
|
fields = @metadata.row_type.fields
|
105
|
-
if fields.count.zero?
|
106
|
-
@closed = true
|
107
|
-
return []
|
108
|
-
end
|
109
|
-
|
110
105
|
values = []
|
111
106
|
buffered_responses = []
|
112
107
|
buffer_upper_bound = 10
|
@@ -130,16 +125,18 @@ module Google
|
|
130
125
|
# This can set the resume_token to nil
|
131
126
|
resume_token = grpc.resume_token
|
132
127
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
128
|
+
if fields.count > 0
|
129
|
+
buffered_responses.each do |resp|
|
130
|
+
if chunked_value
|
131
|
+
resp.values.unshift merge(chunked_value, resp.values.shift)
|
132
|
+
chunked_value = nil
|
133
|
+
end
|
134
|
+
to_iterate = values + Array(resp.values)
|
135
|
+
chunked_value = to_iterate.pop if resp.chunked_value
|
136
|
+
values = to_iterate.pop(to_iterate.count % fields.count)
|
137
|
+
to_iterate.each_slice(fields.count) do |slice|
|
138
|
+
yield Data.from_grpc(slice, fields)
|
139
|
+
end
|
143
140
|
end
|
144
141
|
end
|
145
142
|
|
@@ -154,10 +151,10 @@ module Google
|
|
154
151
|
end
|
155
152
|
|
156
153
|
# Resume the stream from the last known resume_token
|
157
|
-
if @
|
158
|
-
@enum = @service.
|
154
|
+
if @execute_query_options
|
155
|
+
@enum = @service.execute_streaming_sql \
|
159
156
|
@session_path, @sql,
|
160
|
-
@
|
157
|
+
@execute_query_options.merge(resume_token: resume_token)
|
161
158
|
else
|
162
159
|
@enum = @service.streaming_read_table \
|
163
160
|
@session_path, @table, @columns,
|
@@ -174,21 +171,23 @@ module Google
|
|
174
171
|
end
|
175
172
|
|
176
173
|
# clear out any remaining values left over
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
174
|
+
if fields.count > 0
|
175
|
+
buffered_responses.each do |resp|
|
176
|
+
if chunked_value
|
177
|
+
resp.values.unshift merge(chunked_value, resp.values.shift)
|
178
|
+
chunked_value = nil
|
179
|
+
end
|
180
|
+
to_iterate = values + Array(resp.values)
|
181
|
+
chunked_value = to_iterate.pop if resp.chunked_value
|
182
|
+
values = to_iterate.pop(to_iterate.count % fields.count)
|
183
|
+
to_iterate.each_slice(fields.count) do |slice|
|
184
|
+
yield Data.from_grpc(slice, fields)
|
185
|
+
end
|
181
186
|
end
|
182
|
-
|
183
|
-
chunked_value = to_iterate.pop if resp.chunked_value
|
184
|
-
values = to_iterate.pop(to_iterate.count % fields.count)
|
185
|
-
to_iterate.each_slice(fields.count) do |slice|
|
187
|
+
values.each_slice(fields.count) do |slice|
|
186
188
|
yield Data.from_grpc(slice, fields)
|
187
189
|
end
|
188
190
|
end
|
189
|
-
values.each_slice(fields.count) do |slice|
|
190
|
-
yield Data.from_grpc(slice, fields)
|
191
|
-
end
|
192
191
|
|
193
192
|
# If we get this far then we can release the session
|
194
193
|
@closed = true
|
@@ -197,6 +196,33 @@ module Google
|
|
197
196
|
|
198
197
|
# rubocop:enable all
|
199
198
|
|
199
|
+
##
|
200
|
+
# @private
|
201
|
+
# Get row count from stats. This will be the exact row count for DML
|
202
|
+
# statements, and the lower bound row count for PDML statements.
|
203
|
+
def row_count
|
204
|
+
return @stats.row_count_lower_bound if row_count_lower_bound?
|
205
|
+
return @stats.row_count_exact if row_count_exact?
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
|
209
|
+
##
|
210
|
+
# @private
|
211
|
+
# Whether the row count is the lower bound row count for PDML
|
212
|
+
# statements.
|
213
|
+
def row_count_lower_bound?
|
214
|
+
return nil if @stats.nil?
|
215
|
+
@stats.row_count == :row_count_lower_bound
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# @private
|
220
|
+
# Whether the row count is the exact row count for DML statements.
|
221
|
+
def row_count_exact?
|
222
|
+
return nil if @stats.nil?
|
223
|
+
@stats.row_count == :row_count_exact
|
224
|
+
end
|
225
|
+
|
200
226
|
# @private
|
201
227
|
def self.from_enum enum, service
|
202
228
|
grpc = enum.peek
|
@@ -211,16 +237,21 @@ module Google
|
|
211
237
|
end
|
212
238
|
|
213
239
|
# @private
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
240
|
+
|
241
|
+
def self.execute_query service, session_path, sql, params: nil,
|
242
|
+
types: nil, transaction: nil,
|
243
|
+
partition_token: nil, seqno: nil
|
244
|
+
execute_query_options = {
|
245
|
+
transaction: transaction, params: params, types: types,
|
246
|
+
partition_token: partition_token, seqno: seqno
|
247
|
+
}
|
248
|
+
enum = service.execute_streaming_sql session_path, sql,
|
249
|
+
execute_query_options
|
220
250
|
from_enum(enum, service).tap do |results|
|
221
|
-
results.instance_variable_set :@session_path,
|
222
|
-
results.instance_variable_set :@sql,
|
223
|
-
results.instance_variable_set :@
|
251
|
+
results.instance_variable_set :@session_path, session_path
|
252
|
+
results.instance_variable_set :@sql, sql
|
253
|
+
results.instance_variable_set :@execute_query_options,
|
254
|
+
execute_query_options
|
224
255
|
end
|
225
256
|
end
|
226
257
|
|
@@ -257,16 +257,19 @@ module Google
|
|
257
257
|
end
|
258
258
|
end
|
259
259
|
|
260
|
-
def
|
260
|
+
def execute_streaming_sql session_name, sql, transaction: nil,
|
261
261
|
params: nil, types: nil, resume_token: nil,
|
262
|
-
partition_token: nil
|
262
|
+
partition_token: nil, seqno: nil
|
263
263
|
opts = default_options_from_session session_name
|
264
264
|
execute do
|
265
265
|
service.execute_streaming_sql \
|
266
|
-
session_name, sql, transaction: transaction,
|
266
|
+
session_name, sql, transaction: transaction,
|
267
|
+
params: params,
|
267
268
|
param_types: types,
|
268
269
|
resume_token: resume_token,
|
269
|
-
partition_token: partition_token,
|
270
|
+
partition_token: partition_token,
|
271
|
+
seqno: seqno,
|
272
|
+
options: opts
|
270
273
|
end
|
271
274
|
end
|
272
275
|
|
@@ -367,6 +370,17 @@ module Google
|
|
367
370
|
end
|
368
371
|
end
|
369
372
|
|
373
|
+
def create_pdml session_name
|
374
|
+
tx_opts = Google::Spanner::V1::TransactionOptions.new(
|
375
|
+
partitioned_dml: \
|
376
|
+
Google::Spanner::V1::TransactionOptions::PartitionedDml.new
|
377
|
+
)
|
378
|
+
opts = default_options_from_session session_name
|
379
|
+
execute do
|
380
|
+
service.begin_transaction session_name, tx_opts, options: opts
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
370
384
|
def inspect
|
371
385
|
"#{self.class}(#{@project})"
|
372
386
|
end
|
@@ -151,6 +151,8 @@ module Google
|
|
151
151
|
# @param [Google::Spanner::V1::TransactionSelector] transaction The
|
152
152
|
# transaction selector value to send. Only used for single-use
|
153
153
|
# transactions.
|
154
|
+
# @param [Integer] seqno A per-transaction sequence number used to
|
155
|
+
# identify this request.
|
154
156
|
#
|
155
157
|
# @return [Google::Cloud::Spanner::Results] The results of the query
|
156
158
|
# execution.
|
@@ -162,7 +164,7 @@ module Google
|
|
162
164
|
#
|
163
165
|
# db = spanner.client "my-instance", "my-database"
|
164
166
|
#
|
165
|
-
# results = db.
|
167
|
+
# results = db.execute_query "SELECT * FROM users"
|
166
168
|
#
|
167
169
|
# results.rows.each do |row|
|
168
170
|
# puts "User #{row[:id]} is #{row[:name]}"
|
@@ -175,8 +177,10 @@ module Google
|
|
175
177
|
#
|
176
178
|
# db = spanner.client "my-instance", "my-database"
|
177
179
|
#
|
178
|
-
# results = db.
|
179
|
-
#
|
180
|
+
# results = db.execute_query(
|
181
|
+
# "SELECT * FROM users WHERE active = @active",
|
182
|
+
# params: { active: true }
|
183
|
+
# )
|
180
184
|
#
|
181
185
|
# results.rows.each do |row|
|
182
186
|
# puts "User #{row[:id]} is #{row[:name]}"
|
@@ -191,11 +195,13 @@ module Google
|
|
191
195
|
#
|
192
196
|
# user_hash = { id: 1, name: "Charlie", active: false }
|
193
197
|
#
|
194
|
-
# results = db.
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
198
|
+
# results = db.execute_query(
|
199
|
+
# "SELECT * FROM users WHERE " \
|
200
|
+
# "ID = @user_struct.id " \
|
201
|
+
# "AND name = @user_struct.name " \
|
202
|
+
# "AND active = @user_struct.active",
|
203
|
+
# params: { user_struct: user_hash }
|
204
|
+
# )
|
199
205
|
#
|
200
206
|
# results.rows.each do |row|
|
201
207
|
# puts "User #{row[:id]} is #{row[:name]}"
|
@@ -211,12 +217,14 @@ module Google
|
|
211
217
|
# user_type = db.fields id: :INT64, name: :STRING, active: :BOOL
|
212
218
|
# user_hash = { id: 1, name: nil, active: false }
|
213
219
|
#
|
214
|
-
# results = db.
|
215
|
-
#
|
216
|
-
#
|
217
|
-
#
|
218
|
-
#
|
219
|
-
#
|
220
|
+
# results = db.execute_query(
|
221
|
+
# "SELECT * FROM users WHERE " \
|
222
|
+
# "ID = @user_struct.id " \
|
223
|
+
# "AND name = @user_struct.name " \
|
224
|
+
# "AND active = @user_struct.active",
|
225
|
+
# params: { user_struct: user_hash },
|
226
|
+
# types: { user_struct: user_type }
|
227
|
+
# )
|
220
228
|
#
|
221
229
|
# results.rows.each do |row|
|
222
230
|
# puts "User #{row[:id]} is #{row[:name]}"
|
@@ -232,31 +240,35 @@ module Google
|
|
232
240
|
# user_type = db.fields id: :INT64, name: :STRING, active: :BOOL
|
233
241
|
# user_data = user_type.struct id: 1, name: nil, active: false
|
234
242
|
#
|
235
|
-
# results = db.
|
236
|
-
#
|
237
|
-
#
|
238
|
-
#
|
239
|
-
#
|
243
|
+
# results = db.execute_query(
|
244
|
+
# "SELECT * FROM users WHERE " \
|
245
|
+
# "ID = @user_struct.id " \
|
246
|
+
# "AND name = @user_struct.name " \
|
247
|
+
# "AND active = @user_struct.active",
|
248
|
+
# params: { user_struct: user_struct }
|
249
|
+
# )
|
240
250
|
#
|
241
251
|
# results.rows.each do |row|
|
242
252
|
# puts "User #{row[:id]} is #{row[:name]}"
|
243
253
|
# end
|
244
254
|
#
|
245
|
-
def
|
246
|
-
|
255
|
+
def execute_query sql, params: nil, types: nil, transaction: nil,
|
256
|
+
partition_token: nil, seqno: nil
|
247
257
|
ensure_service!
|
248
258
|
|
249
|
-
results = Results.
|
250
|
-
|
251
|
-
|
252
|
-
|
259
|
+
results = Results.execute_query service, path, sql,
|
260
|
+
params: params,
|
261
|
+
types: types,
|
262
|
+
transaction: transaction,
|
263
|
+
partition_token: partition_token,
|
264
|
+
seqno: seqno
|
253
265
|
@last_updated_at = Time.now
|
254
266
|
results
|
255
267
|
end
|
256
268
|
|
257
269
|
##
|
258
270
|
# Read rows from a database table, as a simple alternative to
|
259
|
-
# {#
|
271
|
+
# {#execute_query}.
|
260
272
|
#
|
261
273
|
# @param [String] table The name of the table in the database to be
|
262
274
|
# read.
|
@@ -612,7 +624,7 @@ module Google
|
|
612
624
|
# Keeps the session alive by executing `"SELECT 1"`.
|
613
625
|
def keepalive!
|
614
626
|
ensure_service!
|
615
|
-
|
627
|
+
execute_query "SELECT 1"
|
616
628
|
return true
|
617
629
|
rescue Google::Cloud::NotFoundError
|
618
630
|
labels = @grpc.labels.to_h unless @grpc.labels.to_h.empty?
|