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 +4 -4
- data/.github/CODEOWNERS +1 -1
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +6 -0
- data/Gemfile +5 -0
- data/acceptance/cases/migration/change_schema_test.rb +1 -1
- data/acceptance/cases/migration/schema_dumper_test.rb +1 -1
- data/acceptance/cases/type/time_test.rb +123 -3
- data/examples/snippets/Rakefile +21 -0
- data/lib/active_record/type/spanner/time.rb +22 -9
- data/lib/activerecord_spanner_adapter/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69f3ecf02a5d1f545a2a9a0182a768e9bad73c8223b54acdc38e38b9f5a639fb
|
|
4
|
+
data.tar.gz: 641d598c91a69eeb4af93fced37f9842334fb42d95378013711d9fa08ef10f88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 @
|
|
7
|
+
* @googleapis/cloud-sdk-ruby-team @olavloite @rahul2393 @sakthivelmanii
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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
|
data/examples/snippets/Rakefile
CHANGED
|
@@ -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::
|
|
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
|
|
28
|
-
|
|
29
|
-
super
|
|
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
|
|
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
|