google-cloud-spanner 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/google-cloud-spanner.rb +25 -0
  3. data/lib/google/cloud/spanner.rb +71 -11
  4. data/lib/google/cloud/spanner/batch_client.rb +355 -0
  5. data/lib/google/cloud/spanner/batch_snapshot.rb +588 -0
  6. data/lib/google/cloud/spanner/client.rb +27 -20
  7. data/lib/google/cloud/spanner/commit.rb +6 -5
  8. data/lib/google/cloud/spanner/convert.rb +39 -0
  9. data/lib/google/cloud/spanner/credentials.rb +7 -7
  10. data/lib/google/cloud/spanner/data.rb +5 -5
  11. data/lib/google/cloud/spanner/database.rb +13 -7
  12. data/lib/google/cloud/spanner/database/job.rb +7 -2
  13. data/lib/google/cloud/spanner/database/list.rb +1 -1
  14. data/lib/google/cloud/spanner/fields.rb +16 -12
  15. data/lib/google/cloud/spanner/instance.rb +25 -13
  16. data/lib/google/cloud/spanner/instance/config.rb +2 -2
  17. data/lib/google/cloud/spanner/instance/config/list.rb +1 -1
  18. data/lib/google/cloud/spanner/instance/job.rb +7 -2
  19. data/lib/google/cloud/spanner/instance/list.rb +1 -1
  20. data/lib/google/cloud/spanner/partition.rb +208 -0
  21. data/lib/google/cloud/spanner/pool.rb +6 -6
  22. data/lib/google/cloud/spanner/project.rb +59 -16
  23. data/lib/google/cloud/spanner/results.rb +12 -5
  24. data/lib/google/cloud/spanner/service.rb +85 -61
  25. data/lib/google/cloud/spanner/session.rb +45 -9
  26. data/lib/google/cloud/spanner/snapshot.rb +10 -2
  27. data/lib/google/cloud/spanner/status.rb +6 -5
  28. data/lib/google/cloud/spanner/transaction.rb +11 -3
  29. data/lib/google/cloud/spanner/v1/doc/google/protobuf/duration.rb +1 -1
  30. data/lib/google/cloud/spanner/v1/doc/google/protobuf/struct.rb +1 -1
  31. data/lib/google/cloud/spanner/v1/doc/google/protobuf/timestamp.rb +1 -1
  32. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/keys.rb +1 -1
  33. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/mutation.rb +1 -1
  34. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/query_plan.rb +1 -1
  35. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/result_set.rb +1 -1
  36. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/spanner.rb +138 -6
  37. data/lib/google/cloud/spanner/v1/doc/google/spanner/v1/transaction.rb +1 -1
  38. data/lib/google/cloud/spanner/v1/doc/overview.rb +6 -5
  39. data/lib/google/cloud/spanner/v1/spanner_client.rb +257 -33
  40. data/lib/google/cloud/spanner/v1/spanner_client_config.json +10 -0
  41. data/lib/google/cloud/spanner/version.rb +1 -1
  42. data/lib/google/spanner/v1/spanner_pb.rb +35 -0
  43. data/lib/google/spanner/v1/spanner_services_pb.rb +20 -2
  44. metadata +12 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fe7bf5ea7f7d653d908671aa73e72ae3f2916e045daace8dafcfdf13a56a886b
4
- data.tar.gz: 20347421634b0f6e1ece36c787f62bb99bf5388b2ae2f873f06f38cc79dda647
3
+ metadata.gz: 3ed2386d9ad779724db65c4f41c42e78271e5cd94fe6785bb576083057b8cfae
4
+ data.tar.gz: 19a748cfaf89324614928996273a424aa0416d3804c60aa86eb6404736f2c30e
5
5
  SHA512:
6
- metadata.gz: ec44b85b8bd9455526338e7c5f41872755ebbc1427110bee0c346b1dbcd2a1311aa867e02b08f495503f85306f30360afc3f8896ea5b0318bfc74740cb346db8
7
- data.tar.gz: '08df6524da2309df130b8b4ad12ef3ed8a736511087d8bb2b1164146a332c6056136a65bb7a792ce03f8c24bb55884837b60d15111007883d9fe75547f53f114'
6
+ metadata.gz: bd287ff170bdd0c48aad7407b8a7cf92af5e0cc1479396a477c579e01d15de61c5f8d1e935fd54bff9ed035f35f66788aaec17b26a96695374d203644702fa88
7
+ data.tar.gz: 4fe37f4d9e0b166a941fb631704e43e54a698e1eba6b74d7c16b2afc7fc6dc9e5b39e947e3f3a99d2035302f8eb2949913fca2e35fb922942f2f74599c125ecf
@@ -20,6 +20,8 @@
20
20
 
21
21
  gem "google-cloud-core"
22
22
  require "google/cloud"
23
+ require "google/cloud/config"
24
+ require "googleauth"
23
25
 
24
26
  module Google
25
27
  module Cloud
@@ -107,3 +109,26 @@ module Google
107
109
  end
108
110
  end
109
111
  end
112
+
113
+ # Set the default spanner configuration
114
+ Google::Cloud.configure.add_config! :spanner do |config|
115
+ default_project = Google::Cloud::Config.deferred do
116
+ ENV["SPANNER_PROJECT"]
117
+ end
118
+ default_creds = Google::Cloud::Config.deferred do
119
+ Google::Cloud::Config.credentials_from_env(
120
+ "SPANNER_CREDENTIALS", "SPANNER_CREDENTIALS_JSON",
121
+ "SPANNER_KEYFILE", "SPANNER_KEYFILE_JSON"
122
+ )
123
+ end
124
+
125
+ config.add_field! :project_id, default_project, match: String, allow_nil: true
126
+ config.add_alias! :project, :project_id
127
+ config.add_field! :credentials, default_creds,
128
+ match: [String, Hash, Google::Auth::Credentials],
129
+ allow_nil: true
130
+ config.add_alias! :keyfile, :credentials
131
+ config.add_field! :scope, nil, match: [String, Array]
132
+ config.add_field! :timeout, nil, match: Integer
133
+ config.add_field! :client_config, nil, match: Hash
134
+ end
@@ -15,6 +15,8 @@
15
15
 
16
16
  require "google-cloud-spanner"
17
17
  require "google/cloud/spanner/project"
18
+ require "google/cloud/config"
19
+ require "google/cloud/env"
18
20
 
19
21
  module Google
20
22
  module Cloud
@@ -30,11 +32,12 @@ module Google
30
32
  # Spanner Documentation](https://cloud.google.com/spanner/docs/).
31
33
  #
32
34
  # The goal of google-cloud is to provide an API that is comfortable to
33
- # Rubyists. Authentication is handled by {Google::Cloud#spanner}. You can
34
- # provide the project and credential information to connect to the Cloud
35
- # Spanner service, or if you are running on Google Compute Engine this
36
- # configuration is taken care of for you. You can read more about the
37
- # options for connecting in the [Authentication
35
+ # Rubyists. Your authentication credentials are detected automatically in
36
+ # Google Cloud Platform environments such as Google Compute Engine, Google
37
+ # App Engine and Google Kubernetes Engine. In other environments you can
38
+ # configure authentication easily, either directly in your code or via
39
+ # environment variables. Read more about the options for connecting in the
40
+ # [Authentication
38
41
  # Guide](https://googlecloudplatform.github.io/google-cloud-ruby/#/docs/guides/authentication).
39
42
  #
40
43
  # ## Creating instances
@@ -61,7 +64,12 @@ module Google
61
64
  # job.done? #=> false
62
65
  # job.reload! # API call
63
66
  # job.done? #=> true
64
- # instance = job.instance
67
+ #
68
+ # if job.error?
69
+ # status = job.error
70
+ # else
71
+ # instance = job.instance
72
+ # end
65
73
  # ```
66
74
  #
67
75
  # ## Creating databases
@@ -83,7 +91,12 @@ module Google
83
91
  # job.done? #=> false
84
92
  # job.reload! # API call
85
93
  # job.done? #=> true
86
- # database = job.database
94
+ #
95
+ # if job.error?
96
+ # status = job.error
97
+ # else
98
+ # database = job.database
99
+ # end
87
100
  # ```
88
101
  #
89
102
  # ## Updating database schemas
@@ -370,11 +383,14 @@ module Google
370
383
  #
371
384
  def self.new project_id: nil, credentials: nil, scope: nil, timeout: nil,
372
385
  client_config: nil, project: nil, keyfile: nil
373
- project_id ||= (project || Spanner::Project.default_project_id)
386
+ project_id ||= (project || default_project_id)
374
387
  project_id = project_id.to_s # Always cast to a string
375
- fail ArgumentError, "project_id is missing" if project_id.empty?
388
+ raise ArgumentError, "project_id is missing" if project_id.empty?
376
389
 
377
- credentials ||= (keyfile || Spanner::Credentials.default(scope: scope))
390
+ scope ||= configure.scope
391
+ timeout ||= configure.timeout
392
+ client_config ||= configure.client_config
393
+ credentials ||= (keyfile || default_credentials(scope: scope))
378
394
  unless credentials.is_a? Google::Auth::Credentials
379
395
  credentials = Spanner::Credentials.new credentials, scope: scope
380
396
  end
@@ -382,7 +398,51 @@ module Google
382
398
  Spanner::Project.new(
383
399
  Spanner::Service.new(
384
400
  project_id, credentials, timeout: timeout,
385
- client_config: client_config))
401
+ client_config: client_config
402
+ )
403
+ )
404
+ end
405
+
406
+ ##
407
+ # Configure the Google Cloud Spanner library.
408
+ #
409
+ # The following Spanner configuration parameters are supported:
410
+ #
411
+ # * `project_id` - (String) Identifier for a Spanner project. (The
412
+ # parameter `project` is considered deprecated, but may also be used.)
413
+ # * `credentials` - (String, Hash, Google::Auth::Credentials) The path to
414
+ # the keyfile as a String, the contents of the keyfile as a Hash, or a
415
+ # Google::Auth::Credentials object. (See {Spanner::Credentials}) (The
416
+ # parameter `keyfile` is considered deprecated, but may also be used.)
417
+ # * `scope` - (String, Array<String>) The OAuth 2.0 scopes controlling
418
+ # the set of resources and operations that the connection can access.
419
+ # * `timeout` - (Integer) Default timeout to use in requests.
420
+ # * `client_config` - (Hash) A hash of values to override the default
421
+ # behavior of the API client.
422
+ #
423
+ # @return [Google::Cloud::Config] The configuration object the
424
+ # Google::Cloud::Spanner library uses.
425
+ #
426
+ def self.configure
427
+ yield Google::Cloud.configure.spanner if block_given?
428
+
429
+ Google::Cloud.configure.spanner
430
+ end
431
+
432
+ ##
433
+ # @private Default project.
434
+ def self.default_project_id
435
+ Google::Cloud.configure.spanner.project_id ||
436
+ Google::Cloud.configure.project_id ||
437
+ Google::Cloud.env.project_id
438
+ end
439
+
440
+ ##
441
+ # @private Default credentials.
442
+ def self.default_credentials scope: nil
443
+ Google::Cloud.configure.spanner.credentials ||
444
+ Google::Cloud.configure.credentials ||
445
+ Spanner::Credentials.default(scope: scope)
386
446
  end
387
447
  end
388
448
  end
@@ -0,0 +1,355 @@
1
+ # Copyright 2018 Google LLC
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
+ # https://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/session"
19
+ require "google/cloud/spanner/batch_snapshot"
20
+
21
+ module Google
22
+ module Cloud
23
+ module Spanner
24
+ ##
25
+ # # BatchClient
26
+ #
27
+ # Provides a batch client that can be used to read data from a Cloud
28
+ # Spanner database. An instance of this class is tied to a specific
29
+ # database.
30
+ #
31
+ # BatchClient is useful when one wants to read or query a large amount of
32
+ # data from Cloud Spanner across multiple processes, even across different
33
+ # machines. It allows to create partitions of Cloud Spanner database and
34
+ # then read or query over each partition independently yet at the same
35
+ # snapshot.
36
+ #
37
+ # See {Google::Cloud::Spanner::Project#batch_client}.
38
+ #
39
+ # @example
40
+ # require "google/cloud/spanner"
41
+ #
42
+ # spanner = Google::Cloud::Spanner.new
43
+ #
44
+ # batch_client = spanner.batch_client "my-instance", "my-database"
45
+ #
46
+ # batch_snapshot = batch_client.batch_snapshot
47
+ #
48
+ # partitions = batch_snapshot.partition_read "users", [:id, :name]
49
+ #
50
+ # partition = partitions.first
51
+ #
52
+ # serialized_snapshot = batch_snapshot.dump
53
+ # serialized_partition = partition.dump
54
+ #
55
+ # # In a separate process
56
+ # new_batch_snapshot = batch_client.load_batch_snapshot \
57
+ # serialized_snapshot
58
+ #
59
+ # new_partition = batch_client.load_partition \
60
+ # serialized_partition
61
+ #
62
+ # results = new_batch_snapshot.execute_partition \
63
+ # new_partition
64
+ #
65
+ class BatchClient
66
+ ##
67
+ # @private Creates a new Spanner BatchClient instance.
68
+ def initialize project, instance_id, database_id
69
+ @project = project
70
+ @instance_id = instance_id
71
+ @database_id = database_id
72
+ end
73
+
74
+ # The unique identifier for the project.
75
+ # @return [String]
76
+ def project_id
77
+ @project.service.project
78
+ end
79
+
80
+ # The unique identifier for the instance.
81
+ # @return [String]
82
+ def instance_id
83
+ @instance_id
84
+ end
85
+
86
+ # The unique identifier for the database.
87
+ # @return [String]
88
+ def database_id
89
+ @database_id
90
+ end
91
+
92
+ # The Spanner project connected to.
93
+ # @return [Project]
94
+ def project
95
+ @project
96
+ end
97
+
98
+ # The Spanner instance connected to.
99
+ # @return [Instance]
100
+ def instance
101
+ @project.instance instance_id
102
+ end
103
+
104
+ # The Spanner database connected to.
105
+ # @return [Database]
106
+ def database
107
+ @project.database instance_id, database_id
108
+ end
109
+
110
+ ##
111
+ # Returns a {BatchSnapshot} context in which multiple reads and/or
112
+ # queries can be performed. All reads/queries will use the same
113
+ # timestamp, and the timestamp can be inspected after this transaction
114
+ # is created successfully. This is a blocking method since it waits to
115
+ # finish the RPCs.
116
+ #
117
+ # @param [true, false] strong Read at a timestamp where all previously
118
+ # committed transactions are visible.
119
+ # @param [Time, DateTime] timestamp Executes all reads at the given
120
+ # timestamp. Unlike other modes, reads at a specific timestamp are
121
+ # repeatable; the same read at the same timestamp always returns the
122
+ # same data. If the timestamp is in the future, the read will block
123
+ # until the specified timestamp, modulo the read's deadline.
124
+ #
125
+ # Useful for large scale consistent reads such as mapreduces, or for
126
+ # coordinating many reads against a consistent snapshot of the data.
127
+ # (See
128
+ # [TransactionOptions](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#transactionoptions).)
129
+ # @param [Time, DateTime] read_timestamp Same as `timestamp`.
130
+ # @param [Numeric] staleness Executes all reads at a timestamp that is
131
+ # `staleness` seconds old. For example, the number 10.1 is translated
132
+ # to 10 seconds and 100 milliseconds.
133
+ #
134
+ # Guarantees that all writes that have committed more than the
135
+ # specified number of seconds ago are visible. Because Cloud Spanner
136
+ # chooses the exact timestamp, this mode works even if the client's
137
+ # local clock is substantially skewed from Cloud Spanner commit
138
+ # timestamps.
139
+ #
140
+ # Useful for reading at nearby replicas without the distributed
141
+ # timestamp negotiation overhead of single-use `staleness`. (See
142
+ # [TransactionOptions](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.v1#transactionoptions).)
143
+ # @param [Numeric] exact_staleness Same as `staleness`.
144
+ #
145
+ # @yield [snapshot] The block for reading and writing data.
146
+ # @yieldparam [Google::Cloud::Spanner::Snapshot] snapshot The Snapshot
147
+ # object.
148
+ #
149
+ # @return [Google::Cloud::Spanner::BatchSnapshot]
150
+ #
151
+ # @example
152
+ # require "google/cloud/spanner"
153
+ #
154
+ # spanner = Google::Cloud::Spanner.new
155
+ #
156
+ # batch_client = spanner.batch_client "my-instance", "my-database"
157
+ #
158
+ # batch_snapshot = batch_client.batch_snapshot
159
+ #
160
+ # partitions = batch_snapshot.partition_read "users", [:id, :name]
161
+ #
162
+ # partition = partitions.first
163
+ #
164
+ # serialized_snapshot = batch_snapshot.dump
165
+ # serialized_partition = partition.dump
166
+ #
167
+ # # In a separate process
168
+ # new_batch_snapshot = batch_client.load_batch_snapshot \
169
+ # serialized_snapshot
170
+ #
171
+ # new_partition = batch_client.load_partition \
172
+ # serialized_partition
173
+ #
174
+ # results = new_batch_snapshot.execute_partition \
175
+ # new_partition
176
+ #
177
+ def batch_snapshot strong: nil, timestamp: nil, read_timestamp: nil,
178
+ staleness: nil, exact_staleness: nil
179
+ validate_snapshot_args! strong: strong, timestamp: timestamp,
180
+ read_timestamp: read_timestamp,
181
+ staleness: staleness,
182
+ exact_staleness: exact_staleness
183
+
184
+ ensure_service!
185
+ snp_session = session
186
+ snp_grpc = @project.service.create_snapshot \
187
+ snp_session.path, strong: strong,
188
+ timestamp: (timestamp || read_timestamp),
189
+ staleness: (staleness || exact_staleness)
190
+ BatchSnapshot.from_grpc snp_grpc, snp_session
191
+ end
192
+
193
+ ##
194
+ # Returns a {BatchSnapshot} context in which multiple reads and/or
195
+ # queries can be performed. All reads/queries will use the same
196
+ # timestamp, and the timestamp can be inspected after this transaction
197
+ # is created successfully. This method does not perform an RPC.
198
+ #
199
+ # @param [String] serialized_snapshot The serialized representation of
200
+ # an existing batch snapshot. See {BatchSnapshot#dump}.
201
+ #
202
+ # @return [Google::Cloud::Spanner::BatchSnapshot]
203
+ #
204
+ # @example
205
+ # require "google/cloud/spanner"
206
+ #
207
+ # spanner = Google::Cloud::Spanner.new
208
+ #
209
+ # batch_client = spanner.batch_client "my-instance", "my-database"
210
+ #
211
+ # batch_snapshot = batch_client.batch_snapshot
212
+ #
213
+ # partitions = batch_snapshot.partition_read "users", [:id, :name]
214
+ #
215
+ # partition = partitions.first
216
+ #
217
+ # serialized_snapshot = batch_snapshot.dump
218
+ # serialized_partition = partition.dump
219
+ #
220
+ # # In a separate process
221
+ # new_batch_snapshot = batch_client.load_batch_snapshot \
222
+ # serialized_snapshot
223
+ #
224
+ # new_partition = batch_client.load_partition \
225
+ # serialized_partition
226
+ #
227
+ # results = new_batch_snapshot.execute_partition \
228
+ # new_partition
229
+ #
230
+ def load_batch_snapshot serialized_snapshot
231
+ ensure_service!
232
+
233
+ BatchSnapshot.load serialized_snapshot, service: @project.service
234
+ end
235
+
236
+ ##
237
+ # Returns a {Partition} from a serialized representation. See
238
+ # {Partition.load}.
239
+ #
240
+ # @param [String] serialized_partition The serialized representation of
241
+ # an existing batch partition. See {Partition#dump}.
242
+ #
243
+ # @return [Google::Cloud::Spanner::Partition]
244
+ #
245
+ # @example
246
+ # require "google/cloud/spanner"
247
+ #
248
+ # spanner = Google::Cloud::Spanner.new
249
+ #
250
+ # batch_client = spanner.batch_client "my-instance", "my-database"
251
+ #
252
+ # batch_snapshot = batch_client.batch_snapshot
253
+ #
254
+ # partitions = batch_snapshot.partition_read "users", [:id, :name]
255
+ #
256
+ # partition = partitions.first
257
+ #
258
+ # serialized_snapshot = batch_snapshot.dump
259
+ # serialized_partition = partition.dump
260
+ #
261
+ # # In a separate process
262
+ # new_batch_snapshot = batch_client.load_batch_snapshot \
263
+ # serialized_snapshot
264
+ #
265
+ # new_partition = batch_client.load_partition \
266
+ # serialized_partition
267
+ #
268
+ # results = new_batch_snapshot.execute_partition \
269
+ # new_partition
270
+ #
271
+ def load_partition serialized_partition
272
+ Partition.load serialized_partition
273
+ end
274
+
275
+ ##
276
+ # Creates a Spanner Range. This can be used in place of a Ruby Range
277
+ # when needing to exclude the beginning value.
278
+ #
279
+ # @param [Object] beginning The object that defines the beginning of the
280
+ # range.
281
+ # @param [Object] ending The object that defines the end of the range.
282
+ # @param [Boolean] exclude_begin Determines if the range excludes its
283
+ # beginning value. Default is `false`.
284
+ # @param [Boolean] exclude_end Determines if the range excludes its
285
+ # ending value. Default is `false`.
286
+ #
287
+ # @return [Google::Cloud::Spanner::Range] The new Range instance.
288
+ #
289
+ # @example
290
+ # require "google/cloud/spanner"
291
+ #
292
+ # spanner = Google::Cloud::Spanner.new
293
+ #
294
+ # batch_client = spanner.batch_client "my-instance", "my-database"
295
+ # batch_snapshot = batch_client.batch_snapshot
296
+ #
297
+ # key_range = batch_client.range 1, 100
298
+ #
299
+ # partitions = batch_snapshot.partition_read "users", [:id, :name],
300
+ # keys: key_range
301
+ #
302
+ def range beginning, ending, exclude_begin: false, exclude_end: false
303
+ Range.new beginning, ending,
304
+ exclude_begin: exclude_begin,
305
+ exclude_end: exclude_end
306
+ end
307
+
308
+ # @private
309
+ def to_s
310
+ "(project_id: #{project_id}, instance_id: #{instance_id}, " \
311
+ "database_id: #{database_id})"
312
+ end
313
+
314
+ # @private
315
+ def inspect
316
+ "#<#{self.class.name} #{self}>"
317
+ end
318
+
319
+ protected
320
+
321
+ ##
322
+ # @private Raise an error unless an active connection to the service is
323
+ # available.
324
+ def ensure_service!
325
+ raise "Must have active connection to service" unless @project.service
326
+ end
327
+
328
+ ##
329
+ # New session for each use.
330
+ def session
331
+ ensure_service!
332
+ grpc = @project.service.create_session \
333
+ Admin::Database::V1::DatabaseAdminClient.database_path(
334
+ project_id, instance_id, database_id
335
+ )
336
+ Session.from_grpc(grpc, @project.service)
337
+ end
338
+
339
+ ##
340
+ # Check for valid snapshot arguments
341
+ def validate_snapshot_args! strong: nil,
342
+ timestamp: nil, read_timestamp: nil,
343
+ staleness: nil, exact_staleness: nil
344
+ valid_args_count = [strong, timestamp, read_timestamp, staleness,
345
+ exact_staleness].compact.count
346
+ return true if valid_args_count <= 1
347
+ raise ArgumentError,
348
+ "Can only provide one of the following arguments: " \
349
+ "(strong, timestamp, read_timestamp, staleness, " \
350
+ "exact_staleness)"
351
+ end
352
+ end
353
+ end
354
+ end
355
+ end