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,78 @@
1
+ {
2
+ "interfaces": {
3
+ "google.spanner.admin.instance.v1.InstanceAdmin": {
4
+ "retry_codes": {
5
+ "idempotent": [
6
+ "DEADLINE_EXCEEDED",
7
+ "UNAVAILABLE"
8
+ ],
9
+ "non_idempotent": [
10
+ "UNAVAILABLE"
11
+ ]
12
+ },
13
+ "retry_params": {
14
+ "default": {
15
+ "initial_retry_delay_millis": 1000,
16
+ "retry_delay_multiplier": 1.3,
17
+ "max_retry_delay_millis": 32000,
18
+ "initial_rpc_timeout_millis": 60000,
19
+ "rpc_timeout_multiplier": 1.0,
20
+ "max_rpc_timeout_millis": 60000,
21
+ "total_timeout_millis": 600000
22
+ }
23
+ },
24
+ "methods": {
25
+ "ListInstanceConfigs": {
26
+ "timeout_millis": 30000,
27
+ "retry_codes_name": "idempotent",
28
+ "retry_params_name": "default"
29
+ },
30
+ "GetInstanceConfig": {
31
+ "timeout_millis": 30000,
32
+ "retry_codes_name": "idempotent",
33
+ "retry_params_name": "default"
34
+ },
35
+ "ListInstances": {
36
+ "timeout_millis": 30000,
37
+ "retry_codes_name": "idempotent",
38
+ "retry_params_name": "default"
39
+ },
40
+ "GetInstance": {
41
+ "timeout_millis": 30000,
42
+ "retry_codes_name": "idempotent",
43
+ "retry_params_name": "default"
44
+ },
45
+ "CreateInstance": {
46
+ "timeout_millis": 30000,
47
+ "retry_codes_name": "non_idempotent",
48
+ "retry_params_name": "default"
49
+ },
50
+ "UpdateInstance": {
51
+ "timeout_millis": 30000,
52
+ "retry_codes_name": "non_idempotent",
53
+ "retry_params_name": "default"
54
+ },
55
+ "DeleteInstance": {
56
+ "timeout_millis": 30000,
57
+ "retry_codes_name": "idempotent",
58
+ "retry_params_name": "default"
59
+ },
60
+ "SetIamPolicy": {
61
+ "timeout_millis": 30000,
62
+ "retry_codes_name": "non_idempotent",
63
+ "retry_params_name": "default"
64
+ },
65
+ "GetIamPolicy": {
66
+ "timeout_millis": 30000,
67
+ "retry_codes_name": "idempotent",
68
+ "retry_params_name": "default"
69
+ },
70
+ "TestIamPermissions": {
71
+ "timeout_millis": 30000,
72
+ "retry_codes_name": "non_idempotent",
73
+ "retry_params_name": "default"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,1034 @@
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/errors"
17
+ require "google/cloud/spanner/project"
18
+ require "google/cloud/spanner/data"
19
+ require "google/cloud/spanner/pool"
20
+ require "google/cloud/spanner/session"
21
+ require "google/cloud/spanner/transaction"
22
+ require "google/cloud/spanner/snapshot"
23
+ require "google/cloud/spanner/range"
24
+ require "google/cloud/spanner/convert"
25
+
26
+ module Google
27
+ module Cloud
28
+ module Spanner
29
+ ##
30
+ # # Client
31
+ #
32
+ # A client is used to read and/or modify data in a Cloud Spanner database.
33
+ #
34
+ # See {Google::Cloud::Spanner::Project#client}.
35
+ #
36
+ # @example
37
+ # require "google/cloud"
38
+ #
39
+ # spanner = Google::Cloud::Spanner.new
40
+ #
41
+ # db = spanner.client "my-instance", "my-database"
42
+ #
43
+ # db.transaction do |tx|
44
+ # results = tx.execute "SELECT * FROM users"
45
+ #
46
+ # results.rows.each do |row|
47
+ # puts "User #{row[:id]} is #{row[:name]}"
48
+ # end
49
+ # end
50
+ #
51
+ class Client
52
+ ##
53
+ # @private Creates a new Spanner Client instance.
54
+ def initialize project, instance_id, database_id, min: 10, max: 100,
55
+ keepalive: 1800, write_ratio: 0.3, fail: true
56
+ @project = project
57
+ @instance_id = instance_id
58
+ @database_id = database_id
59
+ @pool = Pool.new self, min: min, max: max, keepalive: keepalive,
60
+ write_ratio: write_ratio, fail: fail
61
+ end
62
+
63
+ # The unique identifier for the project.
64
+ # @return [String]
65
+ def project_id
66
+ @project.service.project
67
+ end
68
+
69
+ # The unique identifier for the instance.
70
+ # @return [String]
71
+ def instance_id
72
+ @instance_id
73
+ end
74
+
75
+ # The unique identifier for the database.
76
+ # @return [String]
77
+ def database_id
78
+ @database_id
79
+ end
80
+
81
+ # The Spanner project connected to.
82
+ # @return [Project]
83
+ def project
84
+ @project
85
+ end
86
+
87
+ # The Spanner instance connected to.
88
+ # @return [Instance]
89
+ def instance
90
+ @project.instance instance_id
91
+ end
92
+
93
+ # The Spanner database connected to.
94
+ # @return [Database]
95
+ def database
96
+ @project.database instance_id, database_id
97
+ end
98
+
99
+ ##
100
+ # Executes a SQL query.
101
+ #
102
+ # Arguments can be passed using `params`, Ruby types are mapped to
103
+ # Spanner types as follows:
104
+ #
105
+ # | Spanner | Ruby | Notes |
106
+ # |-------------|----------------|---|
107
+ # | `BOOL` | `true`/`false` | |
108
+ # | `INT64` | `Integer` | |
109
+ # | `FLOAT64` | `Float` | |
110
+ # | `STRING` | `String` | |
111
+ # | `DATE` | `Date` | |
112
+ # | `TIMESTAMP` | `Time`, `DateTime` | |
113
+ # | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
114
+ # | `ARRAY` | `Array` | Nested arrays are not supported. |
115
+ #
116
+ # See [Data
117
+ # types](https://cloud.google.com/spanner/docs/data-definition-language#data_types).
118
+ #
119
+ # @param [String] sql The SQL query string. See [Query
120
+ # syntax](https://cloud.google.com/spanner/docs/query-syntax).
121
+ #
122
+ # The SQL query string can contain parameter placeholders. A parameter
123
+ # placeholder consists of "@" followed by the parameter name.
124
+ # Parameter names consist of any combination of letters, numbers, and
125
+ # underscores.
126
+ # @param [Hash] params SQL parameters for the query string. The
127
+ # parameter placeholders, minus the "@", are the the hash keys, and
128
+ # the literal values are the hash values. If the query string contains
129
+ # something like "WHERE id > @msg_id", then the params must contain
130
+ # something like `:msg_id => 1`.
131
+ # @param [Hash] types Types of the SQL parameters in `params`. It is not
132
+ # always possible for Cloud Spanner to infer the right SQL type from a
133
+ # value in `params`. In these cases, the `types` hash can be used to
134
+ # specify the exact SQL type for some or all of the SQL query
135
+ # parameters.
136
+ #
137
+ # The keys of the hash should be query string parameter placeholders,
138
+ # minus the "@". The values of the hash should be Cloud Spanner type
139
+ # codes from the following list:
140
+ #
141
+ # * `:BOOL`
142
+ # * `:BYTES`
143
+ # * `:DATE`
144
+ # * `:FLOAT64`
145
+ # * `:INT64`
146
+ # * `:STRING`
147
+ # * `:TIMESTAMP`
148
+ #
149
+ # Arrays are specified by providing the type code in an array. For
150
+ # example, an array of integers are specified as `[:INT64]`.
151
+ #
152
+ # Structs are not yet supported in query parameters.
153
+ #
154
+ # Types are optional.
155
+ # @param [Hash] single_use Perform the read with a single-use snapshot
156
+ # (read-only transaction). (See
157
+ # [TransactionOptions](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#transactionoptions).)
158
+ # The snapshot can be created by providing exactly one of the
159
+ # following options in the hash:
160
+ #
161
+ # * **Strong**
162
+ # * `:strong` (true, false) Read at a timestamp where all previously
163
+ # committed transactions are visible.
164
+ # * **Exact**
165
+ # * `:timestamp`/`:read_timestamp` (Time, DateTime) Executes all
166
+ # reads at the given timestamp. Unlike other modes, reads at a
167
+ # specific timestamp are repeatable; the same read at the same
168
+ # timestamp always returns the same data. If the timestamp is in
169
+ # the future, the read will block until the specified timestamp,
170
+ # modulo the read's deadline.
171
+ #
172
+ # Useful for large scale consistent reads such as mapreduces, or
173
+ # for coordinating many reads against a consistent snapshot of the
174
+ # data.
175
+ # * `:staleness`/`:exact_staleness` (Numeric) Executes all reads at
176
+ # a timestamp that is exactly the number of seconds provided old.
177
+ # The timestamp is chosen soon after the read is started.
178
+ #
179
+ # Guarantees that all writes that have committed more than the
180
+ # specified number of seconds ago are visible. Because Cloud
181
+ # Spanner chooses the exact timestamp, this mode works even if the
182
+ # client's local clock is substantially skewed from Cloud Spanner
183
+ # commit timestamps.
184
+ #
185
+ # Useful for reading at nearby replicas without the distributed
186
+ # timestamp negotiation overhead of single-use
187
+ # `bounded_staleness`.
188
+ # * **Bounded**
189
+ # * `:bounded_timestamp`/`:min_read_timestamp` (Time, DateTime)
190
+ # Executes all reads at a timestamp greater than the value
191
+ # provided.
192
+ #
193
+ # This is useful for requesting fresher data than some previous
194
+ # read, or data that is fresh enough to observe the effects of
195
+ # some previously committed transaction whose timestamp is known.
196
+ # * `:bounded_staleness`/`:max_staleness` (Numeric) Read data at a
197
+ # timestamp greater than or equal to the number of seconds
198
+ # provided. Guarantees that all writes that have committed more
199
+ # than the specified number of seconds ago are visible. Because
200
+ # Cloud Spanner chooses the exact timestamp, this mode works even
201
+ # if the client's local clock is substantially skewed from Cloud
202
+ # Spanner commit timestamps.
203
+ #
204
+ # Useful for reading the freshest data available at a nearby
205
+ # replica, while bounding the possible staleness if the local
206
+ # replica has fallen behind.
207
+ #
208
+ # @return [Google::Cloud::Spanner::Results] The results of the query
209
+ # execution.
210
+ #
211
+ # @example
212
+ # require "google/cloud/spanner"
213
+ #
214
+ # spanner = Google::Cloud::Spanner.new
215
+ #
216
+ # db = spanner.client "my-instance", "my-database"
217
+ #
218
+ # results = db.execute "SELECT * FROM users"
219
+ #
220
+ # results.rows.each do |row|
221
+ # puts "User #{row[:id]} is #{row[:name]}"
222
+ # end
223
+ #
224
+ # @example Query using query parameters:
225
+ # require "google/cloud/spanner"
226
+ #
227
+ # spanner = Google::Cloud::Spanner.new
228
+ #
229
+ # db = spanner.client "my-instance", "my-database"
230
+ #
231
+ # results = db.execute "SELECT * FROM users WHERE active = @active",
232
+ # params: { active: true }
233
+ #
234
+ # results.rows.each do |row|
235
+ # puts "User #{row[:id]} is #{row[:name]}"
236
+ # end
237
+ #
238
+ def execute sql, params: nil, types: nil, single_use: nil
239
+ validate_single_use_args! single_use
240
+ ensure_service!
241
+
242
+ single_use_tx = single_use_transaction single_use
243
+ results = nil
244
+ @pool.with_session do |session|
245
+ results = session.execute \
246
+ sql, params: params, types: types, transaction: single_use_tx
247
+ end
248
+ results
249
+ end
250
+ alias_method :query, :execute
251
+
252
+ ##
253
+ # Read rows from a database table, as a simple alternative to
254
+ # {#execute}.
255
+ #
256
+ # @param [String] table The name of the table in the database to be
257
+ # read.
258
+ # @param [Array<String, Symbol>] columns The columns of table to be
259
+ # returned for each row matching this request.
260
+ # @param [Object, Array<Object>] keys A single, or list of keys or key
261
+ # ranges to match returned data to. Values should have exactly as many
262
+ # elements as there are columns in the primary key.
263
+ # @param [String] index The name of an index to use instead of the
264
+ # table's primary key when interpreting `id` and sorting result rows.
265
+ # Optional.
266
+ # @param [Integer] limit If greater than zero, no more than this number
267
+ # of rows will be returned. The default is no limit.
268
+ # @param [Hash] single_use Perform the read with a single-use snapshot
269
+ # (read-only transaction). (See
270
+ # [TransactionOptions](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#transactionoptions).)
271
+ # The snapshot can be created by providing exactly one of the
272
+ # following options in the hash:
273
+ #
274
+ # * **Strong**
275
+ # * `:strong` (true, false) Read at a timestamp where all previously
276
+ # committed transactions are visible.
277
+ # * **Exact**
278
+ # * `:timestamp`/`:read_timestamp` (Time, DateTime) Executes all
279
+ # reads at the given timestamp. Unlike other modes, reads at a
280
+ # specific timestamp are repeatable; the same read at the same
281
+ # timestamp always returns the same data. If the timestamp is in
282
+ # the future, the read will block until the specified timestamp,
283
+ # modulo the read's deadline.
284
+ #
285
+ # Useful for large scale consistent reads such as mapreduces, or
286
+ # for coordinating many reads against a consistent snapshot of the
287
+ # data.
288
+ # * `:staleness`/`:exact_staleness` (Numeric) Executes all reads at
289
+ # a timestamp that is exactly the number of seconds provided old.
290
+ # The timestamp is chosen soon after the read is started.
291
+ #
292
+ # Guarantees that all writes that have committed more than the
293
+ # specified number of seconds ago are visible. Because Cloud
294
+ # Spanner chooses the exact timestamp, this mode works even if the
295
+ # client's local clock is substantially skewed from Cloud Spanner
296
+ # commit timestamps.
297
+ #
298
+ # Useful for reading at nearby replicas without the distributed
299
+ # timestamp negotiation overhead of single-use
300
+ # `bounded_staleness`.
301
+ # * **Bounded**
302
+ # * `:bounded_timestamp`/`:min_read_timestamp` (Time, DateTime)
303
+ # Executes all reads at a timestamp greater than the value
304
+ # provided.
305
+ #
306
+ # This is useful for requesting fresher data than some previous
307
+ # read, or data that is fresh enough to observe the effects of
308
+ # some previously committed transaction whose timestamp is known.
309
+ # * `:bounded_staleness`/`:max_staleness` (Numeric) Read data at a
310
+ # timestamp greater than or equal to the number of seconds
311
+ # provided. Guarantees that all writes that have committed more
312
+ # than the specified number of seconds ago are visible. Because
313
+ # Cloud Spanner chooses the exact timestamp, this mode works even
314
+ # if the client's local clock is substantially skewed from Cloud
315
+ # Spanner commit timestamps.
316
+ #
317
+ # Useful for reading the freshest data available at a nearby
318
+ # replica, while bounding the possible staleness if the local
319
+ # replica has fallen behind.
320
+ #
321
+ # @return [Google::Cloud::Spanner::Results] The results of the read.
322
+ #
323
+ # @example
324
+ # require "google/cloud/spanner"
325
+ #
326
+ # spanner = Google::Cloud::Spanner.new
327
+ #
328
+ # db = spanner.client "my-instance", "my-database"
329
+ #
330
+ # results = db.read "users", [:id, :name]
331
+ #
332
+ # results.rows.each do |row|
333
+ # puts "User #{row[:id]} is #{row[:name]}"
334
+ # end
335
+ #
336
+ # @example Use the `keys` option to pass keys and/or key ranges to read.
337
+ # require "google/cloud/spanner"
338
+ #
339
+ # spanner = Google::Cloud::Spanner.new
340
+ #
341
+ # db = spanner.client "my-instance", "my-database"
342
+ #
343
+ # results = db.read "users", [:id, :name], keys: 1..5
344
+ #
345
+ # results.rows.each do |row|
346
+ # puts "User #{row[:id]} is #{row[:name]}"
347
+ # end
348
+ #
349
+ def read table, columns, keys: nil, index: nil, limit: nil,
350
+ single_use: nil
351
+ validate_single_use_args! single_use
352
+ ensure_service!
353
+
354
+ single_use_tx = single_use_transaction single_use
355
+ results = nil
356
+ @pool.with_session do |session|
357
+ results = session.read \
358
+ table, columns, keys: keys, index: index, limit: limit,
359
+ transaction: single_use_tx
360
+ end
361
+ results
362
+ end
363
+
364
+ ##
365
+ # Inserts or updates rows in a table. If any of the rows already exist,
366
+ # then its column values are overwritten with the ones provided. Any
367
+ # column values not explicitly written are preserved.
368
+ #
369
+ # Changes are made immediately upon calling this method using a
370
+ # single-use transaction. To make multiple changes in the same
371
+ # single-use transaction use {#commit}. To make changes in a transaction
372
+ # that supports reads and automatic retry protection use {#transaction}.
373
+ #
374
+ # **Note:** This method does not feature replay protection present in
375
+ # {Transaction#upsert} (See {#transaction}). This method makes a single
376
+ # RPC, whereas {Transaction#upsert} requires two RPCs (one of which may
377
+ # be performed in advance), and so this method may be appropriate for
378
+ # latency sensitive and/or high throughput blind upserts.
379
+ #
380
+ # @param [String] table The name of the table in the database to be
381
+ # modified.
382
+ # @param [Array<Hash>] rows One or more hash objects with the hash keys
383
+ # matching the table's columns, and the hash values matching the
384
+ # table's values.
385
+ #
386
+ # Ruby types are mapped to Spanner types as follows:
387
+ #
388
+ # | Spanner | Ruby | Notes |
389
+ # |-------------|----------------|---|
390
+ # | `BOOL` | `true`/`false` | |
391
+ # | `INT64` | `Integer` | |
392
+ # | `FLOAT64` | `Float` | |
393
+ # | `STRING` | `String` | |
394
+ # | `DATE` | `Date` | |
395
+ # | `TIMESTAMP` | `Time`, `DateTime` | |
396
+ # | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
397
+ # | `ARRAY` | `Array` | Nested arrays are not supported. |
398
+ #
399
+ # See [Data
400
+ # types](https://cloud.google.com/spanner/docs/data-definition-language#data_types).
401
+ #
402
+ # @return [Time] The timestamp at which the operation committed.
403
+ #
404
+ # @example
405
+ # require "google/cloud/spanner"
406
+ #
407
+ # spanner = Google::Cloud::Spanner.new
408
+ #
409
+ # db = spanner.client "my-instance", "my-database"
410
+ #
411
+ # db.upsert "users", [{ id: 1, name: "Charlie", active: false },
412
+ # { id: 2, name: "Harvey", active: true }]
413
+ #
414
+ def upsert table, *rows
415
+ @pool.with_session do |session|
416
+ session.upsert table, rows
417
+ end
418
+ end
419
+ alias_method :save, :upsert
420
+
421
+ ##
422
+ # Inserts new rows in a table. If any of the rows already exist, the
423
+ # write or request fails with {Google::Cloud::AlreadyExistsError}.
424
+ #
425
+ # Changes are made immediately upon calling this method using a
426
+ # single-use transaction. To make multiple changes in the same
427
+ # single-use transaction use {#commit}. To make changes in a transaction
428
+ # that supports reads and automatic retry protection use {#transaction}.
429
+ #
430
+ # **Note:** This method does not feature replay protection present in
431
+ # {Transaction#insert} (See {#transaction}). This method makes a single
432
+ # RPC, whereas {Transaction#insert} requires two RPCs (one of which may
433
+ # be performed in advance), and so this method may be appropriate for
434
+ # latency sensitive and/or high throughput blind inserts.
435
+ #
436
+ # @param [String] table The name of the table in the database to be
437
+ # modified.
438
+ # @param [Array<Hash>] rows One or more hash objects with the hash keys
439
+ # matching the table's columns, and the hash values matching the
440
+ # table's values.
441
+ #
442
+ # Ruby types are mapped to Spanner types as follows:
443
+ #
444
+ # | Spanner | Ruby | Notes |
445
+ # |-------------|----------------|---|
446
+ # | `BOOL` | `true`/`false` | |
447
+ # | `INT64` | `Integer` | |
448
+ # | `FLOAT64` | `Float` | |
449
+ # | `STRING` | `String` | |
450
+ # | `DATE` | `Date` | |
451
+ # | `TIMESTAMP` | `Time`, `DateTime` | |
452
+ # | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
453
+ # | `ARRAY` | `Array` | Nested arrays are not supported. |
454
+ #
455
+ # See [Data
456
+ # types](https://cloud.google.com/spanner/docs/data-definition-language#data_types).
457
+ #
458
+ # @return [Time] The timestamp at which the operation committed.
459
+ #
460
+ # @example
461
+ # require "google/cloud/spanner"
462
+ #
463
+ # spanner = Google::Cloud::Spanner.new
464
+ #
465
+ # db = spanner.client "my-instance", "my-database"
466
+ #
467
+ # db.insert "users", [{ id: 1, name: "Charlie", active: false },
468
+ # { id: 2, name: "Harvey", active: true }]
469
+ #
470
+ def insert table, *rows
471
+ @pool.with_session do |session|
472
+ session.insert table, rows
473
+ end
474
+ end
475
+
476
+ ##
477
+ # Updates existing rows in a table. If any of the rows does not already
478
+ # exist, the request fails with {Google::Cloud::NotFoundError}.
479
+ #
480
+ # Changes are made immediately upon calling this method using a
481
+ # single-use transaction. To make multiple changes in the same
482
+ # single-use transaction use {#commit}. To make changes in a transaction
483
+ # that supports reads and automatic retry protection use {#transaction}.
484
+ #
485
+ # **Note:** This method does not feature replay protection present in
486
+ # {Transaction#update} (See {#transaction}). This method makes a single
487
+ # RPC, whereas {Transaction#update} requires two RPCs (one of which may
488
+ # be performed in advance), and so this method may be appropriate for
489
+ # latency sensitive and/or high throughput blind updates.
490
+ #
491
+ # @param [String] table The name of the table in the database to be
492
+ # modified.
493
+ # @param [Array<Hash>] rows One or more hash objects with the hash keys
494
+ # matching the table's columns, and the hash values matching the
495
+ # table's values.
496
+ #
497
+ # Ruby types are mapped to Spanner types as follows:
498
+ #
499
+ # | Spanner | Ruby | Notes |
500
+ # |-------------|----------------|---|
501
+ # | `BOOL` | `true`/`false` | |
502
+ # | `INT64` | `Integer` | |
503
+ # | `FLOAT64` | `Float` | |
504
+ # | `STRING` | `String` | |
505
+ # | `DATE` | `Date` | |
506
+ # | `TIMESTAMP` | `Time`, `DateTime` | |
507
+ # | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
508
+ # | `ARRAY` | `Array` | Nested arrays are not supported. |
509
+ #
510
+ # See [Data
511
+ # types](https://cloud.google.com/spanner/docs/data-definition-language#data_types).
512
+ #
513
+ # @return [Time] The timestamp at which the operation committed.
514
+ #
515
+ # @example
516
+ # require "google/cloud/spanner"
517
+ #
518
+ # spanner = Google::Cloud::Spanner.new
519
+ #
520
+ # db = spanner.client "my-instance", "my-database"
521
+ #
522
+ # db.update "users", [{ id: 1, name: "Charlie", active: false },
523
+ # { id: 2, name: "Harvey", active: true }]
524
+ #
525
+ def update table, *rows
526
+ @pool.with_session do |session|
527
+ session.update table, rows
528
+ end
529
+ end
530
+
531
+ ##
532
+ # Inserts or replaces rows in a table. If any of the rows already exist,
533
+ # it is deleted, and the column values provided are inserted instead.
534
+ # Unlike #upsert, this means any values not explicitly written become
535
+ # `NULL`.
536
+ #
537
+ # Changes are made immediately upon calling this method using a
538
+ # single-use transaction. To make multiple changes in the same
539
+ # single-use transaction use {#commit}. To make changes in a transaction
540
+ # that supports reads and automatic retry protection use {#transaction}.
541
+ #
542
+ # **Note:** This method does not feature replay protection present in
543
+ # {Transaction#replace} (See {#transaction}). This method makes a single
544
+ # RPC, whereas {Transaction#replace} requires two RPCs (one of which may
545
+ # be performed in advance), and so this method may be appropriate for
546
+ # latency sensitive and/or high throughput blind replaces.
547
+ #
548
+ # @param [String] table The name of the table in the database to be
549
+ # modified.
550
+ # @param [Array<Hash>] rows One or more hash objects with the hash keys
551
+ # matching the table's columns, and the hash values matching the
552
+ # table's values.
553
+ #
554
+ # Ruby types are mapped to Spanner types as follows:
555
+ #
556
+ # | Spanner | Ruby | Notes |
557
+ # |-------------|----------------|---|
558
+ # | `BOOL` | `true`/`false` | |
559
+ # | `INT64` | `Integer` | |
560
+ # | `FLOAT64` | `Float` | |
561
+ # | `STRING` | `String` | |
562
+ # | `DATE` | `Date` | |
563
+ # | `TIMESTAMP` | `Time`, `DateTime` | |
564
+ # | `BYTES` | `File`, `IO`, `StringIO`, or similar | |
565
+ # | `ARRAY` | `Array` | Nested arrays are not supported. |
566
+ #
567
+ # See [Data
568
+ # types](https://cloud.google.com/spanner/docs/data-definition-language#data_types).
569
+ #
570
+ # @return [Time] The timestamp at which the operation committed.
571
+ #
572
+ # @example
573
+ # require "google/cloud/spanner"
574
+ #
575
+ # spanner = Google::Cloud::Spanner.new
576
+ #
577
+ # db = spanner.client "my-instance", "my-database"
578
+ #
579
+ # db.replace "users", [{ id: 1, name: "Charlie", active: false },
580
+ # { id: 2, name: "Harvey", active: true }]
581
+ #
582
+ def replace table, *rows
583
+ @pool.with_session do |session|
584
+ session.replace table, rows
585
+ end
586
+ end
587
+
588
+ ##
589
+ # Deletes rows from a table. Succeeds whether or not the specified rows
590
+ # were present.
591
+ #
592
+ # Changes are made immediately upon calling this method using a
593
+ # single-use transaction. To make multiple changes in the same
594
+ # single-use transaction use {#commit}. To make changes in a transaction
595
+ # that supports reads and automatic retry protection use {#transaction}.
596
+ #
597
+ # **Note:** This method does not feature replay protection present in
598
+ # {Transaction#delete} (See {#transaction}). This method makes a single
599
+ # RPC, whereas {Transaction#delete} requires two RPCs (one of which may
600
+ # be performed in advance), and so this method may be appropriate for
601
+ # latency sensitive and/or high throughput blind deletions.
602
+ #
603
+ # @param [String] table The name of the table in the database to be
604
+ # modified.
605
+ # @param [Object, Array<Object>] keys A single, or list of keys or key
606
+ # ranges to match returned data to. Values should have exactly as many
607
+ # elements as there are columns in the primary key.
608
+ #
609
+ # @return [Time] The timestamp at which the operation committed.
610
+ #
611
+ # @example
612
+ # require "google/cloud/spanner"
613
+ #
614
+ # spanner = Google::Cloud::Spanner.new
615
+ #
616
+ # db = spanner.client "my-instance", "my-database"
617
+ #
618
+ # db.delete "users", [1, 2, 3]
619
+ #
620
+ def delete table, keys = []
621
+ @pool.with_session do |session|
622
+ session.delete table, keys
623
+ end
624
+ end
625
+
626
+ ##
627
+ # Creates and commits a transaction for writes that execute atomically
628
+ # at a single logical point in time across columns, rows, and tables in
629
+ # a database.
630
+ #
631
+ # All changes are accumulated in memory until the block completes.
632
+ # Unlike {#transaction}, which can also perform reads, this operation
633
+ # accepts only mutations and makes a single API request.
634
+ #
635
+ # **Note:** This method does not feature replay protection present in
636
+ # {Transaction#commit} (See {#transaction}). This method makes a single
637
+ # RPC, whereas {Transaction#commit} requires two RPCs (one of which may
638
+ # be performed in advance), and so this method may be appropriate for
639
+ # latency sensitive and/or high throughput blind changes.
640
+ #
641
+ # @yield [commit] The block for mutating the data.
642
+ # @yieldparam [Google::Cloud::Spanner::Commit] commit The Commit object.
643
+ #
644
+ # @return [Time] The timestamp at which the operation committed.
645
+ #
646
+ # @example
647
+ # require "google/cloud/spanner"
648
+ #
649
+ # spanner = Google::Cloud::Spanner.new
650
+ #
651
+ # db = spanner.client "my-instance", "my-database"
652
+ #
653
+ # db.commit do |c|
654
+ # c.update "users", [{ id: 1, name: "Charlie", active: false }]
655
+ # c.insert "users", [{ id: 2, name: "Harvey", active: true }]
656
+ # end
657
+ #
658
+ def commit &block
659
+ fail ArgumentError, "Must provide a block" unless block_given?
660
+
661
+ @pool.with_session do |session|
662
+ session.commit(&block)
663
+ end
664
+ end
665
+
666
+ ##
667
+ # Creates a transaction for reads and writes that execute atomically at
668
+ # a single logical point in time across columns, rows, and tables in a
669
+ # database.
670
+ #
671
+ # The transaction will always commit unless an error is raised. If the
672
+ # error raised is {Rollback} the transaction method will return without
673
+ # passing on the error. All other errors will be passed on.
674
+ #
675
+ # All changes are accumulated in memory until the block completes.
676
+ # Transactions will be automatically retried when possible, until
677
+ # `deadline` is reached. This operation makes separate API requests to
678
+ # begin and commit the transaction.
679
+ #
680
+ # @param [Numeric] deadline The total amount of time in seconds the
681
+ # transaction has to succeed. The default is `120`.
682
+ #
683
+ # @yield [transaction] The block for reading and writing data.
684
+ # @yieldparam [Google::Cloud::Spanner::Transaction] transaction The
685
+ # Transaction object.
686
+ #
687
+ # @return [Time] The timestamp at which the transaction committed.
688
+ #
689
+ # @example
690
+ # require "google/cloud/spanner"
691
+ #
692
+ # spanner = Google::Cloud::Spanner.new
693
+ # db = spanner.client "my-instance", "my-database"
694
+ #
695
+ # db.transaction do |tx|
696
+ # results = tx.execute "SELECT * FROM users"
697
+ #
698
+ # results.rows.each do |row|
699
+ # puts "User #{row[:id]} is #{row[:name]}"
700
+ # end
701
+ #
702
+ # c.update "users", [{ id: 1, name: "Charlie", active: false }]
703
+ # c.insert "users", [{ id: 2, name: "Harvey", active: true }]
704
+ # end
705
+ #
706
+ # @example Manually rollback the transaction using {Rollback}:
707
+ # require "google/cloud/spanner"
708
+ #
709
+ # spanner = Google::Cloud::Spanner.new
710
+ # db = spanner.client "my-instance", "my-database"
711
+ #
712
+ # db.transaction do |tx|
713
+ # c.update "users", [{ id: 1, name: "Charlie", active: false }]
714
+ # c.insert "users", [{ id: 2, name: "Harvey", active: true }]
715
+ #
716
+ # if something_wrong?
717
+ # # Rollback the transaction without passing on the error
718
+ # # outside of the transaction method.
719
+ # raise Google::Cloud::Spanner::Rollback
720
+ # end
721
+ # end
722
+ #
723
+ def transaction deadline: 120, &block
724
+ ensure_service!
725
+
726
+ deadline = validate_deadline deadline
727
+ backoff = 1.0
728
+ start_time = current_time
729
+
730
+ @pool.with_transaction do |tx|
731
+ begin
732
+ block.call tx
733
+ commit_resp = @project.service.commit \
734
+ tx.session.path, tx.mutations, transaction_id: tx.transaction_id
735
+ return Convert.timestamp_to_time commit_resp.commit_timestamp
736
+ rescue Google::Cloud::AbortedError => err
737
+ # Re-raise if deadline has passed
738
+ raise err if current_time - start_time > deadline
739
+ # Sleep the amount from RetryDelay, or incremental backoff
740
+ sleep(delay_from_aborted(err) || backoff *= 1.3)
741
+ # Create new transaction on the session and retry the block
742
+ tx = tx.session.create_transaction
743
+ retry
744
+ rescue => err
745
+ # Rollback transaction when handling unexpected error
746
+ tx.session.rollback tx.transaction_id
747
+ # Return nil if raised with rollback.
748
+ return nil if err.is_a? Rollback
749
+ # Re-raise error.
750
+ raise err
751
+ end
752
+ end
753
+ end
754
+
755
+ ##
756
+ # Creates a snapshot read-only transaction for reads that execute
757
+ # atomically at a single logical point in time across columns, rows, and
758
+ # tables in a database. For transactions that only read, snapshot
759
+ # read-only transactions provide simpler semantics and are almost always
760
+ # faster than read-write transactions.
761
+ #
762
+ # @param [true, false] strong Read at a timestamp where all previously
763
+ # committed transactions are visible.
764
+ # @param [Time, DateTime] timestamp Executes all reads at the given
765
+ # timestamp. Unlike other modes, reads at a specific timestamp are
766
+ # repeatable; the same read at the same timestamp always returns the
767
+ # same data. If the timestamp is in the future, the read will block
768
+ # until the specified timestamp, modulo the read's deadline.
769
+ #
770
+ # Useful for large scale consistent reads such as mapreduces, or for
771
+ # coordinating many reads against a consistent snapshot of the data.
772
+ # (See
773
+ # [TransactionOptions](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#transactionoptions).)
774
+ # @param [Time, DateTime] read_timestamp Same as `timestamp`.
775
+ # @param [Numeric] staleness Executes all reads at a timestamp that is
776
+ # +staleness+ old. The timestamp is chosen soon after the read
777
+ # is started.
778
+ #
779
+ # Guarantees that all writes that have committed more than the
780
+ # specified number of seconds ago are visible. Because Cloud Spanner
781
+ # chooses the exact timestamp, this mode works even if the client's
782
+ # local clock is substantially skewed from Cloud Spanner commit
783
+ # timestamps.
784
+ #
785
+ # Useful for reading at nearby replicas without the distributed
786
+ # timestamp negotiation overhead of single-use +staleness+. (See
787
+ # [TransactionOptions](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#transactionoptions).)
788
+ # @param [Numeric] exact_staleness Same as `staleness`.
789
+ #
790
+ # @yield [snapshot] The block for reading and writing data.
791
+ # @yieldparam [Google::Cloud::Spanner::Snapshot] snapshot The Snapshot
792
+ # object.
793
+ #
794
+ # @example
795
+ # require "google/cloud/spanner"
796
+ #
797
+ # spanner = Google::Cloud::Spanner.new
798
+ # db = spanner.client "my-instance", "my-database"
799
+ #
800
+ # db.snapshot do |snp|
801
+ # results = snp.execute "SELECT * FROM users"
802
+ #
803
+ # results.rows.each do |row|
804
+ # puts "User #{row[:id]} is #{row[:name]}"
805
+ # end
806
+ # end
807
+ #
808
+ def snapshot strong: nil, timestamp: nil, read_timestamp: nil,
809
+ staleness: nil, exact_staleness: nil
810
+ validate_snapshot_args! strong: strong, timestamp: timestamp,
811
+ read_timestamp: read_timestamp,
812
+ staleness: staleness,
813
+ exact_staleness: exact_staleness
814
+ ensure_service!
815
+ @pool.with_session do |session|
816
+ snp_grpc = @project.service.create_snapshot \
817
+ session.path, strong: strong,
818
+ timestamp: (timestamp || read_timestamp),
819
+ staleness: (staleness || exact_staleness)
820
+ snp = Snapshot.from_grpc(snp_grpc, session)
821
+ yield snp if block_given?
822
+ end
823
+ nil
824
+ end
825
+
826
+ ##
827
+ # @private
828
+ # Creates fields object from types.
829
+ #
830
+ # @param [Array, Hash] types Accepts an array of types, array of type
831
+ # pairs, hash of positional types, hash of named types.
832
+ #
833
+ # @return [Fields] The fields of the given types.
834
+ #
835
+ # @example
836
+ # require "google/cloud/spanner"
837
+ #
838
+ # spanner = Google::Cloud::Spanner.new
839
+ #
840
+ # db = spanner.client "my-instance", "my-database"
841
+ # user_fields = db.fields id: :INT64, name: :STRING, active: :BOOL
842
+ #
843
+ # db.update "users", [user_fields.data(1, "Charlie", false),
844
+ # user_fields.data(2, "Harvey", true)]
845
+ #
846
+ def fields types
847
+ Fields.new types
848
+ end
849
+
850
+ ##
851
+ # @private
852
+ # Executes a query to retrieve the field names and types for a table.
853
+ #
854
+ # @param [String] table The name of the table in the database to
855
+ # retrieve fields for.
856
+ #
857
+ # @return [Fields] The fields of the given table.
858
+ #
859
+ # @example
860
+ # require "google/cloud/spanner"
861
+ #
862
+ # spanner = Google::Cloud::Spanner.new
863
+ #
864
+ # db = spanner.client "my-instance", "my-database"
865
+ #
866
+ # users_types = db.fields_for "users"
867
+ # db.insert "users", [{ id: 1, name: "Charlie", active: false },
868
+ # { id: 2, name: "Harvey", active: true }],
869
+ # types: users_types
870
+ #
871
+ def fields_for table
872
+ execute("SELECT * FROM #{table} WHERE 1 = 0").fields
873
+ end
874
+
875
+ ##
876
+ # Creates a Spanner Range. This can be used in place of a Ruby Range
877
+ # when needing to exclude the beginning value.
878
+ #
879
+ # @param [Object] beginning The object that defines the beginning of the
880
+ # range.
881
+ # @param [Object] ending The object that defines the end of the range.
882
+ # @param [Boolean] exclude_begin Determines if the range excludes its
883
+ # beginning value. Default is `false`.
884
+ # @param [Boolean] exclude_end Determines if the range excludes its
885
+ # ending value. Default is `false`.
886
+ #
887
+ # @return [Google::Cloud::Spanner::Range] The new Range instance.
888
+ #
889
+ # @example
890
+ # require "google/cloud/spanner"
891
+ #
892
+ # spanner = Google::Cloud::Spanner.new
893
+ #
894
+ # db = spanner.client "my-instance", "my-database"
895
+ #
896
+ # key_range = db.range 1, 100
897
+ # results = db.read "users", [:id, :name], keys: key_range
898
+ #
899
+ # results.rows.each do |row|
900
+ # puts "User #{row[:id]} is #{row[:name]}"
901
+ # end
902
+ #
903
+ def range beginning, ending, exclude_begin: false, exclude_end: false
904
+ Range.new beginning, ending,
905
+ exclude_begin: exclude_begin,
906
+ exclude_end: exclude_end
907
+ end
908
+
909
+ ##
910
+ # Closes the client connection and releases resources.
911
+ #
912
+ def close
913
+ @pool.close
914
+ end
915
+
916
+ ##
917
+ # @private
918
+ # Creates a new session object every time.
919
+ def create_new_session
920
+ ensure_service!
921
+ grpc = @project.service.create_session \
922
+ Admin::Database::V1::DatabaseAdminClient.database_path(
923
+ project_id, instance_id, database_id)
924
+ Session.from_grpc(grpc, @project.service)
925
+ end
926
+
927
+ # @private
928
+ def to_s
929
+ "(project_id: #{project_id}, instance_id: #{instance_id}, " \
930
+ "database_id: #{database_id})"
931
+ end
932
+
933
+ # @private
934
+ def inspect
935
+ "#<#{self.class.name} #{self}>"
936
+ end
937
+
938
+ protected
939
+
940
+ ##
941
+ # @private Raise an error unless an active connection to the service is
942
+ # available.
943
+ def ensure_service!
944
+ fail "Must have active connection to service" unless @project.service
945
+ end
946
+
947
+ ##
948
+ # Check for valid snapshot arguments
949
+ def validate_single_use_args! opts
950
+ return true if opts.nil? || opts.empty?
951
+ valid_keys = [:strong, :timestamp, :read_timestamp, :staleness,
952
+ :exact_staleness, :bounded_timestamp,
953
+ :min_read_timestamp, :bounded_staleness, :max_staleness]
954
+ if opts.keys.count == 1 && valid_keys.include?(opts.keys.first)
955
+ return true
956
+ end
957
+ fail ArgumentError,
958
+ "Must provide only one of the following single_use values: " \
959
+ "#{valid_keys}"
960
+ end
961
+
962
+ ##
963
+ # Create a single-use TransactionSelector
964
+ def single_use_transaction opts
965
+ return nil if opts.nil? || opts.empty?
966
+
967
+ exact_timestamp = Convert.time_to_timestamp \
968
+ opts[:timestamp] || opts[:read_timestamp]
969
+ exact_staleness = Convert.number_to_duration \
970
+ opts[:staleness] || opts[:exact_staleness]
971
+ bounded_timestamp = Convert.time_to_timestamp \
972
+ opts[:bounded_timestamp] || opts[:min_read_timestamp]
973
+ bounded_staleness = Convert.number_to_duration \
974
+ opts[:bounded_staleness] || opts[:max_staleness]
975
+
976
+ Google::Spanner::V1::TransactionSelector.new(single_use:
977
+ Google::Spanner::V1::TransactionOptions.new(read_only:
978
+ Google::Spanner::V1::TransactionOptions::ReadOnly.new({
979
+ strong: opts[:strong],
980
+ read_timestamp: exact_timestamp,
981
+ exact_staleness: exact_staleness,
982
+ min_read_timestamp: bounded_timestamp,
983
+ max_staleness: bounded_staleness,
984
+ return_read_timestamp: true
985
+ }.delete_if { |_, v| v.nil? })))
986
+ end
987
+
988
+ ##
989
+ # Check for valid snapshot arguments
990
+ def validate_snapshot_args! strong: nil,
991
+ timestamp: nil, read_timestamp: nil,
992
+ staleness: nil, exact_staleness: nil
993
+ valid_args_count = [strong, timestamp, read_timestamp, staleness,
994
+ exact_staleness].compact.count
995
+ return true if valid_args_count <= 1
996
+ fail ArgumentError,
997
+ "Can only provide one of the following arguments: " \
998
+ "(strong, timestamp, read_timestamp, staleness, exact_staleness)"
999
+ end
1000
+
1001
+ def validate_deadline deadline
1002
+ return 120 unless deadline.is_a? Numeric
1003
+ return 120 if deadline < 0
1004
+ deadline
1005
+ end
1006
+
1007
+ ##
1008
+ # Defer to this method so we have something to mock for tests
1009
+ def current_time
1010
+ Time.now
1011
+ end
1012
+
1013
+ ##
1014
+ # Retrieves the delay value from Google::Cloud::AbortedError
1015
+ def delay_from_aborted err
1016
+ return nil if err.nil?
1017
+ if err.respond_to?(:metadata) && err.metadata["retryDelay"]
1018
+ # a correct metadata will look like this:
1019
+ # "{\"retryDelay\":{\"seconds\":60}}"
1020
+ seconds = err.metadata["retryDelay"]["seconds"].to_i
1021
+ nanos = err.metadata["retryDelay"]["nanos"].to_i
1022
+ return seconds if nanos.zero?
1023
+ return seconds + (nanos / 1000000000.0)
1024
+ end
1025
+ # No metadata? Try the inner error
1026
+ delay_from_aborted(err.cause)
1027
+ rescue
1028
+ # Any error indicates the backoff should be handled elsewhere
1029
+ return nil
1030
+ end
1031
+ end
1032
+ end
1033
+ end
1034
+ end