activerecord-spanner-adapter 2.5.0 → 2.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5132b6ef0337fa324c42c5b61ff6bee8e745352c58c46afc5f583a253fc6571
4
- data.tar.gz: 699d2b993d739c78fc95fc12b30189e512517111aa9ae787d929b9d3ac828576
3
+ metadata.gz: 69f3ecf02a5d1f545a2a9a0182a768e9bad73c8223b54acdc38e38b9f5a639fb
4
+ data.tar.gz: 641d598c91a69eeb4af93fced37f9842334fb42d95378013711d9fa08ef10f88
5
5
  SHA512:
6
- metadata.gz: 3d97e90c71f7dad83a35fc6cfb1921c24989d88b050c88b75ff444dbc58d4c0c2a9abbde2f8197d700dca121dda5c12853e4f6cae6c27bfc0520c7facbff3338
7
- data.tar.gz: 5dbb5523a45941eff16db888d0af3c5ab7ea3e54af9e94d0f0943cef2a9b6d5e39078df1a1761e05a436910906cd19825f84f298d8e2b9043d79981dff51b4f7
6
+ metadata.gz: 951478249b5dc96f1dec3b9b1aa0b8764a361963e129283e5cb22ae391fc85d3572cf5a3056442c09a46732237524c89807aede0f0bb55b8c938c92b6a891475
7
+ data.tar.gz: 2d36a28118c79c5680227a8a7cba2ba3a07586168229151bbeae764ecf1ef36e70c6bb4327703d5835e18fa5b0093cfc9a49f6f2555ecb3ab670bbf2cda4cbda
data/.github/CODEOWNERS CHANGED
@@ -4,4 +4,4 @@
4
4
  # For syntax help see:
5
5
  # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax
6
6
 
7
- * @googleapis/cloud-sdk-ruby-team @olavloite @rahul2393 @ansh0l
7
+ * @googleapis/cloud-sdk-ruby-team @olavloite @rahul2393 @sakthivelmanii
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "2.5.0"
2
+ ".": "2.5.1"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ### 2.5.1 (2026-06-30)
4
+
5
+ #### Bug Fixes
6
+
7
+ * use DateTime instead of Time for time data type ([#395](https://github.com/googleapis/ruby-spanner-activerecord/issues/395))
8
+
3
9
  ### 2.5.0 (2026-02-11)
4
10
 
5
11
  #### Features
data/Gemfile CHANGED
@@ -22,3 +22,8 @@ end
22
22
  # Required for samples
23
23
  gem "docker-api"
24
24
  gem "sinatra-activerecord"
25
+
26
+ # Force google-protobuf to compile from source to avoid ABI issues in CI
27
+ if ENV["CI"]
28
+ gem "google-protobuf", force_ruby_platform: true
29
+ end
@@ -300,7 +300,7 @@ module ActiveRecord
300
300
 
301
301
  column = connection.columns(:testings).find { |c| c.name == "foo" }
302
302
 
303
- assert_equal :time, column.type
303
+ assert_equal :datetime, column.type
304
304
  assert_equal "TIMESTAMP", column.sql_type
305
305
  end
306
306
 
@@ -76,7 +76,7 @@ module ActiveRecord
76
76
  connection = pool_or_connection
77
77
  schema = StringIO.new
78
78
  ActiveRecord::SchemaDumper.dump connection, schema
79
- assert schema.string.include?("t.time \"last_updated\", allow_commit_timestamp: true"), schema.string
79
+ assert_match(/t\.(datetime|timestamp) "last_updated",.*allow_commit_timestamp: true/, schema.string, schema.string)
80
80
  end
81
81
 
82
82
  def test_dump_schema_contains_virtual_column
@@ -45,7 +45,7 @@ module ActiveRecord
45
45
  end
46
46
 
47
47
  def test_date_time_string_value_with_subsecond_precision
48
- expected_time = ::Time.local 2017, 07, 4, 14, 19, 10, 897761
48
+ expected_time = ::Time.utc 2017, 7, 4, 14, 19, 10, 897761
49
49
 
50
50
  string_value = "2017-07-04 14:19:10.897761"
51
51
 
@@ -53,6 +53,7 @@ module ActiveRecord
53
53
  assert_equal expected_time, record.start_time.utc
54
54
 
55
55
  record.save!
56
+ record.reload
56
57
  assert_equal expected_time, record.start_time.utc
57
58
 
58
59
  assert_equal record, TestTypeModel.find_by(start_time: string_value)
@@ -60,12 +61,13 @@ module ActiveRecord
60
61
 
61
62
  def test_date_time_with_string_value_with_non_iso_format
62
63
  string_value = "04/07/2017 2:19pm"
63
- expected_time = ::Time.local 2017, 07, 4, 14, 19
64
+ expected_time = ::Time.utc 2017, 7, 4, 14, 19
64
65
 
65
66
  record = TestTypeModel.new start_time: string_value
66
67
  assert_equal expected_time, record.start_time
67
68
 
68
69
  record.save!
70
+ record.reload
69
71
  assert_equal expected_time, record.start_time.utc
70
72
 
71
73
  assert_equal record, TestTypeModel.find_by(start_time: string_value)
@@ -82,6 +84,124 @@ module ActiveRecord
82
84
 
83
85
  assert_equal expected_time, record.start_time
84
86
  end
87
+
88
+ def test_multiparameter_datetime
89
+ expected_time = ::Time.utc 2020, 12, 25, 10, 30, 0
90
+ record = TestTypeModel.new start_time: { 1 => 2020, 2 => 12, 3 => 25, 4 => 10, 5 => 30 }
91
+
92
+ assert_equal expected_time, record.start_time
93
+
94
+ record.save!
95
+ record.reload
96
+
97
+ assert_equal expected_time, record.start_time
98
+ end
99
+
100
+ def test_date_time_string_value_with_timezone_aware_attributes
101
+ old_zone = ::Time.zone
102
+ ::Time.zone = "UTC"
103
+ TestTypeModel.time_zone_aware_attributes = true
104
+ TestTypeModel.reset_column_information
105
+
106
+ string_value = "2017-07-04 14:19:10.897761"
107
+ expected_time = ::Time.zone.local 2017, 7, 4, 14, 19, 10, 897761
108
+
109
+ record = TestTypeModel.new start_time: string_value
110
+ assert_equal expected_time, record.start_time
111
+
112
+ record.save!
113
+ record.reload
114
+ assert_equal expected_time, record.start_time
115
+
116
+ assert_equal record, TestTypeModel.find_by(start_time: string_value)
117
+ ensure
118
+ TestTypeModel.time_zone_aware_attributes = false
119
+ TestTypeModel.reset_column_information
120
+ ::Time.zone = old_zone
121
+ end
122
+
123
+ def test_string_value_with_paris_timezone
124
+ old_zone = ::Time.zone
125
+ ::Time.zone = "Paris" # Paris is UTC+2 in July (DST)
126
+ TestTypeModel.time_zone_aware_attributes = true
127
+ TestTypeModel.reset_column_information
128
+
129
+ begin
130
+ string_value = "2017-07-04 14:19:10.897761123"
131
+ # 14:19:10 in Paris (DST) is 12:19:10 UTC
132
+ # Subseconds: 897761123 nanoseconds = 897761.123 microseconds
133
+ expected_time = ::Time.zone.local(2017, 7, 4, 14, 19, 10, Rational(897761123, 1000))
134
+
135
+ record = TestTypeModel.new start_time: string_value
136
+ assert_equal expected_time, record.start_time
137
+ assert_equal 897761123, record.start_time.nsec
138
+ assert_instance_of ActiveSupport::TimeWithZone, record.start_time
139
+ assert_equal "Paris", record.start_time.time_zone.name
140
+
141
+ record.save!
142
+
143
+ # Verify directly in the database using the raw Spanner client (bypasses ActiveRecord)
144
+ raw_client = Google::Cloud::Spanner.new(
145
+ project: connector_config["project"],
146
+ emulator_host: connector_config["emulator_host"]
147
+ )
148
+ db_client = raw_client.client(
149
+ connector_config["instance"],
150
+ connector_config["database"]
151
+ )
152
+
153
+ results = db_client.execute "SELECT start_time FROM test_types WHERE id = #{record.id}"
154
+ raw_value = results.rows.first[:start_time]
155
+
156
+ # Spanner client returns TIMESTAMP as a UTC Time object
157
+ expected_raw_time = ::Time.utc(2017, 7, 4, 12, 19, 10, Rational(897761123, 1000))
158
+ assert_equal expected_raw_time, raw_value
159
+ assert_equal 897761123, raw_value.nsec
160
+ assert_equal "UTC", raw_value.zone
161
+
162
+ record.reload
163
+
164
+ assert_equal expected_time, record.start_time
165
+ assert_equal 897761123, record.start_time.nsec
166
+ assert_instance_of ActiveSupport::TimeWithZone, record.start_time
167
+ assert_equal "Paris", record.start_time.time_zone.name
168
+ ensure
169
+ TestTypeModel.time_zone_aware_attributes = false
170
+ TestTypeModel.reset_column_information
171
+ ::Time.zone = old_zone
172
+ end
173
+ end
174
+
175
+ def test_string_value_with_utc_and_local_timezones
176
+ # 1. With 'Z' (UTC)
177
+ string_value_utc = "2017-07-04 14:19:10.897761123Z"
178
+ expected_time_utc = ::Time.utc 2017, 7, 4, 14, 19, 10, Rational(897761123, 1000)
179
+ record = TestTypeModel.new start_time: string_value_utc
180
+ assert_equal expected_time_utc, record.start_time
181
+ assert_equal 897761123, record.start_time.nsec
182
+
183
+ # 2. With Offset (+02:00)
184
+ string_value_offset = "2017-07-04 14:19:10.897761123+02:00"
185
+ # 14:19:10+02:00 is 12:19:10 UTC
186
+ expected_time_offset = ::Time.utc 2017, 7, 4, 12, 19, 10, Rational(897761123, 1000)
187
+ record = TestTypeModel.new start_time: string_value_offset
188
+ assert_equal expected_time_offset, record.start_time
189
+ assert_equal 897761123, record.start_time.nsec
190
+
191
+ # 3. Without offset, but with ActiveRecord.default_timezone = :local
192
+ old_default_timezone = ActiveRecord.default_timezone
193
+ ActiveRecord.default_timezone = :local
194
+ begin
195
+ string_value_no_offset = "2017-07-04 14:19:10.897761123"
196
+ # Should be interpreted as local time
197
+ expected_time_local = ::Time.local 2017, 7, 4, 14, 19, 10, Rational(897761123, 1000)
198
+ record = TestTypeModel.new start_time: string_value_no_offset
199
+ assert_equal expected_time_local, record.start_time
200
+ assert_equal 897761123, record.start_time.nsec
201
+ ensure
202
+ ActiveRecord.default_timezone = old_default_timezone
203
+ end
204
+ end
85
205
  end
86
206
  end
87
- end
207
+ end
@@ -6,6 +6,7 @@
6
6
 
7
7
  require_relative "config/environment"
8
8
  require "docker"
9
+ require "google/cloud/spanner"
9
10
 
10
11
  def fetch_samples
11
12
  Dir.entries(".").select do |entry|
@@ -50,6 +51,25 @@ task :run, [:sample] do |_t, args|
50
51
  run_sample sample
51
52
  end
52
53
 
54
+ def wait_for_emulator
55
+ puts "Waiting for Spanner emulator to be ready..."
56
+ spanner = Google::Cloud::Spanner.new project: "test-project", emulator_host: "127.0.0.1:9010"
57
+ retries = 20
58
+ begin
59
+ # Make a cheap API call to verify the emulator is actually responding
60
+ spanner.instances
61
+ puts "Spanner emulator is ready."
62
+ rescue StandardError => e
63
+ if retries > 0
64
+ retries -= 1
65
+ sleep 0.5
66
+ retry
67
+ else
68
+ puts "Timed out waiting for Spanner emulator. Last error: #{e.message}"
69
+ end
70
+ end
71
+ end
72
+
53
73
  def run_sample sample
54
74
  puts "Running #{sample}"
55
75
  puts "Downloading Spanner emulator image..."
@@ -68,6 +88,7 @@ def run_sample sample
68
88
  begin
69
89
  puts "Starting Spanner emulator..."
70
90
  container.start!
91
+ wait_for_emulator
71
92
  Dir.chdir sample do
72
93
  sh "ruby ../bin/create_emulator_instance.rb"
73
94
  sh "rake db:migrate"
@@ -9,7 +9,24 @@
9
9
  module ActiveRecord
10
10
  module Type
11
11
  module Spanner
12
- class Time < ActiveRecord::Type::Time
12
+ class Time < ActiveRecord::Type::DateTime
13
+ def cast_value value
14
+ if value.is_a? ::String
15
+ return if value.empty?
16
+ begin
17
+ if ActiveRecord.default_timezone == :utc
18
+ ::DateTime.parse(value).to_time.getutc
19
+ else
20
+ ::Time.parse(value).getlocal
21
+ end
22
+ rescue StandardError
23
+ super
24
+ end
25
+ else
26
+ super
27
+ end
28
+ end
29
+
13
30
  def serialize_with_isolation_level value, isolation_level
14
31
  if value == :commit_timestamp
15
32
  return "PENDING_COMMIT_TIMESTAMP()" if isolation_level == :dml
@@ -24,18 +41,14 @@ module ActiveRecord
24
41
  val.acts_like?(:time) ? val.utc.rfc3339(9) : val
25
42
  end
26
43
 
27
- def user_input_in_time_zone value
28
- return value.in_time_zone if value.is_a? ::Time
29
- super value
44
+ def value_from_multiparameter_assignment values
45
+ defaults = { 1 => 2000, 2 => 1, 3 => 1 }
46
+ super defaults.merge(values)
30
47
  end
31
48
 
32
49
  private
33
50
 
34
- def cast_value value
35
- if value.is_a? ::String
36
- value = value.empty? ? nil : ::Time.parse(value)
37
- end
38
-
51
+ def apply_seconds_precision value
39
52
  value
40
53
  end
41
54
  end
@@ -5,5 +5,5 @@
5
5
  # https://opensource.org/licenses/MIT.
6
6
 
7
7
  module ActiveRecordSpannerAdapter
8
- VERSION = "2.5.0".freeze
8
+ VERSION = "2.5.1".freeze
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-spanner-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Google LLC