google-cloud-spanner 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3ecf7b383ed94a1522a5d90bd7574113f087c5ab
4
- data.tar.gz: c4698851d6e116762e2ea65a3e6e74f252af7060
3
+ metadata.gz: 0d52caffdd789f039f91609da3f8dba633459c82
4
+ data.tar.gz: ecc7cca28368c261123342f43e9b055f13c22d70
5
5
  SHA512:
6
- metadata.gz: 6014ed1d974e1524681604fc4da27ff8404308e7e5998ff6b2aa7c69cfc9d599d41a4312755177c33c4c499cc18c8623ce59fe433cebe30ce0dff96c63c73439
7
- data.tar.gz: a38b63f7ebada70240457881d43d12df69238ad0562f2a9384647c74cf8ddcec34e286e7946e657c55cfe5d09e467e0ae2655fbe32ac89c4ab2cef26e6e2e7c1
6
+ metadata.gz: cfd38cc3172d32788a6891d78e44e349b8410c23285bcaa2bd79b582a58a48ce167f45251fec7ceff4df44b8f9fc6484f83d4feb7d744958ca6d8c99b1f2d457
7
+ data.tar.gz: 41f84f6f1432833bc79ca731f8b99d29ea6edd70bc71ebdc6b34afe7a7b16f0b1709968d2d358aee5ba6df91f052cd6324a53590a6d7c5d8d73e544b42f5b9df
@@ -132,7 +132,7 @@ module Google
132
132
  #
133
133
  # db = spanner.client "my-instance", "my-database"
134
134
  #
135
- # results = client.execute "SELECT 1"
135
+ # results = db.execute "SELECT 1"
136
136
  #
137
137
  # results.rows.each do |row|
138
138
  # puts row
@@ -160,8 +160,8 @@ module Google
160
160
  # db = spanner.client "my-instance", "my-database"
161
161
  #
162
162
  # db.commit do |c|
163
- # c.update "users", [{ id: 1, name: "Charlie", active: false }]
164
- # c.insert "users", [{ id: 2, name: "Harvey", active: true }]
163
+ # c.update "users", [{ id: 1, username: "charlie94", name: "Charlie" }]
164
+ # c.insert "users", [{ id: 2, username: "harvey00", name: "Harvey" }]
165
165
  # end
166
166
  # ```
167
167
  #
@@ -232,7 +232,7 @@ module Google
232
232
  # # Read the second album budget.
233
233
  # second_album_result = tx.read "Albums", ["marketing_budget"],
234
234
  # keys: [[2, 2]], limit: 1
235
- # second_album_row = second_album_result.first
235
+ # second_album_row = second_album_result.rows.first
236
236
  # second_album_budget = second_album_row.values.first
237
237
  #
238
238
  # transfer_amount = 200000
@@ -245,7 +245,7 @@ module Google
245
245
  # # Read the first album's budget.
246
246
  # first_album_result = tx.read "Albums", ["marketing_budget"],
247
247
  # keys: [[1, 1]], limit: 1
248
- # first_album_row = first_album_result.first
248
+ # first_album_row = first_album_result.rows.first
249
249
  # first_album_budget = first_album_row.values.first
250
250
  #
251
251
  # # Update the budgets.
@@ -366,12 +366,16 @@ module Google
366
366
  def self.new project: nil, keyfile: nil, scope: nil, timeout: nil,
367
367
  client_config: nil
368
368
  project ||= Google::Cloud::Spanner::Project.default_project
369
+ project = project.to_s # Always cast to a string
370
+ fail ArgumentError, "project is missing" if project.empty?
371
+
369
372
  if keyfile.nil?
370
373
  credentials = Google::Cloud::Spanner::Credentials.default scope: scope
371
374
  else
372
375
  credentials = Google::Cloud::Spanner::Credentials.new(
373
376
  keyfile, scope: scope)
374
377
  end
378
+
375
379
  Google::Cloud::Spanner::Project.new(
376
380
  Google::Cloud::Spanner::Service.new(
377
381
  project, credentials, timeout: timeout,
@@ -6,9 +6,7 @@
6
6
  "DEADLINE_EXCEEDED",
7
7
  "UNAVAILABLE"
8
8
  ],
9
- "non_idempotent": [
10
- "UNAVAILABLE"
11
- ]
9
+ "non_idempotent": []
12
10
  },
13
11
  "retry_params": {
14
12
  "default": {
@@ -6,9 +6,7 @@
6
6
  "DEADLINE_EXCEEDED",
7
7
  "UNAVAILABLE"
8
8
  ],
9
- "non_idempotent": [
10
- "UNAVAILABLE"
11
- ]
9
+ "non_idempotent": []
12
10
  },
13
11
  "retry_params": {
14
12
  "default": {
@@ -51,13 +51,11 @@ module Google
51
51
  class Client
52
52
  ##
53
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
54
+ def initialize project, instance_id, database_id, opts = {}
56
55
  @project = project
57
56
  @instance_id = instance_id
58
57
  @database_id = database_id
59
- @pool = Pool.new self, min: min, max: max, keepalive: keepalive,
60
- write_ratio: write_ratio, fail: fail
58
+ @pool = Pool.new self, opts
61
59
  end
62
60
 
63
61
  # The unique identifier for the project.
@@ -663,6 +661,9 @@ module Google
663
661
  end
664
662
  end
665
663
 
664
+ # rubocop:disable Metrics/AbcSize
665
+ # rubocop:disable Metrics/MethodLength
666
+
666
667
  ##
667
668
  # Creates a transaction for reads and writes that execute atomically at
668
669
  # a single logical point in time across columns, rows, and tables in a
@@ -699,8 +700,8 @@ module Google
699
700
  # puts "User #{row[:id]} is #{row[:name]}"
700
701
  # end
701
702
  #
702
- # c.update "users", [{ id: 1, name: "Charlie", active: false }]
703
- # c.insert "users", [{ id: 2, name: "Harvey", active: true }]
703
+ # tx.update "users", [{ id: 1, name: "Charlie", active: false }]
704
+ # tx.insert "users", [{ id: 2, name: "Harvey", active: true }]
704
705
  # end
705
706
  #
706
707
  # @example Manually rollback the transaction using {Rollback}:
@@ -710,8 +711,8 @@ module Google
710
711
  # db = spanner.client "my-instance", "my-database"
711
712
  #
712
713
  # db.transaction do |tx|
713
- # c.update "users", [{ id: 1, name: "Charlie", active: false }]
714
- # c.insert "users", [{ id: 2, name: "Harvey", active: true }]
714
+ # tx.update "users", [{ id: 1, name: "Charlie", active: false }]
715
+ # tx.insert "users", [{ id: 2, name: "Harvey", active: true }]
715
716
  #
716
717
  # if something_wrong?
717
718
  # # Rollback the transaction without passing on the error
@@ -722,6 +723,9 @@ module Google
722
723
  #
723
724
  def transaction deadline: 120, &block
724
725
  ensure_service!
726
+ unless Thread.current[:transaction_id].nil?
727
+ fail "Nested transactions are not allowed"
728
+ end
725
729
 
726
730
  deadline = validate_deadline deadline
727
731
  backoff = 1.0
@@ -729,13 +733,19 @@ module Google
729
733
 
730
734
  @pool.with_transaction do |tx|
731
735
  begin
736
+ Thread.current[:transaction_id] = tx.transaction_id
732
737
  block.call tx
733
738
  commit_resp = @project.service.commit \
734
739
  tx.session.path, tx.mutations, transaction_id: tx.transaction_id
735
740
  return Convert.timestamp_to_time commit_resp.commit_timestamp
736
- rescue Google::Cloud::AbortedError => err
741
+ rescue GRPC::Aborted, Google::Cloud::AbortedError => err
737
742
  # Re-raise if deadline has passed
738
- raise err if current_time - start_time > deadline
743
+ if current_time - start_time > deadline
744
+ if err.is_a? GRPC::BadStatus
745
+ err = Google::Cloud::Error.from_error err
746
+ end
747
+ raise err
748
+ end
739
749
  # Sleep the amount from RetryDelay, or incremental backoff
740
750
  sleep(delay_from_aborted(err) || backoff *= 1.3)
741
751
  # Create new transaction on the session and retry the block
@@ -748,10 +758,15 @@ module Google
748
758
  return nil if err.is_a? Rollback
749
759
  # Re-raise error.
750
760
  raise err
761
+ ensure
762
+ Thread.current[:transaction_id] = nil
751
763
  end
752
764
  end
753
765
  end
754
766
 
767
+ # rubocop:enable Metrics/AbcSize
768
+ # rubocop:enable Metrics/MethodLength
769
+
755
770
  ##
756
771
  # Creates a snapshot read-only transaction for reads that execute
757
772
  # atomically at a single logical point in time across columns, rows, and
@@ -811,14 +826,24 @@ module Google
811
826
  read_timestamp: read_timestamp,
812
827
  staleness: staleness,
813
828
  exact_staleness: exact_staleness
829
+
814
830
  ensure_service!
831
+ unless Thread.current[:transaction_id].nil?
832
+ fail "Nested snapshots are not allowed"
833
+ end
834
+
815
835
  @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?
836
+ begin
837
+ snp_grpc = @project.service.create_snapshot \
838
+ session.path, strong: strong,
839
+ timestamp: (timestamp || read_timestamp),
840
+ staleness: (staleness || exact_staleness)
841
+ Thread.current[:transaction_id] = snp_grpc.id
842
+ snp = Snapshot.from_grpc(snp_grpc, session)
843
+ yield snp if block_given?
844
+ ensure
845
+ Thread.current[:transaction_id] = nil
846
+ end
822
847
  end
823
848
  nil
824
849
  end
@@ -1011,7 +1036,8 @@ module Google
1011
1036
  end
1012
1037
 
1013
1038
  ##
1014
- # Retrieves the delay value from Google::Cloud::AbortedError
1039
+ # Retrieves the delay value from Google::Cloud::AbortedError or
1040
+ # GRPC::Aborted
1015
1041
  def delay_from_aborted err
1016
1042
  return nil if err.nil?
1017
1043
  if err.respond_to?(:metadata) && err.metadata["retryDelay"]
@@ -209,48 +209,6 @@ module Google
209
209
  end
210
210
  end
211
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
212
  def number_to_duration number
255
213
  return nil if number.nil?
256
214
 
@@ -72,6 +72,8 @@ module Google
72
72
  # job = spanner.create_database "my-instance",
73
73
  # "my-new-database"
74
74
  #
75
+ # job.done? #=> false
76
+ # job.reload!
75
77
  # job.done? #=> true
76
78
  # database = job.database
77
79
  #
@@ -31,8 +31,8 @@ module Google
31
31
  # db = spanner.client "my-instance", "my-database"
32
32
  #
33
33
  # db.transaction do |tx|
34
- # c.update "users", [{ id: 1, name: "Charlie", active: false }]
35
- # c.insert "users", [{ id: 2, name: "Harvey", active: true }]
34
+ # tx.update "users", [{ id: 1, name: "Charlie", active: false }]
35
+ # tx.insert "users", [{ id: 2, name: "Harvey", active: true }]
36
36
  #
37
37
  # if something_wrong?
38
38
  # # Rollback the transaction without passing on the error
@@ -279,7 +279,7 @@ module Google
279
279
  #
280
280
  # spanner = Google::Cloud::Spanner.new
281
281
  # instance = spanner.instance "my-instance"
282
- # database = instance.database "my-database" #=> nil
282
+ # database = instance.database "my-database" # nil
283
283
  #
284
284
  def database database_id
285
285
  ensure_service!
@@ -78,6 +78,8 @@ module Google
78
78
  # nodes: 5,
79
79
  # labels: { production: :env }
80
80
  #
81
+ # job.done? #=> false
82
+ # job.reload!
81
83
  # job.done? #=> true
82
84
  # instance = job.instance
83
85
  #
@@ -158,20 +158,6 @@ module Google
158
158
  roles[role_name] ||= []
159
159
  end
160
160
 
161
- ##
162
- # Returns a deep copy of the policy.
163
- #
164
- # @return [Policy]
165
- #
166
- def deep_dup
167
- dup.tap do |p|
168
- roles_dup = p.roles.each_with_object({}) do |(k, v), memo|
169
- memo[k] = v.dup rescue value
170
- end
171
- p.instance_variable_set "@roles", roles_dup
172
- end
173
- end
174
-
175
161
  ##
176
162
  # @private Convert the Policy to a Google::Iam::V1::Policy object.
177
163
  def to_grpc
@@ -33,7 +33,7 @@ module Google
33
33
  attr_accessor :all_sessions, :session_queue, :transaction_queue
34
34
 
35
35
  def initialize client, min: 10, max: 100, keepalive: 1800,
36
- write_ratio: 0.3, fail: true
36
+ write_ratio: 0.3, fail: true, threads: nil
37
37
  @client = client
38
38
  @min = min
39
39
  @max = max
@@ -42,6 +42,7 @@ module Google
42
42
  @write_ratio = 0 if write_ratio < 0
43
43
  @write_ratio = 1 if write_ratio > 1
44
44
  @fail = fail
45
+ @threads = threads || [2, Concurrent.processor_count * 2].max
45
46
 
46
47
  @mutex = Mutex.new
47
48
  @resource = ConditionVariable.new
@@ -86,11 +87,11 @@ module Google
86
87
  end
87
88
 
88
89
  def checkin_session session
89
- unless all_sessions.include? session
90
- fail ArgumentError, "Cannot checkin session"
91
- end
92
-
93
90
  @mutex.synchronize do
91
+ unless all_sessions.include? session
92
+ fail ArgumentError, "Cannot checkin session"
93
+ end
94
+
94
95
  session_queue.push session
95
96
 
96
97
  @resource.signal
@@ -144,11 +145,11 @@ module Google
144
145
  end
145
146
 
146
147
  def checkin_transaction tx
147
- unless all_sessions.include? tx.session
148
- fail ArgumentError, "Cannot checkin session"
149
- end
150
-
151
148
  @mutex.synchronize do
149
+ unless all_sessions.include? tx.session
150
+ fail ArgumentError, "Cannot checkin session"
151
+ end
152
+
152
153
  transaction_queue.push tx
153
154
 
154
155
  @resource.signal
@@ -215,10 +216,7 @@ module Google
215
216
 
216
217
  def init
217
218
  # init the thread pool
218
- @thread_pool = Concurrent::FixedThreadPool.new(
219
- [2, Concurrent.processor_count].max * 2,
220
- fallback_policy: :caller_runs
221
- )
219
+ @thread_pool = Concurrent::FixedThreadPool.new @threads
222
220
  # init the queues
223
221
  @new_sessions_in_process = @min.to_i
224
222
  @all_sessions = []
@@ -158,7 +158,7 @@ module Google
158
158
  # require "google/cloud/spanner"
159
159
  #
160
160
  # spanner = Google::Cloud::Spanner.new
161
- # instance = spanner.instance "non-existing" #=> nil
161
+ # instance = spanner.instance "non-existing" # nil
162
162
  #
163
163
  def instance instance_id
164
164
  ensure_service!
@@ -288,7 +288,7 @@ module Google
288
288
  # require "google/cloud/spanner"
289
289
  #
290
290
  # spanner = Google::Cloud::Spanner.new
291
- # config = spanner.instance_config "non-existing" #=> nil
291
+ # config = spanner.instance_config "non-existing" # nil
292
292
  #
293
293
  def instance_config instance_config_id
294
294
  ensure_service!
@@ -355,7 +355,7 @@ module Google
355
355
  # require "google/cloud/spanner"
356
356
  #
357
357
  # spanner = Google::Cloud::Spanner.new
358
- # database = spanner.database "my-instance", "my-database" #=> nil
358
+ # database = spanner.database "my-instance", "my-database" # nil
359
359
  #
360
360
  def database instance_id, database_id
361
361
  ensure_service!
@@ -432,6 +432,8 @@ module Google
432
432
  # {SessionLimitError} when the client has allocated the `max` number
433
433
  # of sessions. When `false` the client blocks until a session
434
434
  # becomes available. The default is `true`.
435
+ # * `:threads` (Integer) The number of threads in the thread pool. The
436
+ # default is twice the number of available CPUs.
435
437
  #
436
438
  # @return [Client] The newly created client.
437
439
  #
@@ -471,7 +473,8 @@ module Google
471
473
 
472
474
  def valid_session_pool_options opts = {}
473
475
  { min: opts[:min], max: opts[:max], keepalive: opts[:keepalive],
474
- write_ratio: opts[:write_ratio], fail: opts[:fail]
476
+ write_ratio: opts[:write_ratio], fail: opts[:fail],
477
+ threads: opts[:threads]
475
478
  }.delete_if { |_k, v| v.nil? }
476
479
  end
477
480
  end
@@ -36,7 +36,7 @@ module Google
36
36
  #
37
37
  # results = db.execute "SELECT * FROM users"
38
38
  #
39
- # results.types.each do |name, type|
39
+ # results.fields.pairs.each do |name, type|
40
40
  # puts "Column #{name} is type {type}"
41
41
  # end
42
42
  #
@@ -272,24 +272,6 @@ module Google
272
272
  end
273
273
  end
274
274
 
275
- def execute_sql session_name, sql, transaction: nil, params: nil
276
- input_params = nil
277
- input_param_types = nil
278
- unless params.nil?
279
- input_param_pairs = Convert.to_query_params params
280
- input_params = Google::Protobuf::Struct.new(
281
- fields: Hash[input_param_pairs.map { |k, v| [k, v.first] }])
282
- input_param_types = Hash[
283
- input_param_pairs.map { |k, v| [k, v.last] }]
284
- end
285
- opts = default_options_from_session session_name
286
- execute do
287
- service.execute_sql \
288
- session_name, sql, transaction: transaction, params: input_params,
289
- param_types: input_param_types, options: opts
290
- end
291
- end
292
-
293
275
  def streaming_execute_sql session_name, sql, transaction: nil,
294
276
  params: nil, types: nil, resume_token: nil
295
277
  input_params = nil
@@ -310,18 +292,6 @@ module Google
310
292
  end
311
293
  end
312
294
 
313
- def read_table session_name, table_name, columns, keys: nil, index: nil,
314
- transaction: nil, limit: nil
315
- columns.map!(&:to_s)
316
- opts = default_options_from_session session_name
317
- execute do
318
- service.read \
319
- session_name, table_name, columns, key_set(keys),
320
- transaction: transaction, index: index, limit: limit,
321
- options: opts
322
- end
323
- end
324
-
325
295
  def streaming_read_table session_name, table_name, columns, keys: nil,
326
296
  index: nil, transaction: nil, limit: nil,
327
297
  resume_token: nil
@@ -38,11 +38,38 @@ module Google
38
38
  # db = spanner.client "my-instance", "my-database"
39
39
  #
40
40
  # db.transaction do |tx|
41
- # results = tx.execute "SELECT * FROM users"
41
+ # # Read the second album budget.
42
+ # second_album_result = tx.read "Albums", ["marketing_budget"],
43
+ # keys: [[2, 2]], limit: 1
44
+ # second_album_row = second_album_result.rows.first
45
+ # second_album_budget = second_album_row.values.first
42
46
  #
43
- # results.rows.each do |row|
44
- # puts "User #{row[:id]} is #{row[:name]}"
47
+ # transfer_amount = 200000
48
+ #
49
+ # if second_album_budget < 300000
50
+ # # Raising an exception will automatically roll back the
51
+ # # transaction.
52
+ # raise "The second album doesn't have enough funds to transfer"
45
53
  # end
54
+ #
55
+ # # Read the first album's budget.
56
+ # first_album_result = tx.read "Albums", ["marketing_budget"],
57
+ # keys: [[1, 1]], limit: 1
58
+ # first_album_row = first_album_result.rows.first
59
+ # first_album_budget = first_album_row.values.first
60
+ #
61
+ # # Update the budgets.
62
+ # second_album_budget -= transfer_amount
63
+ # first_album_budget += transfer_amount
64
+ # puts "Setting first album's budget to #{first_album_budget} and " \
65
+ # "the second album's budget to #{second_album_budget}."
66
+ #
67
+ # # Update the rows.
68
+ # rows = [
69
+ # {singer_id: 1, album_id: 1, marketing_budget: first_album_budget},
70
+ # {singer_id: 2, album_id: 2, marketing_budget: second_album_budget}
71
+ # ]
72
+ # tx.update "Albums", rows
46
73
  # end
47
74
  #
48
75
  class Transaction
@@ -423,7 +450,7 @@ module Google
423
450
  # db = spanner.client "my-instance", "my-database"
424
451
  #
425
452
  # db.transaction do |tx|
426
- # users_types = rx.fields_for "users"
453
+ # users_types = tx.fields_for "users"
427
454
  # tx.insert "users", [{ id: 1, name: "Charlie", active: false },
428
455
  # { id: 2, name: "Harvey", active: true }],
429
456
  # types: users_types
@@ -6,9 +6,7 @@
6
6
  "DEADLINE_EXCEEDED",
7
7
  "UNAVAILABLE"
8
8
  ],
9
- "non_idempotent": [
10
- "UNAVAILABLE"
11
- ]
9
+ "non_idempotent": []
12
10
  },
13
11
  "retry_params": {
14
12
  "default": {
@@ -16,7 +16,7 @@
16
16
  module Google
17
17
  module Cloud
18
18
  module Spanner
19
- VERSION = "0.21.0"
19
+ VERSION = "0.22.0"
20
20
  end
21
21
  end
22
22
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-cloud-spanner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Moore
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-06-08 00:00:00.000000000 Z
12
+ date: 2017-07-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: google-cloud-core
@@ -197,16 +197,16 @@ dependencies:
197
197
  name: yard-doctest
198
198
  requirement: !ruby/object:Gem::Requirement
199
199
  requirements:
200
- - - "~>"
200
+ - - "<="
201
201
  - !ruby/object:Gem::Version
202
- version: 0.1.6
202
+ version: 0.1.8
203
203
  type: :development
204
204
  prerelease: false
205
205
  version_requirements: !ruby/object:Gem::Requirement
206
206
  requirements:
207
- - - "~>"
207
+ - - "<="
208
208
  - !ruby/object:Gem::Version
209
- version: 0.1.6
209
+ version: 0.1.8
210
210
  description: google-cloud-spanner is the official library for Google Cloud Spanner
211
211
  API.
212
212
  email: