google-cloud-spanner 1.2.0 → 1.3.1

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