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,351 @@
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
+ require "google/cloud/spanner/data"
17
+ require "google/cloud/spanner/convert"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Spanner
22
+ ##
23
+ # # Commit
24
+ #
25
+ # Accepts mutations for execution within a transaction. All writes will
26
+ # execute atomically at a single logical point in time across columns,
27
+ # rows, and tables in a database.
28
+ #
29
+ # All changes are accumulated in memory until the block passed to
30
+ # {Client#commit} completes.
31
+ #
32
+ # @example
33
+ # require "google/cloud/spanner"
34
+ #
35
+ # spanner = Google::Cloud::Spanner.new
36
+ #
37
+ # db = spanner.client "my-instance", "my-database"
38
+ #
39
+ # db.commit do |c|
40
+ # c.update "users", [{ id: 1, name: "Charlie", active: false }]
41
+ # c.insert "users", [{ id: 2, name: "Harvey", active: true }]
42
+ # end
43
+ #
44
+ class Commit
45
+ ##
46
+ # @private
47
+ def initialize
48
+ @mutations = []
49
+ end
50
+
51
+ ##
52
+ # Inserts or updates rows in a table. If any of the rows already exist,
53
+ # then its column values are overwritten with the ones provided. Any
54
+ # column values not explicitly written are preserved.
55
+ #
56
+ # All changes are accumulated in memory until the block passed to
57
+ # {Client#commit} completes.
58
+ #
59
+ # @param [String] table The name of the table in the database to be
60
+ # modified.
61
+ # @param [Array<Hash>] rows One or more hash objects with the hash keys
62
+ # matching the table's columns, and the hash values matching the
63
+ # table's values.
64
+ #
65
+ # Ruby types are mapped to Spanner types as follows:
66
+ #
67
+ # | Spanner | Ruby | Notes |
68
+ # |-------------|----------------|---|
69
+ # | `BOOL` | `true`/`false` | |
70
+ # | `INT64` | `Integer` | |
71
+ # | `FLOAT64` | `Float` | |
72
+ # | `STRING` | `String` | |
73
+ # | `DATE` | `Date` | |
74
+ # | `TIMESTAMP` | `Time`, `DateTime` | |
75
+ # | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
76
+ # | `ARRAY` | `Array` | Nested arrays are not supported. |
77
+ #
78
+ # See [Data
79
+ # types](https://cloud.google.com/spanner/docs/data-definition-language#data_types).
80
+ #
81
+ # @example
82
+ # require "google/cloud/spanner"
83
+ #
84
+ # spanner = Google::Cloud::Spanner.new
85
+ #
86
+ # db = spanner.client "my-instance", "my-database"
87
+ #
88
+ # db.commit do |c|
89
+ # c.upsert "users", [{ id: 1, name: "Charlie", active: false },
90
+ # { id: 2, name: "Harvey", active: true }]
91
+ # end
92
+ #
93
+ def upsert table, *rows
94
+ rows = Array(rows).flatten
95
+ return rows if rows.empty?
96
+ rows.delete_if(&:nil?)
97
+ rows.delete_if(&:empty?)
98
+ @mutations += rows.map do |row|
99
+ Google::Spanner::V1::Mutation.new(
100
+ insert_or_update: Google::Spanner::V1::Mutation::Write.new(
101
+ table: table, columns: row.keys.map(&:to_s),
102
+ values: [Convert.raw_to_value(row.values).list_value]
103
+ )
104
+ )
105
+ end
106
+ rows
107
+ end
108
+ alias_method :save, :upsert
109
+
110
+ ##
111
+ # Inserts new rows in a table. If any of the rows already exist, the
112
+ # write or request fails with error {Google::Cloud::AlreadyExistsError}.
113
+ #
114
+ # All changes are accumulated in memory until the block passed to
115
+ # {Client#commit} completes.
116
+ #
117
+ # @param [String] table The name of the table in the database to be
118
+ # modified.
119
+ # @param [Array<Hash>] rows One or more hash objects with the hash keys
120
+ # matching the table's columns, and the hash values matching the
121
+ # table's values.
122
+ #
123
+ # Ruby types are mapped to Spanner types as follows:
124
+ #
125
+ # | Spanner | Ruby | Notes |
126
+ # |-------------|----------------|---|
127
+ # | `BOOL` | `true`/`false` | |
128
+ # | `INT64` | `Integer` | |
129
+ # | `FLOAT64` | `Float` | |
130
+ # | `STRING` | `String` | |
131
+ # | `DATE` | `Date` | |
132
+ # | `TIMESTAMP` | `Time`, `DateTime` | |
133
+ # | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
134
+ # | `ARRAY` | `Array` | Nested arrays are not supported. |
135
+ #
136
+ # See [Data
137
+ # types](https://cloud.google.com/spanner/docs/data-definition-language#data_types).
138
+ #
139
+ # @example
140
+ # require "google/cloud/spanner"
141
+ #
142
+ # spanner = Google::Cloud::Spanner.new
143
+ #
144
+ # db = spanner.client "my-instance", "my-database"
145
+ #
146
+ # db.commit do |c|
147
+ # c.insert "users", [{ id: 1, name: "Charlie", active: false },
148
+ # { id: 2, name: "Harvey", active: true }]
149
+ # end
150
+ #
151
+ def insert table, *rows
152
+ rows = Array(rows).flatten
153
+ return rows if rows.empty?
154
+ rows.delete_if(&:nil?)
155
+ rows.delete_if(&:empty?)
156
+ @mutations += rows.map do |row|
157
+ Google::Spanner::V1::Mutation.new(
158
+ insert: Google::Spanner::V1::Mutation::Write.new(
159
+ table: table, columns: row.keys.map(&:to_s),
160
+ values: [Convert.raw_to_value(row.values).list_value]
161
+ )
162
+ )
163
+ end
164
+ rows
165
+ end
166
+
167
+ ##
168
+ # Updates existing rows in a table. If any of the rows does not already
169
+ # exist, the request fails with error {Google::Cloud::NotFoundError}.
170
+ #
171
+ # All changes are accumulated in memory until the block passed to
172
+ # {Client#commit} completes.
173
+ #
174
+ # @param [String] table The name of the table in the database to be
175
+ # modified.
176
+ # @param [Array<Hash>] rows One or more hash objects with the hash keys
177
+ # matching the table's columns, and the hash values matching the
178
+ # table's values.
179
+ #
180
+ # Ruby types are mapped to Spanner types as follows:
181
+ #
182
+ # | Spanner | Ruby | Notes |
183
+ # |-------------|----------------|---|
184
+ # | `BOOL` | `true`/`false` | |
185
+ # | `INT64` | `Integer` | |
186
+ # | `FLOAT64` | `Float` | |
187
+ # | `STRING` | `String` | |
188
+ # | `DATE` | `Date` | |
189
+ # | `TIMESTAMP` | `Time`, `DateTime` | |
190
+ # | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
191
+ # | `ARRAY` | `Array` | Nested arrays are not supported. |
192
+ #
193
+ # See [Data
194
+ # types](https://cloud.google.com/spanner/docs/data-definition-language#data_types).
195
+ #
196
+ # @example
197
+ # require "google/cloud/spanner"
198
+ #
199
+ # spanner = Google::Cloud::Spanner.new
200
+ #
201
+ # db = spanner.client "my-instance", "my-database"
202
+ #
203
+ # db.commit do |c|
204
+ # c.update "users", [{ id: 1, name: "Charlie", active: false },
205
+ # { id: 2, name: "Harvey", active: true }]
206
+ # end
207
+ #
208
+ def update table, *rows
209
+ rows = Array(rows).flatten
210
+ return rows if rows.empty?
211
+ rows.delete_if(&:nil?)
212
+ rows.delete_if(&:empty?)
213
+ @mutations += rows.map do |row|
214
+ Google::Spanner::V1::Mutation.new(
215
+ update: Google::Spanner::V1::Mutation::Write.new(
216
+ table: table, columns: row.keys.map(&:to_s),
217
+ values: [Convert.raw_to_value(row.values).list_value]
218
+ )
219
+ )
220
+ end
221
+ rows
222
+ end
223
+
224
+ ##
225
+ # Inserts or replaces rows in a table. If any of the rows already exist,
226
+ # it is deleted, and the column values provided are inserted instead.
227
+ # Unlike #upsert, this means any values not explicitly written become
228
+ # `NULL`.
229
+ #
230
+ # All changes are accumulated in memory until the block passed to
231
+ # {Client#commit} completes.
232
+ #
233
+ # @param [String] table The name of the table in the database to be
234
+ # modified.
235
+ # @param [Array<Hash>] rows One or more hash objects with the hash keys
236
+ # matching the table's columns, and the hash values matching the
237
+ # table's values.
238
+ #
239
+ # Ruby types are mapped to Spanner types as follows:
240
+ #
241
+ # | Spanner | Ruby | Notes |
242
+ # |-------------|----------------|---|
243
+ # | `BOOL` | `true`/`false` | |
244
+ # | `INT64` | `Integer` | |
245
+ # | `FLOAT64` | `Float` | |
246
+ # | `STRING` | `String` | |
247
+ # | `DATE` | `Date` | |
248
+ # | `TIMESTAMP` | `Time`, `DateTime` | |
249
+ # | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
250
+ # | `ARRAY` | `Array` | Nested arrays are not supported. |
251
+ #
252
+ # See [Data
253
+ # types](https://cloud.google.com/spanner/docs/data-definition-language#data_types).
254
+ #
255
+ # @example
256
+ # require "google/cloud/spanner"
257
+ #
258
+ # spanner = Google::Cloud::Spanner.new
259
+ #
260
+ # db = spanner.client "my-instance", "my-database"
261
+ #
262
+ # db.commit do |c|
263
+ # c.replace "users", [{ id: 1, name: "Charlie", active: false },
264
+ # { id: 2, name: "Harvey", active: true }]
265
+ # end
266
+ #
267
+ def replace table, *rows
268
+ rows = Array(rows).flatten
269
+ return rows if rows.empty?
270
+ rows.delete_if(&:nil?)
271
+ rows.delete_if(&:empty?)
272
+ @mutations += rows.map do |row|
273
+ Google::Spanner::V1::Mutation.new(
274
+ replace: Google::Spanner::V1::Mutation::Write.new(
275
+ table: table, columns: row.keys.map(&:to_s),
276
+ values: [Convert.raw_to_value(row.values).list_value]
277
+ )
278
+ )
279
+ end
280
+ rows
281
+ end
282
+
283
+ ##
284
+ # Deletes rows from a table. Succeeds whether or not the specified rows
285
+ # were present.
286
+ #
287
+ # All changes are accumulated in memory until the block passed to
288
+ # {Client#commit} completes.
289
+ #
290
+ # @param [String] table The name of the table in the database to be
291
+ # modified.
292
+ # @param [Object, Array<Object>] keys A single, or list of keys or key
293
+ # ranges to match returned data to. Values should have exactly as many
294
+ # elements as there are columns in the primary key.
295
+ #
296
+ # @example
297
+ # require "google/cloud/spanner"
298
+ #
299
+ # spanner = Google::Cloud::Spanner.new
300
+ #
301
+ # db = spanner.client "my-instance", "my-database"
302
+ #
303
+ # db.commit do |c|
304
+ # c.delete "users", [1, 2, 3]
305
+ # end
306
+ #
307
+ def delete table, keys = []
308
+ @mutations += [
309
+ Google::Spanner::V1::Mutation.new(
310
+ delete: Google::Spanner::V1::Mutation::Delete.new(
311
+ table: table, key_set: key_set(keys))
312
+ )
313
+ ]
314
+ keys
315
+ end
316
+
317
+ # @private
318
+ def mutations
319
+ @mutations
320
+ end
321
+
322
+ protected
323
+
324
+ def key_set keys
325
+ return Google::Spanner::V1::KeySet.new(all: true) if keys.nil?
326
+ keys = [keys] unless keys.is_a? Array
327
+ return Google::Spanner::V1::KeySet.new(all: true) if keys.empty?
328
+ if keys_are_ranges? keys
329
+ keys = [keys] unless keys.is_a? Array
330
+ key_ranges = keys.map do |r|
331
+ Convert.to_key_range(r)
332
+ end
333
+ return Google::Spanner::V1::KeySet.new(ranges: key_ranges)
334
+ end
335
+ key_list = Array(keys).map do |i|
336
+ Convert.raw_to_value(Array(i)).list_value
337
+ end
338
+ Google::Spanner::V1::KeySet.new keys: key_list
339
+ end
340
+
341
+ def keys_are_ranges? keys
342
+ keys.each do |key|
343
+ return true if key.is_a? ::Range
344
+ return true if key.is_a? Google::Cloud::Spanner::Range
345
+ end
346
+ false
347
+ end
348
+ end
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,311 @@
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
+ require "time"
17
+ require "date"
18
+ require "stringio"
19
+ require "base64"
20
+ require "google/cloud/spanner/data"
21
+
22
+ module Google
23
+ module Cloud
24
+ module Spanner
25
+ ##
26
+ # @private Helper module for converting Spanner values.
27
+ module Convert
28
+ # rubocop:disable all
29
+
30
+ module ClassMethods
31
+ def to_query_params params, types = nil
32
+ types ||= {}
33
+ formatted_params = params.map do |key, obj|
34
+ [String(key), raw_to_value_and_type(obj, types[key])]
35
+ end
36
+ Hash[formatted_params]
37
+ end
38
+
39
+ def raw_to_value_and_type obj, type = nil
40
+ if NilClass === obj
41
+ if type
42
+ if type.is_a?(Array) && type.count == 1
43
+ [Google::Protobuf::Value.new(null_value: :NULL_VALUE),
44
+ Google::Spanner::V1::Type.new(
45
+ code: :ARRAY, array_element_type:
46
+ Google::Spanner::V1::Type.new(code: type.first))]
47
+ # elsif type.is_a? Fields
48
+ else
49
+ [Google::Protobuf::Value.new(null_value: :NULL_VALUE),
50
+ Google::Spanner::V1::Type.new(code: type)]
51
+ end
52
+ else
53
+ raise ArgumentError, "Must provide type for nil values."
54
+ end
55
+ elsif String === obj
56
+ [raw_to_value(obj), Google::Spanner::V1::Type.new(code: :STRING)]
57
+ elsif Symbol === obj
58
+ [raw_to_value(obj.to_s),
59
+ Google::Spanner::V1::Type.new(code: :STRING)]
60
+ elsif TrueClass === obj
61
+ [raw_to_value(obj), Google::Spanner::V1::Type.new(code: :BOOL)]
62
+ elsif FalseClass === obj
63
+ [raw_to_value(obj), Google::Spanner::V1::Type.new(code: :BOOL)]
64
+ elsif Integer === obj
65
+ [raw_to_value(obj.to_s),
66
+ Google::Spanner::V1::Type.new(code: :INT64)]
67
+ elsif Numeric === obj # Any number not an integer gets to be a float
68
+ [raw_to_value(obj),
69
+ Google::Spanner::V1::Type.new(code: :FLOAT64)]
70
+ elsif Time === obj || DateTime === obj
71
+ [raw_to_value(obj),
72
+ Google::Spanner::V1::Type.new(code: :TIMESTAMP)]
73
+ elsif Date === obj
74
+ [raw_to_value(obj.to_s),
75
+ Google::Spanner::V1::Type.new(code: :DATE)]
76
+ elsif Array === obj
77
+ if type && !type.is_a?(Array)
78
+ raise ArgumentError, "Array values must have an Array type."
79
+ end
80
+ type ||= begin
81
+ # Find the param type for the first non-nil item the list
82
+ nested_param_value = obj.detect { |x| !x.nil? }
83
+ if nested_param_value.nil?
84
+ raise ArgumentError, "Array values must have an Array type."
85
+ end
86
+ [raw_to_value_and_type(nested_param_value).last.code]
87
+ end
88
+ [raw_to_value(obj),
89
+ Google::Spanner::V1::Type.new(
90
+ code: :ARRAY, array_element_type:
91
+ Google::Spanner::V1::Type.new(code: type.first))]
92
+ elsif Hash === obj
93
+ field_pairs = obj.map do |key, value|
94
+ [key, raw_to_value_and_type(value).last]
95
+ end
96
+ formatted_fields = field_pairs.map do |name, param_type|
97
+ Google::Spanner::V1::StructType::Field.new(
98
+ name: String(name), type: param_type
99
+ )
100
+ end
101
+ [raw_to_value(obj),
102
+ Google::Spanner::V1::Type.new(
103
+ code: :STRUCT,
104
+ struct_type: Google::Spanner::V1::StructType.new(
105
+ fields: formatted_fields
106
+ ))]
107
+ elsif obj.respond_to?(:read) && obj.respond_to?(:rewind)
108
+ obj.rewind
109
+ [raw_to_value(obj),
110
+ Google::Spanner::V1::Type.new(code: :BYTES)]
111
+ else
112
+ raise ArgumentError,
113
+ "A parameter of type #{obj.class} is not supported."
114
+ end
115
+ end
116
+
117
+ def raw_to_value obj
118
+ if NilClass === obj
119
+ Google::Protobuf::Value.new null_value: :NULL_VALUE
120
+ elsif String === obj
121
+ Google::Protobuf::Value.new string_value: obj
122
+ elsif Symbol === obj
123
+ Google::Protobuf::Value.new string_value: obj.to_s
124
+ elsif TrueClass === obj
125
+ Google::Protobuf::Value.new bool_value: true
126
+ elsif FalseClass === obj
127
+ Google::Protobuf::Value.new bool_value: false
128
+ elsif Integer === obj
129
+ Google::Protobuf::Value.new string_value: obj.to_s
130
+ elsif Numeric === obj # Any number not an integer gets to be a float
131
+ if obj == Float::INFINITY
132
+ Google::Protobuf::Value.new string_value: "Infinity"
133
+ elsif obj == -Float::INFINITY
134
+ Google::Protobuf::Value.new string_value: "-Infinity"
135
+ elsif obj.respond_to?(:nan?) && obj.nan?
136
+ Google::Protobuf::Value.new string_value: "NaN"
137
+ else
138
+ Google::Protobuf::Value.new number_value: obj.to_f
139
+ end
140
+ elsif Time === obj || DateTime === obj
141
+ Google::Protobuf::Value.new(string_value:
142
+ obj.to_time.utc.strftime("%FT%T.%NZ"))
143
+ elsif Date === obj
144
+ Google::Protobuf::Value.new string_value: obj.to_s
145
+ elsif Array === obj
146
+ Google::Protobuf::Value.new list_value:
147
+ Google::Protobuf::ListValue.new(values:
148
+ obj.map { |o| raw_to_value(o) })
149
+ elsif Hash === obj
150
+ Google::Protobuf::Value.new struct_value:
151
+ Google::Protobuf::Struct.new(fields:
152
+ Hash[obj.map { |k, v| [String(k), raw_to_value(v)] }])
153
+ elsif obj.respond_to?(:read) && obj.respond_to?(:rewind)
154
+ obj.rewind
155
+ content = obj.read.force_encoding("ASCII-8BIT")
156
+ encoded_content = Base64.strict_encode64(content)
157
+ Google::Protobuf::Value.new(string_value: encoded_content)
158
+ else
159
+ raise ArgumentError,
160
+ "A value of type #{obj.class} is not supported."
161
+ end
162
+ end
163
+
164
+ def row_to_pairs row_types, row
165
+ row_types.zip(row).map do |field, value|
166
+ [field.name.to_sym, value_to_raw(value, field.type)]
167
+ end
168
+ end
169
+
170
+ def row_to_raw row_types, row
171
+ Hash[row_to_pairs(row_types, row)]
172
+ end
173
+
174
+ def value_to_raw value, type
175
+ return nil if value.kind == :null_value
176
+ case type.code
177
+ when :BOOL
178
+ value.bool_value
179
+ when :INT64
180
+ Integer value.string_value
181
+ when :FLOAT64
182
+ if value.kind == :string_value
183
+ if value.string_value == "Infinity"
184
+ Float::INFINITY
185
+ elsif value.string_value == "-Infinity"
186
+ -Float::INFINITY
187
+ elsif value.string_value == "NaN"
188
+ Float::NAN
189
+ else
190
+ Float value.string_value
191
+ end
192
+ else
193
+ value.number_value
194
+ end
195
+ when :TIMESTAMP
196
+ Time.parse value.string_value
197
+ when :DATE
198
+ Date.parse value.string_value
199
+ when :STRING
200
+ value.string_value
201
+ when :BYTES
202
+ StringIO.new Base64.decode64 value.string_value
203
+ when :ARRAY
204
+ value.list_value.values.map do |v|
205
+ value_to_raw v, type.array_element_type
206
+ end
207
+ when :STRUCT
208
+ Data.from_grpc value.list_value.values, type.struct_type.fields
209
+ end
210
+ end
211
+
212
+ ##
213
+ # @private Convert an Object to a Google::Protobuf::Value.
214
+ def object_to_value obj
215
+ case obj
216
+ when NilClass then Google::Protobuf::Value.new null_value:
217
+ :NULL_VALUE
218
+ when Numeric then Google::Protobuf::Value.new number_value: obj
219
+ when String then Google::Protobuf::Value.new string_value: obj
220
+ when TrueClass then Google::Protobuf::Value.new bool_value: true
221
+ when FalseClass then Google::Protobuf::Value.new bool_value: false
222
+ when Hash then Google::Protobuf::Value.new struct_value:
223
+ hash_to_struct(obj)
224
+ when Array then Google::Protobuf::Value.new list_value:
225
+ Google::Protobuf::ListValue.new(values:
226
+ obj.map { |o| object_to_value(o) })
227
+ else
228
+ # TODO: Could raise ArgumentError here, or convert to a string
229
+ Google::Protobuf::Value.new string_value: obj.to_s
230
+ end
231
+ end
232
+
233
+ ##
234
+ # @private Convert a Google::Protobuf::Value to an Object.
235
+ def value_to_object value
236
+ # TODO: ArgumentError if struct is not a Google::Protobuf::Value
237
+ if value.kind == :null_value
238
+ nil
239
+ elsif value.kind == :number_value
240
+ value.number_value
241
+ elsif value.kind == :string_value
242
+ value.string_value
243
+ elsif value.kind == :bool_value
244
+ value.bool_value
245
+ elsif value.kind == :struct_value
246
+ struct_to_hash value.struct_value
247
+ elsif value.kind == :list_value
248
+ value.list_value.values.map { |v| value_to_object(v) }
249
+ else
250
+ nil # just in case
251
+ end
252
+ end
253
+
254
+ def number_to_duration number
255
+ return nil if number.nil?
256
+
257
+ Google::Protobuf::Duration.new \
258
+ seconds: number.to_i,
259
+ nanos: (number.remainder(1) * 1000000000).round
260
+ end
261
+
262
+ def duration_to_number duration
263
+ return nil if duration.nil?
264
+
265
+ return duration.seconds if duration.nanos == 0
266
+
267
+ duration.seconds + (duration.nanos / 1000000000.0)
268
+ end
269
+
270
+ def time_to_timestamp time
271
+ return nil if time.nil?
272
+
273
+ # Force the object to be a Time object.
274
+ time = time.to_time
275
+
276
+ Google::Protobuf::Timestamp.new \
277
+ seconds: time.to_i,
278
+ nanos: time.nsec
279
+ end
280
+
281
+ def timestamp_to_time timestamp
282
+ return nil if timestamp.nil?
283
+
284
+ Time.at timestamp.seconds, Rational(timestamp.nanos, 1000)
285
+ end
286
+
287
+ def to_key_range range
288
+ range_opts = {
289
+ start_closed: raw_to_value(Array(range.begin)).list_value,
290
+ end_closed: raw_to_value(Array(range.end)).list_value }
291
+
292
+ if range.respond_to?(:exclude_begin?) && range.exclude_begin?
293
+ range_opts[:start_open] = range_opts[:start_closed]
294
+ range_opts.delete :start_closed
295
+ end
296
+ if range.exclude_end?
297
+ range_opts[:end_open] = range_opts[:end_closed]
298
+ range_opts.delete :end_closed
299
+ end
300
+
301
+ Google::Spanner::V1::KeyRange.new range_opts
302
+ end
303
+ end
304
+
305
+ # rubocop:enable all
306
+
307
+ extend ClassMethods
308
+ end
309
+ end
310
+ end
311
+ end