google-cloud-spanner 2.26.0 → 2.28.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: 850ac8a54d873ff6d92cc7d7b835abd1d0fd980d22529296b036b67d5c4ecbfe
4
+ data.tar.gz: 585441baf49b27942fddc9876044221965fe9edd475bdf8f6c53f463a3a75cf0
5
5
  SHA512:
6
- metadata.gz: 58624fe569f909d5923f897fc02c6865c4861dd289ec5b1f58aead9ae01b87968ebd7b5e23d0305e4661011fd4bc2e3479cc08c08ef0c19b320daa2e68300fca
7
- data.tar.gz: c011acec4ad559505de0b89e77dae6d2046f7e00768a421f4bd347ff25a3b8ee35673f1714037528e1d13572e054486ded0fd666d253df9b322a2c90ef19a1bf
6
+ metadata.gz: dca36d5f2479c792b0c7e10051cf52915314c76de12581d09ff8284ae8e591430d2d67f779c71b2956580e4f970054ddcac3d3f8429ed8ced759b8cd0be0339a
7
+ data.tar.gz: 887212c39b640de69edf12df3bca506494a7bb65c6f0b102eb80cefb3c41e8c3511b027f18334669ab58a9d1ba7114ab1d5bdc83d4982c39521ff4d542b94344
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Release History
2
2
 
3
+ ### 2.28.0 (2025-10-22)
4
+
5
+ #### Features
6
+
7
+ * Add support for order_by and lock_hint options ([#182](https://github.com/googleapis/ruby-spanner/issues/182))
8
+
9
+ #### Bug Fixes
10
+
11
+ * use create Transaction when retrying when enumerating rows in `Spanner::Results` ([#191](https://github.com/googleapis/ruby-spanner/issues/191))
12
+ * run explicit BeginTransaction in Client#transaction for mutation-only payloads ([#191](https://github.com/googleapis/ruby-spanner/issues/191))
13
+ * Transaction#initialize is no longer incorrectly marked as public in documentation ([#191](https://github.com/googleapis/ruby-spanner/issues/191))
14
+ * several field accessors in Partition are no longer incorrectly marked as public in documentation ([#191](https://github.com/googleapis/ruby-spanner/issues/191))
15
+ * session keepalive uses Process.clock_gettime instead of Time.now ([#185](https://github.com/googleapis/ruby-spanner/pull/185))
16
+
17
+ ### 2.27.0 (2025-05-28)
18
+
19
+ #### Features
20
+
21
+ * Spanner Interval type ([#162](https://github.com/googleapis/ruby-spanner/issues/162))
22
+ * Updated required Ruby version to 3.1 ([#160](https://github.com/googleapis/ruby-spanner/issues/160))
23
+
3
24
  ### 2.26.0 (2025-03-24)
4
25
 
5
26
  #### Features
@@ -33,8 +33,9 @@ module Google
33
33
  # {Google::Cloud::Spanner::Admin::Database.database_admin}.list_backup_operations instead.
34
34
  #
35
35
  class List < DelegateClass(::Array)
36
+ # The `Spanner::Service` reference.
36
37
  # @private
37
- # The gRPC Service object.
38
+ # @return [::Google::Cloud::Spanner::Service]
38
39
  attr_accessor :service
39
40
 
40
41
  # @private
@@ -148,13 +149,14 @@ module Google
148
149
  end
149
150
  end
150
151
 
151
- ##
152
- # @private
153
- #
154
- # New Backup::Job::List from a
152
+ # Creates a new `Spanner::Backup::Job::List` of `Gapic::Operation` operations from a
155
153
  # `Gapic::PagedEnumerable<Google::Longrunning::Operation>`
156
154
  # object. Operation object is a backup operation.
157
- #
155
+ # @param grpc [::Gapic::PagedEnumerable<::Google::Longrunning::Operation>]
156
+ # Wrapped `Gapic::PagedEnumberable` reference.
157
+ # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` reference.
158
+ # @private
159
+ # @return [::Google::Cloud::Spanner::Backup::Job::List]
158
160
  def self.from_grpc grpc, service
159
161
  operations_client =
160
162
  service.databases.instance_variable_get "@operations_client"
@@ -58,12 +58,14 @@ module Google
58
58
  # end
59
59
  #
60
60
  class Job
61
- ##
62
- # @private The `Gapic::Operation` gRPC object.
61
+ # The wrapped `Gapic::Operation` object.
62
+ # @private
63
+ # @return [::Gapic::Operation]
63
64
  attr_accessor :grpc
64
65
 
65
- ##
66
- # @private The gRPC Service object.
66
+ # The `Spanner::Service` reference.
67
+ # @private
68
+ # @return [::Google::Cloud::Spanner::Service]
67
69
  attr_accessor :service
68
70
 
69
71
  ##
@@ -265,8 +267,11 @@ module Google
265
267
  Convert.timestamp_to_time @grpc.metadata.cancel_time
266
268
  end
267
269
 
268
- ##
269
- # @private New Backup::Job from a `Gapic::Operation` object.
270
+ # Create a new Backup::Job from a `Gapic::Operation` object.
271
+ # @param grpc [::Gapic::Operation`] The wrapped `Gapic::Operation` object.
272
+ # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` reference.
273
+ # @private
274
+ # @return [::Google::Cloud::Spanner::Backup::Job]
270
275
  def self.from_grpc grpc, service
271
276
  new.tap do |job|
272
277
  job.instance_variable_set :@grpc, grpc
@@ -63,8 +63,18 @@ module Google
63
63
  # new_partition
64
64
  #
65
65
  class BatchClient
66
- ##
67
- # @private Creates a new Spanner BatchClient instance.
66
+ # Creates a new Spanner BatchClient instance.
67
+ # @param project [::Google::Cloud::Spanner::Project] A `Spanner::Project` ref.
68
+ # @param instance_id [::String] Instance id, e.g. `"my-instance"`.
69
+ # @param database_id [::String] Database id, e.g. `"my-database"`.
70
+ # @param session_labels [::Hash, nil] Optional. The labels to be applied to all sessions
71
+ # created by the client. Example: `"team" => "billing-service"`.
72
+ # @param query_options [::Hash, nil] Optional. A hash of values to specify the custom
73
+ # query options for executing SQL query. Example parameter `:optimizer_version`.
74
+ # @param directed_read_options [::Hash, nil] Optional. Client options used to set
75
+ # the `directed_read_options` for all ReadRequests and ExecuteSqlRequests.
76
+ # Converts to `V1::DirectedReadOptions`. Example option: `:exclude_replicas`.
77
+ # @private
68
78
  def initialize project, instance_id, database_id, session_labels: nil,
69
79
  query_options: nil, directed_read_options: nil
70
80
  @project = project
@@ -404,15 +414,18 @@ module Google
404
414
 
405
415
  protected
406
416
 
407
- ##
408
- # @private Raise an error unless an active connection to the service is
417
+ # Raise an error unless an active connection to the service is
409
418
  # available.
419
+ # @private
420
+ # @raise [StandardError]
421
+ # @return [nil]
410
422
  def ensure_service!
411
423
  raise "Must have active connection to service" unless @project.service
412
424
  end
413
425
 
414
- ##
415
426
  # New session for each use.
427
+ # @private
428
+ # @return [::Google::Cloud::Spanner::Session]
416
429
  def session
417
430
  ensure_service!
418
431
  grpc = @project.service.create_session \
@@ -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|
@@ -51,12 +51,26 @@ module Google
51
51
  # end
52
52
  #
53
53
  class Client
54
- ##
54
+ # A semi-arbitrary constant for thread-wide global parameter name
55
55
  # @private
56
56
  IS_TRANSACTION_RUNNING_KEY = "ruby_spanner_is_transaction_running".freeze
57
57
 
58
- ##
59
- # @private Creates a new Spanner Client instance.
58
+ # Creates a new Spanner Client instance.
59
+ # @param project [::Google::Cloud::Spanner::Project] A `Spanner::Project` ref.
60
+ # @param instance_id [::String] Instance id, e.g. `"my-instance"`.
61
+ # @param database_id [::String] Database id, e.g. `"my-database"`.
62
+ # @param session_labels [::Hash, nil] Optional. The labels to be applied to all sessions
63
+ # created by the client. Example: `"team" => "billing-service"`.
64
+ # @param pool_opts [::Hash] Optional. `Spanner::Pool` creation options.
65
+ # Example parameter: `:keepalive`.
66
+ # @param query_options [::Hash, nil] Optional. A hash of values to specify the custom
67
+ # query options for executing SQL query. Example parameter `:optimizer_version`.
68
+ # @param database_role [::String, nil] Optional. The Spanner session creator role.
69
+ # Example: `analyst`
70
+ # @param directed_read_options [::Hash, nil] Optional. Client options used to set
71
+ # the `directed_read_options` for all ReadRequests and ExecuteSqlRequests.
72
+ # Converts to `V1::DirectedReadOptions`. Example option: `:exclude_replicas`.
73
+ # @private
60
74
  def initialize project, instance_id, database_id, session_labels: nil,
61
75
  pool_opts: {}, query_options: nil, database_role: nil,
62
76
  directed_read_options: nil
@@ -89,7 +103,7 @@ module Google
89
103
  end
90
104
 
91
105
  # The Spanner project connected to.
92
- # @return [Project]
106
+ # @return [::Google::Cloud::Spanner::Project]
93
107
  def project
94
108
  @project
95
109
  end
@@ -878,6 +892,14 @@ module Google
878
892
  # and all replicas are exhausted without finding a healthy replica,
879
893
  # Spanner will wait for a replica in the list to become available,
880
894
  # requests may fail due to DEADLINE_EXCEEDED errors.
895
+ # @param [::Google::Cloud::Spanner::V1::ReadRequest::OrderBy] order_by An option to control the order in which
896
+ # rows are returned from a read.
897
+ # To see the available options refer to
898
+ # [Google::Cloud::Spanner::V1::ReadRequest::OrderBy](https://cloud.google.com/ruby/docs/reference/google-cloud-spanner-v1/latest/Google-Cloud-Spanner-V1-ReadRequest-OrderBy)
899
+ # @param [::Google::Cloud::Spanner::V1::ReadRequest::LockHint] lock_hint A lock hint mechanism for reads done
900
+ # within a transaction.
901
+ # To see the available options refer to
902
+ # [Google::Cloud::Spanner::V1::ReadRequest::LockHint](https://cloud.google.com/ruby/docs/reference/google-cloud-spanner-v1/latest/Google-Cloud-Spanner-V1-ReadRequest-LockHint)
881
903
  #
882
904
  # @return [Google::Cloud::Spanner::Results] The results of the read.
883
905
  #
@@ -962,7 +984,7 @@ module Google
962
984
  #
963
985
  def read table, columns, keys: nil, index: nil, limit: nil,
964
986
  single_use: nil, request_options: nil, call_options: nil,
965
- directed_read_options: nil
987
+ directed_read_options: nil, order_by: nil, lock_hint: nil
966
988
  validate_single_use_args! single_use
967
989
  ensure_service!
968
990
 
@@ -981,7 +1003,9 @@ module Google
981
1003
  request_options: request_options,
982
1004
  call_options: call_options,
983
1005
  directed_read_options: directed_read_options || @directed_read_options,
984
- route_to_leader: route_to_leader
1006
+ route_to_leader: route_to_leader,
1007
+ order_by: order_by,
1008
+ lock_hint: lock_hint
985
1009
  end
986
1010
  results
987
1011
  end
@@ -2102,15 +2126,28 @@ module Google
2102
2126
  begin
2103
2127
  Thread.current[IS_TRANSACTION_RUNNING_KEY] = true
2104
2128
  yield tx
2105
- transaction_id = nil
2106
- transaction_id = tx.transaction_id if tx.existing_transaction?
2107
- commit_resp = @project.service.commit \
2108
- tx.session.path, tx.mutations,
2129
+
2130
+ unless tx.existing_transaction?
2131
+ # This can happen if the yielded `tx` object was only used to add mutations.
2132
+ # Then it never called any RPCs and didn't create a server-side Transaction object.
2133
+ # In which case we should make an explicit BeginTransaction call here.
2134
+ tx.safe_begin_transaction!(
2135
+ exclude_from_change_streams: exclude_txn_from_change_streams,
2136
+ request_options: request_options,
2137
+ call_options: call_options
2138
+ )
2139
+ end
2140
+
2141
+ transaction_id = tx.transaction_id
2142
+ commit_resp = @project.service.commit(
2143
+ tx.session.path,
2144
+ tx.mutations,
2109
2145
  transaction_id: transaction_id,
2110
2146
  exclude_txn_from_change_streams: exclude_txn_from_change_streams,
2111
2147
  commit_options: commit_options,
2112
2148
  request_options: request_options,
2113
2149
  call_options: call_options
2150
+ )
2114
2151
  resp = CommitResponse.from_grpc commit_resp
2115
2152
  commit_options ? resp : resp.timestamp
2116
2153
  rescue GRPC::Aborted,
@@ -2409,17 +2446,21 @@ module Google
2409
2446
  @pool.reset
2410
2447
  end
2411
2448
 
2412
- ##
2449
+ # Creates a new Session objece.
2450
+ # @param multiplexed [::Boolean] Optional. Default to `false`.
2451
+ # If `true`, specifies a multiplexed session.
2413
2452
  # @private
2414
- # Creates a new session object every time.
2415
- def create_new_session
2453
+ # @return [::Google::Cloud::Spanner::Session]
2454
+ def create_new_session multiplexed: false
2416
2455
  ensure_service!
2417
2456
  grpc = @project.service.create_session \
2418
2457
  Admin::Database::V1::DatabaseAdmin::Paths.database_path(
2419
2458
  project: project_id, instance: instance_id, database: database_id
2420
2459
  ),
2421
2460
  labels: @session_labels,
2422
- database_role: @database_role
2461
+ database_role: @database_role,
2462
+ multiplexed: multiplexed
2463
+
2423
2464
  Session.from_grpc grpc, @project.service, query_options: @query_options
2424
2465
  end
2425
2466
 
@@ -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
@@ -74,11 +74,16 @@ module Google
74
74
  # end
75
75
  #
76
76
  class Instance
77
- ##
78
- # @private The gRPC Service object.
77
+ # The `Spanner::Service` reference.
78
+ # @private
79
+ # @return [::Google::Cloud::Spanner::Service]
79
80
  attr_accessor :service
80
81
 
81
- # @private Creates a new Instance instance.
82
+ # Creates a new `Spanner::Instance` instance.
83
+ # @param grpc [::Google::Cloud::Spanner::Admin::Instance::V1::Instance]
84
+ # The protobuf `V1::Instance` underlying object.
85
+ # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` reference.
86
+ # @private
82
87
  def initialize grpc, service
83
88
  @grpc = grpc
84
89
  @service = service
@@ -957,9 +962,13 @@ module Google
957
962
  grpc.permissions
958
963
  end
959
964
 
960
- ##
961
- # @private Creates a new Instance instance from a
965
+ # Creates a new Instance instance from a
962
966
  # `Google::Cloud::Spanner::Admin::Instance::V1::Instance`.
967
+ # @param grpc [::Google::Cloud::Spanner::Admin::Instance::V1::Instance]
968
+ # The protobuf `V1::Instance` underlying object.
969
+ # @param service [::Google::Cloud::Spanner::Service] A `Spanner::Service` reference.
970
+ # @private
971
+ # @return [::Google::Cloud::Spanner::Instance]
963
972
  def self.from_grpc grpc, service
964
973
  new grpc, service
965
974
  end
@@ -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
@@ -44,8 +44,14 @@ module Google
44
44
  # results = batch_snapshot.execute_partition partition
45
45
  #
46
46
  class Partition
47
- # @ private
47
+ # A `V1::ExecuteSqlRequest` that is related to this partition.
48
+ # @private
49
+ # @return [::Google::Cloud::Spanner::V1::ExecuteSqlRequest]
48
50
  attr_reader :execute
51
+
52
+ # A `V1::ReadRequest` that is related to this partition.
53
+ # @private
54
+ # @return [::Google::Cloud::Spanner::V1::ReadRequest]
49
55
  attr_reader :read
50
56
 
51
57
  ##