google-cloud-spanner 2.26.0 → 2.27.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
  SHA256:
3
- metadata.gz: cafab317421fcff06f127fb2d156c722f67b8e5d1914ae68d3e0ec8b56db4f76
4
- data.tar.gz: 26820cba11c9d0a94d5dbaaff4c28ea0581c25ef3d7a9876d39ef5ca42fe5edc
3
+ metadata.gz: 2950745d1ef3cf6be982e2bc64a29ce467e8a15710293265181d41dd138a80ad
4
+ data.tar.gz: 6e96c531e76300e6d77d5d261ec0d09da96157f82aa3f4c32c502b52ce6b6f74
5
5
  SHA512:
6
- metadata.gz: 58624fe569f909d5923f897fc02c6865c4861dd289ec5b1f58aead9ae01b87968ebd7b5e23d0305e4661011fd4bc2e3479cc08c08ef0c19b320daa2e68300fca
7
- data.tar.gz: c011acec4ad559505de0b89e77dae6d2046f7e00768a421f4bd347ff25a3b8ee35673f1714037528e1d13572e054486ded0fd666d253df9b322a2c90ef19a1bf
6
+ metadata.gz: 55291fac48fb628c64d4df17266cf1a07babbd918b8411e7116e8ba23b0430c20dbf0f210098f27052bbcd9d97c9e3ed0ec2937a4c342cb5994946df2dcf2ac6
7
+ data.tar.gz: b2e91d7c79a316a69b3657eace1232936a13013236117170b974a9487e0d9fdd5c9de34bba851ffbe310a26c6864954fd94de68df91fdb1a9d64bd06773d5aff
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Release History
2
2
 
3
+ ### 2.27.0 (2025-05-28)
4
+
5
+ #### Features
6
+
7
+ * Spanner Interval type ([#162](https://github.com/googleapis/ruby-spanner/issues/162))
8
+ * Updated required Ruby version to 3.1 ([#160](https://github.com/googleapis/ruby-spanner/issues/160))
9
+
3
10
  ### 2.26.0 (2025-03-24)
4
11
 
5
12
  #### Features
@@ -92,9 +92,9 @@ module Google
92
92
  #
93
93
  # @yield [::Google::Cloud::Spanner::BatchWriteResults::BatchResult]
94
94
  #
95
- def each &block
95
+ def each(&)
96
96
  if defined? @results
97
- @results.each(&block)
97
+ @results.each(&)
98
98
  else
99
99
  results = []
100
100
  @enumerable.each do |grpc|
@@ -19,6 +19,7 @@ require "stringio"
19
19
  require "base64"
20
20
  require "bigdecimal"
21
21
  require "google/cloud/spanner/data"
22
+ require "google/cloud/spanner/interval"
22
23
 
23
24
  module Google
24
25
  module Cloud
@@ -108,6 +109,8 @@ module Google
108
109
  else
109
110
  Google::Protobuf::Value.new string_value: obj.to_json
110
111
  end
112
+ when Interval
113
+ obj.to_s
111
114
  when Google::Protobuf::MessageExts
112
115
  proto_class = obj.class
113
116
  content = proto_class.encode obj
@@ -252,6 +255,8 @@ module Google
252
255
  BigDecimal value.string_value
253
256
  when :JSON
254
257
  JSON.parse value.string_value
258
+ when :INTERVAL
259
+ Interval.parse value.string_value
255
260
  when :PROTO
256
261
  descriptor = Google::Protobuf::DescriptorPool.generated_pool.lookup(type.proto_type_fqn).msgclass
257
262
  content = Base64.decode64 value.string_value
@@ -0,0 +1,309 @@
1
+ # Copyright 2017 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
+ module Google
16
+ module Cloud
17
+ module Spanner
18
+ ##
19
+ # # Interval
20
+ #
21
+ # Represents an interval of time by storing the time components
22
+ # in months, days and nanoseconds.
23
+ #
24
+ # @example
25
+ # require "google/cloud/spanner"
26
+ #
27
+ # iso_8601_string = "P1Y2M3DT4H5M6S"
28
+ # interval = Google::Cloud::Spanner::Interval::parse iso_8601_string
29
+ #
30
+ # puts interval # "P1Y2M3DT4H5M6S"
31
+ class Interval
32
+ NANOSECONDS_IN_A_SECOND = 1_000_000_000
33
+ NANOSECONDS_IN_A_MINUTE = NANOSECONDS_IN_A_SECOND * 60
34
+ NANOSECONDS_IN_AN_HOUR = NANOSECONDS_IN_A_MINUTE * 60
35
+ NANOSECONDS_IN_A_MILLISECOND = 1_000_000
36
+ NANOSECONDS_IN_A_MICROSECOND = 1_000
37
+ MAX_MONTHS = 120_000
38
+ MIN_MONTHS = -MAX_MONTHS
39
+ MAX_DAYS = 3_660_000
40
+ MIN_DAYS = -MAX_DAYS
41
+ MAX_NANOSECONDS = 316_224_000_000_000_000_000
42
+ MIN_NANOSECONDS = -316_224_000_000_000_000_000
43
+
44
+ private_constant :NANOSECONDS_IN_A_SECOND, :NANOSECONDS_IN_A_MINUTE, :NANOSECONDS_IN_AN_HOUR,
45
+ :NANOSECONDS_IN_A_MILLISECOND, :NANOSECONDS_IN_A_MICROSECOND, :MAX_MONTHS,
46
+ :MIN_MONTHS, :MAX_DAYS, :MIN_DAYS, :MAX_NANOSECONDS, :MIN_NANOSECONDS
47
+
48
+ class << self
49
+ # rubocop:disable Metrics/AbcSize
50
+ # rubocop:disable Metrics/MethodLength
51
+
52
+ # Parses an ISO8601 string and returns an Interval instance.
53
+ #
54
+ # The accepted format for the ISO8601 standard is:
55
+ # `P[n]Y[n]M[n]DT[n]H[n]M[n[.fraction]]S`
56
+ # where `n` represents an integer number.
57
+ #
58
+ # @param interval_string [String] An ISO8601 formatted string.
59
+ # @return [Google::Cloud::Spanner::Interval]
60
+ #
61
+ # @example
62
+ # require "google/cloud/spanner"
63
+ #
64
+ # iso_8601_string = "P1Y2M3DT4H5M6S"
65
+ # interval = Google::Cloud::Spanner::Interval::parse iso_8601_string
66
+ #
67
+ # puts interval # "P1Y2M3DT4H5M6S"
68
+ #
69
+ def parse interval_string
70
+ pattern = /^
71
+ P(?!$)
72
+ (?:(?<years>-?\d+)Y)?
73
+ (?:(?<months>-?\d+)M)?
74
+ (?:(?<days>-?\d+)D)?
75
+ (?:T(?!$)
76
+ (?:(?<hours>-?\d+)H)?
77
+ (?:(?<minutes>-?\d+)M)?
78
+ (?:(?<seconds>-?(?!S)\d*(?:[\.,]\d{1,9})?)S)?)?
79
+ $
80
+ /x
81
+ interval_months = 0
82
+ interval_days = 0
83
+ interval_nanoseconds = 0
84
+
85
+ matches = interval_string.match pattern
86
+
87
+ raise ArgumentError, "The provided string does not follow ISO8601 standard." if matches.nil?
88
+
89
+ raise ArgumentError, "The provided string does not follow ISO8601 standard." if matches.captures.empty?
90
+
91
+ interval_months += matches[:years].to_i * 12 if matches[:years]
92
+
93
+ interval_months += matches[:months].to_i if matches[:months]
94
+
95
+ interval_days = matches[:days].to_i if matches[:days]
96
+
97
+ interval_nanoseconds += matches[:hours].to_i * NANOSECONDS_IN_AN_HOUR if matches[:hours]
98
+
99
+ interval_nanoseconds += matches[:minutes].to_i * NANOSECONDS_IN_A_MINUTE if matches[:minutes]
100
+
101
+ # Only seconds can be fractional. Both period and comma are valid inputs.
102
+ if matches[:seconds]
103
+ interval_nanoseconds += (matches[:seconds].gsub(",", ".").to_f * NANOSECONDS_IN_A_SECOND).to_i
104
+ end
105
+
106
+ Interval.new interval_months, interval_days, interval_nanoseconds
107
+ end
108
+
109
+ # rubocop:enable Metrics/AbcSize
110
+ # rubocop:enable Metrics/MethodLength
111
+
112
+ # Returns an Interval instance with the given months.
113
+ #
114
+ # @param months [Integer]
115
+ # @return [Interval]
116
+ def from_months months
117
+ Interval.new months, 0, 0
118
+ end
119
+
120
+ # Returns an Interval instance with the given days.
121
+ #
122
+ # @param days [Integer]
123
+ # @return [Interval]
124
+ def from_days days
125
+ Interval.new 0, days, 0
126
+ end
127
+
128
+ # Returns an Interval instance with the given seconds.
129
+ #
130
+ # @param seconds [Integer]
131
+ # @return [Interval]
132
+ def from_seconds seconds
133
+ nanoseconds = seconds * NANOSECONDS_IN_A_SECOND
134
+ Interval.new 0, 0, nanoseconds
135
+ end
136
+
137
+ # Returns an Interval instance with the given milliseconds.
138
+ #
139
+ # @param milliseconds [Integer]
140
+ # @return [Interval]
141
+ def from_milliseconds milliseconds
142
+ nanoseconds = milliseconds * NANOSECONDS_IN_A_MILLISECOND
143
+ Interval.new 0, 0, nanoseconds
144
+ end
145
+
146
+ # Returns an Interval instance with the given microseconds.
147
+ #
148
+ # @param microseconds [Integer]
149
+ # @return [Interval]
150
+ def from_microseconds microseconds
151
+ nanoseconds = microseconds * NANOSECONDS_IN_A_MICROSECOND
152
+ Interval.new 0, 0, nanoseconds
153
+ end
154
+
155
+ # Returns an Interval instance with the given nanoseconds.
156
+ #
157
+ # @param nanoseconds [Integer]
158
+ # @return [Interval]
159
+ def from_nanoseconds nanoseconds
160
+ Interval.new 0, 0, nanoseconds
161
+ end
162
+ end
163
+
164
+
165
+ # Converts the [Interval] to an ISO8601 Standard string.
166
+ # @return [String] The interval's ISO8601 string representation.
167
+ def to_s
168
+ # Memoizing it as the logic can be a bit heavy.
169
+ @to_s ||= to_string
170
+ end
171
+
172
+ ##
173
+ # @private Creates a new Google::Cloud::Spanner instance.
174
+ def initialize months, days, nanoseconds
175
+ if months > MAX_MONTHS || months < MIN_MONTHS
176
+ raise ArgumentError, "The Interval class supports months from #{MIN_MONTHS} to #{MAX_MONTHS}."
177
+ end
178
+ @months = months
179
+
180
+ if days > MAX_DAYS || days < MIN_DAYS
181
+ raise ArgumentError, "The Interval class supports days from #{MIN_DAYS} to #{MAX_DAYS}."
182
+ end
183
+ @days = days
184
+
185
+ if nanoseconds > MAX_NANOSECONDS || nanoseconds < MIN_NANOSECONDS
186
+ raise ArgumentError, "The Interval class supports nanoseconds from #{MIN_NANOSECONDS} to #{MAX_NANOSECONDS}"
187
+ end
188
+ @nanoseconds = nanoseconds
189
+ end
190
+
191
+
192
+ # @return [Integer] The numbers of months in the time interval.
193
+ attr_reader :months
194
+
195
+ # @return [Integer] The numbers of days in the time interval.
196
+ attr_reader :days
197
+
198
+ # @return [Integer] The numbers of nanoseconds in the time interval.
199
+ attr_reader :nanoseconds
200
+
201
+
202
+ ##
203
+ # Standard value equality check for this object.
204
+ #
205
+ # @param [Object] other An object to compare with.
206
+ # @return [Boolean]
207
+ def eql? other
208
+ other.is_a?(Interval) &&
209
+ months == other.months &&
210
+ days == other.days &&
211
+ nanoseconds == other.nanoseconds
212
+ end
213
+ alias == eql?
214
+
215
+ ##
216
+ # Generate standard hash code for this object.
217
+ #
218
+ # @return [Integer]
219
+ #
220
+ def hash
221
+ @hash ||= [@months, @days, @nanoseconds].hash
222
+ end
223
+
224
+ private
225
+
226
+ def match_sign value
227
+ value.negative? ? -1 : 1
228
+ end
229
+
230
+ # rubocop:disable Metrics/AbcSize
231
+ # rubocop:disable Metrics/CyclomaticComplexity
232
+ # rubocop:disable Metrics/PerceivedComplexity
233
+
234
+ # Converts [Interval] to an ISO8601 Standard string.
235
+ # @return [String] The interval's ISO8601 string representation.
236
+ def to_string
237
+ # Months should be converted to years and months.
238
+ years = @months.fdiv(12).truncate
239
+ months = @months % (match_sign(@months) * 12)
240
+
241
+ days = @days
242
+
243
+ # Nanoseconds should be converted to hours, minutes and seconds components.
244
+ remaining_nanoseconds = @nanoseconds
245
+
246
+ hours = (remaining_nanoseconds.abs / NANOSECONDS_IN_AN_HOUR) * match_sign(remaining_nanoseconds)
247
+ remaining_nanoseconds %= (match_sign(remaining_nanoseconds) * NANOSECONDS_IN_AN_HOUR)
248
+ minutes = (remaining_nanoseconds.abs / NANOSECONDS_IN_A_MINUTE) * match_sign(remaining_nanoseconds)
249
+ remaining_nanoseconds %= (match_sign(remaining_nanoseconds) * NANOSECONDS_IN_A_MINUTE)
250
+
251
+ # Only seconds can be fractional, and can have a maximum of 9 characters after decimal. Therefore,
252
+ # we convert the remaining nanoseconds to an integer for formatting.
253
+ seconds = (remaining_nanoseconds.abs / NANOSECONDS_IN_A_SECOND) * match_sign(remaining_nanoseconds)
254
+ nanoseconds = remaining_nanoseconds % (match_sign(remaining_nanoseconds) * NANOSECONDS_IN_A_SECOND)
255
+
256
+ interval_string = ["P"]
257
+
258
+ interval_string.append "#{years}Y" if years.nonzero?
259
+
260
+ interval_string.append "#{months}M" if months.nonzero?
261
+
262
+ interval_string.append "#{days}D" if days.nonzero?
263
+
264
+ if hours.nonzero? || minutes.nonzero? || seconds.nonzero? || nanoseconds.nonzero?
265
+ interval_string.append "T"
266
+
267
+ interval_string.append "#{hours}H" if hours.nonzero?
268
+
269
+ interval_string.append "#{minutes}M" if minutes.nonzero?
270
+
271
+ if seconds.nonzero? || nanoseconds.nonzero?
272
+ interval_string.append "#{format_seconds seconds, nanoseconds}S"
273
+ end
274
+ end
275
+
276
+ return "P0Y" if interval_string == ["P"]
277
+
278
+ interval_string.join
279
+ end
280
+
281
+ # rubocop:enable Metrics/AbcSize
282
+ # rubocop:enable Metrics/CyclomaticComplexity
283
+ # rubocop:enable Metrics/PerceivedComplexity
284
+
285
+ # Formats decimal values to be in multiples of 3 length.
286
+ # @return [String]
287
+ def format_seconds seconds, nanoseconds
288
+ return seconds if nanoseconds.zero?
289
+ add_sign = seconds.zero? && nanoseconds.negative?
290
+
291
+ nanoseconds_str = nanoseconds.abs.to_s.rjust 9, "0"
292
+ nanoseconds_str = nanoseconds_str.gsub(/0+$/, "")
293
+
294
+ target_length =
295
+ if nanoseconds_str.length <= 3
296
+ 3
297
+ elsif nanoseconds_str.length <= 6
298
+ 6
299
+ else
300
+ 9
301
+ end
302
+
303
+ nanoseconds_str = (nanoseconds_str + ("0" * target_length))[0...target_length]
304
+ "#{add_sign ? '-' : ''}#{seconds}.#{nanoseconds_str}"
305
+ end
306
+ end
307
+ end
308
+ end
309
+ end
@@ -221,8 +221,8 @@ module Google
221
221
  @keepalive_task.execute
222
222
  end
223
223
 
224
- def future &block
225
- Concurrent::Future.new(executor: @thread_pool, &block).execute
224
+ def future(&)
225
+ Concurrent::Future.new(executor: @thread_pool, &).execute
226
226
  end
227
227
  end
228
228
  end
@@ -16,7 +16,7 @@
16
16
  module Google
17
17
  module Cloud
18
18
  module Spanner
19
- VERSION = "2.26.0".freeze
19
+ VERSION = "2.27.0".freeze
20
20
  end
21
21
  end
22
22
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-cloud-spanner
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.26.0
4
+ version: 2.27.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Moore
8
8
  - Chris Smith
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-28 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -150,6 +150,7 @@ files:
150
150
  - lib/google/cloud/spanner/instance/config/list.rb
151
151
  - lib/google/cloud/spanner/instance/job.rb
152
152
  - lib/google/cloud/spanner/instance/list.rb
153
+ - lib/google/cloud/spanner/interval.rb
153
154
  - lib/google/cloud/spanner/lar_headers.rb
154
155
  - lib/google/cloud/spanner/mutation_group.rb
155
156
  - lib/google/cloud/spanner/partition.rb
@@ -175,14 +176,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
175
176
  requirements:
176
177
  - - ">="
177
178
  - !ruby/object:Gem::Version
178
- version: '3.0'
179
+ version: '3.1'
179
180
  required_rubygems_version: !ruby/object:Gem::Requirement
180
181
  requirements:
181
182
  - - ">="
182
183
  - !ruby/object:Gem::Version
183
184
  version: '0'
184
185
  requirements: []
185
- rubygems_version: 3.6.5
186
+ rubygems_version: 3.6.9
186
187
  specification_version: 4
187
188
  summary: API Client library for Google Cloud Spanner API
188
189
  test_files: []