Skip to content

Commit c528b34

Browse files
committed
Improve postgres interval overflow protection
> Sometimes, operations on Times returns just float numbers of seconds, so, we need to handle that. > Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float) When this happens, it's possible to endup with a pretty large number of seconds, making the ISO 8601 formatted duration to trigger an `interval field value out range error`. PostgreSQL 15 has already improved overflow detection when casting values to interval However, to further reduce the likelihood of such issues and ensure better-formatted duration types, it now implements the construction of `ActiveSupport::Duration` during the serialization step. How to reproduce (at least in versions prior to PG 15+): ``` ruby require "bundler/inline" gemfile(true) do source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem "rails", github: "rails/rails", branch: "main" gem "pg" end require "active_record" require "minitest/autorun" require "logger" ActiveRecord::Base.establish_connection( adapter: 'postgresql', database: 'postgres', username: 'postgres', host: 'localhost' ) ActiveRecord::Base.logger = Logger.new(STDOUT) ActiveRecord::Schema.define do create_table :projects, force: true do |t| t.interval :duration end end class Project < ActiveRecord::Base; end class IntervalBugTest < Minitest::Test def test_duration project = Project.create(duration: 70.years) assert_equal 70.years, project.duration end def test_duration_error duration = 70.years.ago - Time.now() assert_raises ActiveRecord::StatementInvalid do Project.create(duration: duration) end end end ```
1 parent 04f29cb commit c528b34

File tree

2 files changed

+8
-1
lines changed

2 files changed

+8
-1
lines changed

activerecord/lib/active_record/connection_adapters/postgresql/oid/interval.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def serialize(value)
3333
when ::Numeric
3434
# Sometimes operations on Times returns just float number of seconds so we need to handle that.
3535
# Example: Time.current - (Time.current + 1.hour) # => -3600.000001776 (Float)
36-
value.seconds.iso8601(precision: self.precision)
36+
ActiveSupport::Duration.build(value).iso8601(precision: self.precision)
3737
else
3838
super
3939
end

activerecord/test/cases/adapters/postgresql/datatype_test.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ def test_time_values
4444
assert_equal (-21.day), @first_time.scaled_time_interval
4545
end
4646

47+
def test_update_large_time_in_seconds
48+
@first_time.scaled_time_interval = 70.years.to_f
49+
assert @first_time.save
50+
assert @first_time.reload
51+
assert_equal 70.years, @first_time.scaled_time_interval
52+
end
53+
4754
def test_oid_values
4855
assert_equal 1234, @first_oid.obj_id
4956
end

0 commit comments

Comments
 (0)