google-cloud-spanner 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
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