google-cloud-spanner 0.21.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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/lib/google-cloud-spanner.rb +106 -0
  3. data/lib/google/cloud/spanner.rb +382 -0
  4. data/lib/google/cloud/spanner/admin/database/v1.rb +17 -0
  5. data/lib/google/cloud/spanner/admin/database/v1/database_admin_client.rb +703 -0
  6. data/lib/google/cloud/spanner/admin/database/v1/database_admin_client_config.json +73 -0
  7. data/lib/google/cloud/spanner/admin/database/v1/doc/google/iam/v1/policy.rb +139 -0
  8. data/lib/google/cloud/spanner/admin/database/v1/doc/google/protobuf/any.rb +114 -0
  9. data/lib/google/cloud/spanner/admin/database/v1/doc/google/rpc/status.rb +83 -0
  10. data/lib/google/cloud/spanner/admin/database/v1/doc/google/spanner/admin/database/v1/spanner_database_admin.rb +188 -0
  11. data/lib/google/cloud/spanner/admin/instance/v1.rb +17 -0
  12. data/lib/google/cloud/spanner/admin/instance/v1/doc/google/iam/v1/policy.rb +139 -0
  13. data/lib/google/cloud/spanner/admin/instance/v1/doc/google/protobuf/any.rb +114 -0
  14. data/lib/google/cloud/spanner/admin/instance/v1/doc/google/protobuf/field_mask.rb +223 -0
  15. data/lib/google/cloud/spanner/admin/instance/v1/doc/google/rpc/status.rb +83 -0
  16. data/lib/google/cloud/spanner/admin/instance/v1/doc/google/spanner/admin/instance/v1/spanner_instance_admin.rb +268 -0
  17. data/lib/google/cloud/spanner/admin/instance/v1/instance_admin_client.rb +868 -0
  18. data/lib/google/cloud/spanner/admin/instance/v1/instance_admin_client_config.json +78 -0
  19. data/lib/google/cloud/spanner/client.rb +1034 -0
  20. data/lib/google/cloud/spanner/commit.rb +351 -0
  21. data/lib/google/cloud/spanner/convert.rb +311 -0
  22. data/lib/google/cloud/spanner/credentials.rb +32 -0
  23. data/lib/google/cloud/spanner/data.rb +199 -0
  24. data/lib/google/cloud/spanner/database.rb +377 -0
  25. data/lib/google/cloud/spanner/database/job.rb +179 -0
  26. data/lib/google/cloud/spanner/database/list.rb +171 -0
  27. data/lib/google/cloud/spanner/errors.rb +73 -0
  28. data/lib/google/cloud/spanner/fields.rb +252 -0
  29. data/lib/google/cloud/spanner/instance.rb +472 -0
  30. data/lib/google/cloud/spanner/instance/config.rb +99 -0
  31. data/lib/google/cloud/spanner/instance/config/list.rb +171 -0
  32. data/lib/google/cloud/spanner/instance/job.rb +197 -0
  33. data/lib/google/cloud/spanner/instance/list.rb +167 -0
  34. data/lib/google/cloud/spanner/policy.rb +201 -0
  35. data/lib/google/cloud/spanner/pool.rb +279 -0
  36. data/lib/google/cloud/spanner/project.rb +480 -0
  37. data/lib/google/cloud/spanner/range.rb +99 -0
  38. data/lib/google/cloud/spanner/results.rb +280 -0
  39. data/lib/google/cloud/spanner/service.rb +458 -0
  40. data/lib/google/cloud/spanner/session.rb +565 -0
  41. data/lib/google/cloud/spanner/snapshot.rb +260 -0
  42. data/lib/google/cloud/spanner/transaction.rb +533 -0
  43. data/lib/google/cloud/spanner/v1.rb +17 -0
  44. data/lib/google/cloud/spanner/v1/doc/google/protobuf/duration.rb +77 -0
  45. data/lib/google/cloud/spanner/v1/doc/google/protobuf/struct.rb +73 -0
  46. data/lib/google/cloud/spanner/v1/doc/google/protobuf/timestamp.rb +81 -0
  47. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/keys.rb +148 -0
  48. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/mutation.rb +80 -0
  49. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/query_plan.rb +120 -0
  50. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/result_set.rb +175 -0
  51. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/spanner.rb +206 -0
  52. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/transaction.rb +351 -0
  53. data/lib/google/cloud/spanner/v1/spanner_client.rb +850 -0
  54. data/lib/google/cloud/spanner/v1/spanner_client_config.json +78 -0
  55. data/lib/google/cloud/spanner/version.rb +22 -0
  56. data/lib/google/spanner/admin/database/v1/spanner_database_admin_pb.rb +85 -0
  57. data/lib/google/spanner/admin/database/v1/spanner_database_admin_services_pb.rb +95 -0
  58. data/lib/google/spanner/admin/instance/v1/spanner_instance_admin_pb.rb +106 -0
  59. data/lib/google/spanner/admin/instance/v1/spanner_instance_admin_services_pb.rb +180 -0
  60. data/lib/google/spanner/v1/keys_pb.rb +33 -0
  61. data/lib/google/spanner/v1/mutation_pb.rb +38 -0
  62. data/lib/google/spanner/v1/query_plan_pb.rb +47 -0
  63. data/lib/google/spanner/v1/result_set_pb.rb +43 -0
  64. data/lib/google/spanner/v1/spanner_pb.rb +90 -0
  65. data/lib/google/spanner/v1/spanner_services_pb.rb +131 -0
  66. data/lib/google/spanner/v1/transaction_pb.rb +51 -0
  67. data/lib/google/spanner/v1/type_pb.rb +43 -0
  68. metadata +309 -0
@@ -0,0 +1,99 @@
1
+ # Copyright 2017 Google Inc. All rights reserved.
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
+ # http://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
+ # # Range
21
+ #
22
+ # Represents a range of rows in a table or index. A range has a start key
23
+ # and an end key. These keys can be open or closed, indicating if the
24
+ # range includes rows with that key.
25
+ #
26
+ # @example
27
+ # require "google/cloud/spanner"
28
+ #
29
+ # spanner = Google::Cloud::Spanner.new
30
+ #
31
+ # db = spanner.client "my-instance", "my-database"
32
+ #
33
+ # key_range = db.range 1, 100
34
+ # results = db.read "users", [:id, :name], keys: key_range
35
+ #
36
+ # results.rows.each do |row|
37
+ # puts "User #{row[:id]} is #{row[:name]}"
38
+ # end
39
+ #
40
+ class Range
41
+ ##
42
+ # Returns the object that defines the beginning of the range.
43
+ attr_reader :begin
44
+
45
+ ##
46
+ # Returns the object that defines the end of the range.
47
+ attr_reader :end
48
+
49
+ ##
50
+ # Creates a Spanner Range. This can be used in place of a Ruby Range
51
+ # when needing to exclude the beginning value.
52
+ #
53
+ # @param [Object] beginning The object that defines the beginning of the
54
+ # range.
55
+ # @param [Object] ending The object that defines the end of the range.
56
+ # @param [Boolean] exclude_begin Determines if the range excludes its
57
+ # beginning value. Default is `false`.
58
+ # @param [Boolean] exclude_end Determines if the range excludes its
59
+ # ending value. Default is `false`.
60
+ #
61
+ # @example
62
+ # require "google/cloud/spanner"
63
+ #
64
+ # spanner = Google::Cloud::Spanner.new
65
+ #
66
+ # db = spanner.client "my-instance", "my-database"
67
+ #
68
+ # key_range = Google::Cloud::Spanner::Range.new 1, 100
69
+ # results = db.read "users", [:id, :name], keys: key_range
70
+ #
71
+ # results.rows.each do |row|
72
+ # puts "User #{row[:id]} is #{row[:name]}"
73
+ # end
74
+ #
75
+ def initialize beginning, ending, exclude_begin: false,
76
+ exclude_end: false
77
+ @begin = beginning
78
+ @end = ending
79
+ @exclude_begin = exclude_begin
80
+ @exclude_end = exclude_end
81
+ end
82
+
83
+ ##
84
+ # Returns `true` if the range excludes its beginning value.
85
+ # @return [Boolean]
86
+ def exclude_begin?
87
+ @exclude_begin
88
+ end
89
+
90
+ ##
91
+ # Returns `true` if the range excludes its end value.
92
+ # @return [Boolean]
93
+ def exclude_end?
94
+ @exclude_end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,280 @@
1
+ # Copyright 2016 Google Inc. All rights reserved.
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
+ # http://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
+ require "google/cloud/spanner/errors"
17
+ require "google/cloud/spanner/data"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Spanner
22
+ ##
23
+ # # Results
24
+ #
25
+ # Represents the result set from an operation returning data.
26
+ #
27
+ # See {Google::Cloud::Spanner::Client#execute} and
28
+ # {Google::Cloud::Spanner::Client#read}.
29
+ #
30
+ # @example
31
+ # require "google/cloud/spanner"
32
+ #
33
+ # spanner = Google::Cloud::Spanner.new
34
+ #
35
+ # db = spanner.client "my-instance", "my-database"
36
+ #
37
+ # results = db.execute "SELECT * FROM users"
38
+ #
39
+ # results.types.each do |name, type|
40
+ # puts "Column #{name} is type {type}"
41
+ # end
42
+ #
43
+ class Results
44
+ ##
45
+ # The read timestamp chosen for single-use snapshots (read-only
46
+ # transactions).
47
+ # @return [Time] The chosen timestamp.
48
+ def timestamp
49
+ return nil if @metadata.nil? || @metadata.transaction.nil?
50
+ Convert.timestamp_to_time @metadata.transaction.read_timestamp
51
+ end
52
+
53
+ ##
54
+ # Returns the field names and types for the rows in the returned data.
55
+ #
56
+ # @return [Fields] The fields of the returned data.
57
+ #
58
+ # @example
59
+ # require "google/cloud/spanner"
60
+ #
61
+ # spanner = Google::Cloud::Spanner.new
62
+ #
63
+ # db = spanner.client "my-instance", "my-database"
64
+ #
65
+ # results = db.execute "SELECT * FROM users"
66
+ #
67
+ # results.fields.pairs.each do |name, type|
68
+ # puts "Column #{name} is type {type}"
69
+ # end
70
+ #
71
+ def fields
72
+ @fields ||= Fields.from_grpc @metadata.row_type.fields
73
+ end
74
+
75
+ # rubocop:disable all
76
+
77
+ ##
78
+ # The values returned from the request.
79
+ #
80
+ # @yield [row] An enumerator for the rows.
81
+ # @yieldparam [Data] row object that contains the data values.
82
+ #
83
+ # @example
84
+ # require "google/cloud/spanner"
85
+ #
86
+ # spanner = Google::Cloud::Spanner.new
87
+ #
88
+ # db = spanner.client "my-instance", "my-database"
89
+ #
90
+ # results = db.execute "SELECT * FROM users"
91
+ #
92
+ # results.rows.each do |row|
93
+ # puts "User #{row[:id]} is #{row[:name]}"
94
+ # end
95
+ #
96
+ def rows
97
+ return nil if @closed
98
+
99
+ unless block_given?
100
+ return enum_for(:rows)
101
+ end
102
+
103
+ fields = @metadata.row_type.fields
104
+ values = []
105
+ buffered_responses = []
106
+ buffer_upper_bound = 10
107
+ chunked_value = nil
108
+ resume_token = nil
109
+
110
+ # Cannot call Enumerator#each because it won't return the first
111
+ # value that was already identified when calling Enumerator#peek.
112
+ # Iterate only using Enumerator#next and break on StopIteration.
113
+ loop do
114
+ begin
115
+ grpc = @enum.next
116
+ # metadata should be set before the first iteration...
117
+ @metadata ||= grpc.metadata
118
+ @stats ||= grpc.stats
119
+
120
+ buffered_responses << grpc
121
+
122
+ if (grpc.resume_token && grpc.resume_token != "") ||
123
+ buffered_responses.size >= buffer_upper_bound
124
+ # This can set the resume_token to nil
125
+ resume_token = grpc.resume_token
126
+
127
+ buffered_responses.each do |resp|
128
+ if chunked_value
129
+ resp.values.unshift merge(chunked_value, resp.values.shift)
130
+ chunked_value = nil
131
+ end
132
+ to_iterate = values + Array(resp.values)
133
+ chunked_value = to_iterate.pop if resp.chunked_value
134
+ values = to_iterate.pop(to_iterate.count % fields.count)
135
+ to_iterate.each_slice(fields.count) do |slice|
136
+ yield Data.from_grpc(slice, fields)
137
+ end
138
+ end
139
+
140
+ # Flush the buffered responses now that they are all handled
141
+ buffered_responses = []
142
+ end
143
+ rescue GRPC::Unavailable => err
144
+ if resume_token.nil? || resume_token.empty?
145
+ # Re-raise if the resume_token is not a valid value.
146
+ # This can happen if the buffer was flushed.
147
+ raise Google::Cloud::Error.from_error(err)
148
+ end
149
+
150
+ # Resume the stream from the last known resume_token
151
+ if @execute_options
152
+ @enum = @service.streaming_execute_sql \
153
+ @session_path, @sql,
154
+ @execute_options.merge(resume_token: resume_token)
155
+ else
156
+ @enum = @service.streaming_read_table \
157
+ @session_path, @table, @columns,
158
+ @read_options.merge(resume_token: resume_token)
159
+ end
160
+
161
+ # Flush the buffered responses to reset to the resume_token
162
+ buffered_responses = []
163
+ rescue GRPC::BadStatus => err
164
+ raise Google::Cloud::Error.from_error(err)
165
+ rescue StopIteration
166
+ break
167
+ end
168
+ end
169
+
170
+ # clear out any remaining values left over
171
+ buffered_responses.each do |resp|
172
+ if chunked_value
173
+ resp.values.unshift merge(chunked_value, resp.values.shift)
174
+ chunked_value = nil
175
+ end
176
+ to_iterate = values + Array(resp.values)
177
+ chunked_value = to_iterate.pop if resp.chunked_value
178
+ values = to_iterate.pop(to_iterate.count % fields.count)
179
+ to_iterate.each_slice(fields.count) do |slice|
180
+ yield Data.from_grpc(slice, fields)
181
+ end
182
+ end
183
+ values.each_slice(fields.count) do |slice|
184
+ yield Data.from_grpc(slice, fields)
185
+ end
186
+
187
+ # If we get this far then we can release the session
188
+ @closed = true
189
+ nil
190
+ end
191
+
192
+ # rubocop:enable all
193
+
194
+ # @private
195
+ def self.from_enum enum, service
196
+ grpc = enum.peek
197
+ new.tap do |results|
198
+ results.instance_variable_set :@metadata, grpc.metadata
199
+ results.instance_variable_set :@stats, grpc.stats
200
+ results.instance_variable_set :@enum, enum
201
+ results.instance_variable_set :@service, service
202
+ end
203
+ end
204
+
205
+ # @private
206
+ def self.execute service, session_path, sql, params: nil, types: nil,
207
+ transaction: nil
208
+ execute_options = { transaction: transaction, params: params,
209
+ types: types }
210
+ enum = service.streaming_execute_sql session_path, sql,
211
+ execute_options
212
+ from_enum(enum, service).tap do |results|
213
+ results.instance_variable_set :@session_path, session_path
214
+ results.instance_variable_set :@sql, sql
215
+ results.instance_variable_set :@execute_options, execute_options
216
+ end
217
+ end
218
+
219
+ # @private
220
+ def self.read service, session_path, table, columns, keys: nil,
221
+ index: nil, limit: nil, transaction: nil
222
+ read_options = { keys: keys, index: index, limit: limit,
223
+ transaction: transaction }
224
+ enum = service.streaming_read_table \
225
+ session_path, table, columns, read_options
226
+ from_enum(enum, service).tap do |results|
227
+ results.instance_variable_set :@session_path, session_path
228
+ results.instance_variable_set :@table, table
229
+ results.instance_variable_set :@columns, columns
230
+ results.instance_variable_set :@read_options, read_options
231
+ end
232
+ end
233
+
234
+ # @private
235
+ def to_s
236
+ "(#{fields.inspect} streaming)"
237
+ end
238
+
239
+ # @private
240
+ def inspect
241
+ "#<#{self.class.name} #{self}>"
242
+ end
243
+
244
+ protected
245
+
246
+ # rubocop:disable all
247
+
248
+ # @private
249
+ def merge left, right
250
+ if left.kind != right.kind
251
+ raise "Can't merge #{left.kind} and #{right.kind} values"
252
+ end
253
+ if left.kind == :string_value
254
+ left.string_value = left.string_value + right.string_value
255
+ return left
256
+ elsif left.kind == :list_value
257
+ left_val = left.list_value.values.pop
258
+ right_val = right.list_value.values.shift
259
+ if (left_val.kind == right_val.kind) &&
260
+ (left_val.kind == :list_value || left_val.kind == :string_value)
261
+ left.list_value.values << merge(left_val, right_val)
262
+ else
263
+ left.list_value.values << left_val
264
+ left.list_value.values << right_val
265
+ end
266
+ right.list_value.values.each { |val| left.list_value.values << val }
267
+ return left
268
+ elsif left.kind == :struct_value
269
+ # Don't worry about this yet since Spanner isn't return STRUCT
270
+ fail "STRUCT not implemented yet"
271
+ else
272
+ raise "Can't merge #{left.kind} values"
273
+ end
274
+ end
275
+
276
+ # rubocop:enable all
277
+ end
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,458 @@
1
+ # Copyright 2016 Google Inc. All rights reserved.
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
+ # http://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
+ require "google/cloud/spanner/errors"
17
+ require "google/cloud/spanner/credentials"
18
+ require "google/cloud/spanner/version"
19
+ require "google/cloud/spanner/v1"
20
+ require "google/cloud/spanner/admin/instance/v1"
21
+ require "google/cloud/spanner/admin/database/v1"
22
+ require "google/cloud/spanner/convert"
23
+
24
+ module Google
25
+ module Cloud
26
+ module Spanner
27
+ ##
28
+ # @private Represents the gRPC Spanner service, including all the API
29
+ # methods.
30
+ class Service
31
+ attr_accessor :project, :credentials, :host, :timeout, :client_config
32
+
33
+ ##
34
+ # Creates a new Service instance.
35
+ def initialize project, credentials, host: nil, timeout: nil,
36
+ client_config: nil
37
+ @project = project
38
+ @credentials = credentials
39
+ @host = host || V1::SpannerClient::SERVICE_ADDRESS
40
+ @timeout = timeout
41
+ @client_config = client_config || {}
42
+ end
43
+
44
+ def channel
45
+ require "grpc"
46
+ GRPC::Core::Channel.new host, nil, chan_creds
47
+ end
48
+
49
+ def chan_creds
50
+ return credentials if insecure?
51
+ require "grpc"
52
+ GRPC::Core::ChannelCredentials.new.compose \
53
+ GRPC::Core::CallCredentials.new credentials.client.updater_proc
54
+ end
55
+
56
+ def service
57
+ return mocked_service if mocked_service
58
+ @service ||= \
59
+ V1::SpannerClient.new(
60
+ service_path: host,
61
+ channel: channel,
62
+ timeout: timeout,
63
+ client_config: client_config,
64
+ lib_name: "gccl",
65
+ lib_version: Google::Cloud::Spanner::VERSION)
66
+ end
67
+ attr_accessor :mocked_service
68
+
69
+ def instances
70
+ return mocked_instances if mocked_instances
71
+ @instances ||= \
72
+ Admin::Instance::V1::InstanceAdminClient.new(
73
+ service_path: host,
74
+ channel: channel,
75
+ timeout: timeout,
76
+ client_config: client_config,
77
+ lib_name: "gccl",
78
+ lib_version: Google::Cloud::Spanner::VERSION)
79
+ end
80
+ attr_accessor :mocked_instances
81
+
82
+ def databases
83
+ return mocked_databases if mocked_databases
84
+ @databases ||= \
85
+ Admin::Database::V1::DatabaseAdminClient.new(
86
+ service_path: host,
87
+ channel: channel,
88
+ timeout: timeout,
89
+ client_config: client_config,
90
+ lib_name: "gccl",
91
+ lib_version: Google::Cloud::Spanner::VERSION)
92
+ end
93
+ attr_accessor :mocked_databases
94
+
95
+ def insecure?
96
+ credentials == :this_channel_is_insecure
97
+ end
98
+
99
+ def list_instances token: nil, max: nil
100
+ call_options = nil
101
+ call_options = Google::Gax::CallOptions.new page_token: token if token
102
+
103
+ execute do
104
+ paged_enum = instances.list_instances project_path,
105
+ page_size: max,
106
+ options: call_options
107
+
108
+ paged_enum.page.response
109
+ end
110
+ end
111
+
112
+ def get_instance name
113
+ execute do
114
+ instances.get_instance instance_path(name)
115
+ end
116
+ end
117
+
118
+ def create_instance instance_id, name: nil, config: nil, nodes: nil,
119
+ labels: nil
120
+ labels = Hash[labels.map { |k, v| [String(k), String(v)] }] if labels
121
+
122
+ create_obj = Google::Spanner::Admin::Instance::V1::Instance.new({
123
+ display_name: name, config: instance_config_path(config),
124
+ node_count: nodes, labels: labels
125
+ }.delete_if { |_, v| v.nil? })
126
+
127
+ execute do
128
+ instances.create_instance project_path, instance_id, create_obj
129
+ end
130
+ end
131
+
132
+ def update_instance instance_obj
133
+ mask = Google::Protobuf::FieldMask.new(
134
+ paths: %w(display_name node_count labels))
135
+
136
+ execute do
137
+ instances.update_instance instance_obj, mask
138
+ end
139
+ end
140
+
141
+ def delete_instance name
142
+ execute do
143
+ instances.delete_instance instance_path(name)
144
+ end
145
+ end
146
+
147
+ def get_instance_policy name
148
+ execute do
149
+ instances.get_iam_policy instance_path(name)
150
+ end
151
+ end
152
+
153
+ def set_instance_policy name, new_policy
154
+ execute do
155
+ instances.set_iam_policy instance_path(name), new_policy
156
+ end
157
+ end
158
+
159
+ def test_instance_permissions name, permissions
160
+ execute do
161
+ instances.test_iam_permissions instance_path(name), permissions
162
+ end
163
+ end
164
+
165
+ def list_instance_configs token: nil, max: nil
166
+ call_options = nil
167
+ call_options = Google::Gax::CallOptions.new page_token: token if token
168
+
169
+ execute do
170
+ paged_enum = instances.list_instance_configs project_path,
171
+ page_size: max,
172
+ options: call_options
173
+
174
+ paged_enum.page.response
175
+ end
176
+ end
177
+
178
+ def get_instance_config name
179
+ execute do
180
+ instances.get_instance_config instance_config_path(name)
181
+ end
182
+ end
183
+
184
+ def list_databases instance_id, token: nil, max: nil
185
+ call_options = nil
186
+ call_options = Google::Gax::CallOptions.new page_token: token if token
187
+
188
+ execute do
189
+ paged_enum = databases.list_databases instance_path(instance_id),
190
+ page_size: max,
191
+ options: call_options
192
+
193
+ paged_enum.page.response
194
+ end
195
+ end
196
+
197
+ def get_database instance_id, database_id
198
+ execute do
199
+ databases.get_database database_path(instance_id, database_id)
200
+ end
201
+ end
202
+
203
+ def create_database instance_id, database_id, statements: []
204
+ execute do
205
+ databases.create_database \
206
+ instance_path(instance_id),
207
+ "CREATE DATABASE `#{database_id}`",
208
+ extra_statements: Array(statements)
209
+ end
210
+ end
211
+
212
+ def drop_database instance_id, database_id
213
+ execute do
214
+ databases.drop_database database_path(instance_id, database_id)
215
+ end
216
+ end
217
+
218
+ def get_database_ddl instance_id, database_id
219
+ execute do
220
+ databases.get_database_ddl database_path(instance_id, database_id)
221
+ end
222
+ end
223
+
224
+ def update_database_ddl instance_id, database_id, statements: [],
225
+ operation_id: nil
226
+ execute do
227
+ databases.update_database_ddl \
228
+ database_path(instance_id, database_id),
229
+ Array(statements),
230
+ operation_id: operation_id
231
+ end
232
+ end
233
+
234
+ def get_database_policy instance_id, database_id
235
+ execute do
236
+ databases.get_iam_policy database_path(instance_id, database_id)
237
+ end
238
+ end
239
+
240
+ def set_database_policy instance_id, database_id, new_policy
241
+ execute do
242
+ databases.set_iam_policy \
243
+ database_path(instance_id, database_id), new_policy
244
+ end
245
+ end
246
+
247
+ def test_database_permissions instance_id, database_id, permissions
248
+ execute do
249
+ databases.test_iam_permissions \
250
+ database_path(instance_id, database_id), permissions
251
+ end
252
+ end
253
+
254
+ def get_session session_name
255
+ opts = default_options_from_session session_name
256
+ execute do
257
+ service.get_session session_name, options: opts
258
+ end
259
+ end
260
+
261
+ def create_session database_name
262
+ opts = default_options_from_session database_name
263
+ execute do
264
+ service.create_session database_name, options: opts
265
+ end
266
+ end
267
+
268
+ def delete_session session_name
269
+ opts = default_options_from_session session_name
270
+ execute do
271
+ service.delete_session session_name, options: opts
272
+ end
273
+ end
274
+
275
+ def execute_sql session_name, sql, transaction: nil, params: nil
276
+ input_params = nil
277
+ input_param_types = nil
278
+ unless params.nil?
279
+ input_param_pairs = Convert.to_query_params params
280
+ input_params = Google::Protobuf::Struct.new(
281
+ fields: Hash[input_param_pairs.map { |k, v| [k, v.first] }])
282
+ input_param_types = Hash[
283
+ input_param_pairs.map { |k, v| [k, v.last] }]
284
+ end
285
+ opts = default_options_from_session session_name
286
+ execute do
287
+ service.execute_sql \
288
+ session_name, sql, transaction: transaction, params: input_params,
289
+ param_types: input_param_types, options: opts
290
+ end
291
+ end
292
+
293
+ def streaming_execute_sql session_name, sql, transaction: nil,
294
+ params: nil, types: nil, resume_token: nil
295
+ input_params = nil
296
+ input_param_types = nil
297
+ unless params.nil?
298
+ input_param_pairs = Convert.to_query_params params, types
299
+ input_params = Google::Protobuf::Struct.new(
300
+ fields: Hash[input_param_pairs.map { |k, v| [k, v.first] }])
301
+ input_param_types = Hash[
302
+ input_param_pairs.map { |k, v| [k, v.last] }]
303
+ end
304
+ opts = default_options_from_session session_name
305
+ execute do
306
+ service.execute_streaming_sql \
307
+ session_name, sql, transaction: transaction, params: input_params,
308
+ param_types: input_param_types,
309
+ resume_token: resume_token, options: opts
310
+ end
311
+ end
312
+
313
+ def read_table session_name, table_name, columns, keys: nil, index: nil,
314
+ transaction: nil, limit: nil
315
+ columns.map!(&:to_s)
316
+ opts = default_options_from_session session_name
317
+ execute do
318
+ service.read \
319
+ session_name, table_name, columns, key_set(keys),
320
+ transaction: transaction, index: index, limit: limit,
321
+ options: opts
322
+ end
323
+ end
324
+
325
+ def streaming_read_table session_name, table_name, columns, keys: nil,
326
+ index: nil, transaction: nil, limit: nil,
327
+ resume_token: nil
328
+ columns.map!(&:to_s)
329
+ opts = default_options_from_session session_name
330
+ execute do
331
+ service.streaming_read \
332
+ session_name, table_name, columns, key_set(keys),
333
+ transaction: transaction, index: index, limit: limit,
334
+ resume_token: resume_token, options: opts
335
+ end
336
+ end
337
+
338
+ def commit session_name, mutations = [], transaction_id: nil
339
+ tx_opts = nil
340
+ if transaction_id.nil?
341
+ tx_opts = Google::Spanner::V1::TransactionOptions.new(read_write:
342
+ Google::Spanner::V1::TransactionOptions::ReadWrite.new)
343
+ end
344
+ opts = default_options_from_session session_name
345
+ execute do
346
+ service.commit \
347
+ session_name, mutations,
348
+ transaction_id: transaction_id, single_use_transaction: tx_opts,
349
+ options: opts
350
+ end
351
+ end
352
+
353
+ def rollback session_name, transaction_id
354
+ opts = default_options_from_session session_name
355
+ execute do
356
+ service.rollback session_name, transaction_id, options: opts
357
+ end
358
+ end
359
+
360
+ def begin_transaction session_name
361
+ tx_opts = Google::Spanner::V1::TransactionOptions.new(read_write:
362
+ Google::Spanner::V1::TransactionOptions::ReadWrite.new)
363
+ opts = default_options_from_session session_name
364
+ execute do
365
+ service.begin_transaction session_name, tx_opts, options: opts
366
+ end
367
+ end
368
+
369
+ def create_snapshot session_name, strong: nil, timestamp: nil,
370
+ staleness: nil
371
+ tx_opts = Google::Spanner::V1::TransactionOptions.new(read_only:
372
+ Google::Spanner::V1::TransactionOptions::ReadOnly.new({
373
+ strong: strong,
374
+ read_timestamp: Convert.time_to_timestamp(timestamp),
375
+ exact_staleness: Convert.number_to_duration(staleness),
376
+ return_read_timestamp: true
377
+ }.delete_if { |_, v| v.nil? }))
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
+
384
+ def inspect
385
+ "#{self.class}(#{@project})"
386
+ end
387
+
388
+ protected
389
+
390
+ def key_set keys
391
+ return Google::Spanner::V1::KeySet.new(all: true) if keys.nil?
392
+ keys = [keys] unless keys.is_a? Array
393
+ return Google::Spanner::V1::KeySet.new(all: true) if keys.empty?
394
+ if keys_are_ranges? keys
395
+ keys = [keys] unless keys.is_a? Array
396
+ key_ranges = keys.map do |r|
397
+ Convert.to_key_range(r)
398
+ end
399
+ return Google::Spanner::V1::KeySet.new(ranges: key_ranges)
400
+ end
401
+ key_list = Array(keys).map do |i|
402
+ Convert.raw_to_value(Array(i)).list_value
403
+ end
404
+ Google::Spanner::V1::KeySet.new keys: key_list
405
+ end
406
+
407
+ def keys_are_ranges? keys
408
+ keys.each do |key|
409
+ return true if key.is_a? ::Range
410
+ return true if key.is_a? Google::Cloud::Spanner::Range
411
+ end
412
+ false
413
+ end
414
+
415
+ def default_options_from_session session_name
416
+ default_prefix = session_name.split("/sessions/").first
417
+ Google::Gax::CallOptions.new kwargs: \
418
+ { "google-cloud-resource-prefix" => default_prefix }
419
+ end
420
+
421
+ def project_path
422
+ Admin::Instance::V1::InstanceAdminClient.project_path project
423
+ end
424
+
425
+ def instance_path name
426
+ return name if name.to_s.include? "/"
427
+ Admin::Instance::V1::InstanceAdminClient.instance_path(
428
+ project, name)
429
+ end
430
+
431
+ def instance_config_path name
432
+ return name if name.to_s.include? "/"
433
+ Admin::Instance::V1::InstanceAdminClient.instance_config_path(
434
+ project, name.to_s)
435
+ end
436
+
437
+ def database_path instance_id, database_id
438
+ Admin::Database::V1::DatabaseAdminClient.database_path(
439
+ project, instance_id, database_id)
440
+ end
441
+
442
+ def session_path instance_id, database_id, session_id
443
+ V1::SpannerClient.session_path(
444
+ project, instance_id, database_id, session_id)
445
+ end
446
+
447
+ def execute
448
+ yield
449
+ rescue Google::Gax::GaxError => e
450
+ # GaxError wraps BadStatus, but exposes it as #cause
451
+ raise Google::Cloud::Error.from_error(e.cause)
452
+ rescue GRPC::BadStatus => e
453
+ raise Google::Cloud::Error.from_error(e)
454
+ end
455
+ end
456
+ end
457
+ end
458
+ end