Support unbounded time ranges for PostgreSQL

Ruby raises an `ArgumentError` when mixing `Time` and `Float` values in
a `Range`, such as `Time.now..Float::INFINITY`.  Therefore, when
deserializing time ranges, we must use a beginless range to represent a
range with no lower bound, or an endless range to represent a range with
no upper bound.

Fixes #39833.
Closes #45082.

Co-authored-by: fatkodima <fatkodima123@gmail.com>
This commit is contained in:
Jonathan Hefner 2022-05-14 15:47:12 -05:00
parent 445d400a65
commit 4479d81134
2 changed files with 23 additions and 1 deletions

View File

@ -28,7 +28,7 @@ module ActiveRecord
if !infinity?(from) && extracted[:exclude_start]
raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')"
end
::Range.new(from, to, extracted[:exclude_end])
::Range.new(*sanitize_bounds(from, to), extracted[:exclude_end])
end
def serialize(value)
@ -76,6 +76,15 @@ module ActiveRecord
}
end
INFINITE_FLOAT_RANGE = (-::Float::INFINITY)..(::Float::INFINITY) # :nodoc:
def sanitize_bounds(from, to)
[
(from == -::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(to)) ? nil : from,
(to == ::Float::INFINITY && !INFINITE_FLOAT_RANGE.cover?(from)) ? nil : to
]
end
# When formatting the bound values of range types, PostgreSQL quotes
# the bound value using double-quotes in certain conditions. Within
# a double-quoted string, literal " and \ characters are themselves

View File

@ -146,6 +146,7 @@ class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
tz = ::ActiveRecord.default_timezone
assert_equal Time.public_send(tz, 2010, 1, 1, 14, 30, 0)..Time.public_send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range
assert_equal Time.public_send(tz, 2010, 1, 1, 14, 30, 0)...Time.public_send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range
assert_equal Time.public_send(tz, 2010, 1, 1, 14, 30, 0)...nil, @third_range.ts_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range)
assert_nil @empty_range.ts_range
end
@ -153,6 +154,7 @@ class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
def test_tstzrange_values
assert_equal Time.parse("2010-01-01 09:30:00 UTC")..Time.parse("2011-01-01 17:30:00 UTC"), @first_range.tstz_range
assert_equal Time.parse("2010-01-01 09:30:00 UTC")...Time.parse("2011-01-01 17:30:00 UTC"), @second_range.tstz_range
assert_equal Time.parse("2010-01-01 09:30:00 UTC")...nil, @third_range.tstz_range
assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range)
assert_nil @empty_range.tstz_range
end
@ -204,6 +206,11 @@ class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
Time.parse("-1000-01-01 14:30:00 CDT")...Time.parse("2020-02-02 14:30:00 CET"))
end
def test_unbounded_tstzrange
assert_equal_round_trip @first_range, :tstz_range, Time.parse("2010-01-01 14:30:00 CDT")...nil
assert_equal_round_trip @first_range, :tstz_range, nil..Time.parse("2010-01-01 14:30:00 CDT")
end
def test_create_tsrange
tz = ::ActiveRecord.default_timezone
assert_equal_round_trip(@new_range, :ts_range,
@ -224,6 +231,12 @@ class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase
Time.public_send(tz, -1000, 1, 1, 14, 30, 0)...Time.public_send(tz, 2020, 2, 2, 14, 30, 0))
end
def test_unbounded_tsrange
tz = ::ActiveRecord.default_timezone
assert_equal_round_trip @first_range, :ts_range, Time.public_send(tz, 2010, 1, 1, 14, 30, 0)...nil
assert_equal_round_trip @first_range, :ts_range, nil..Time.public_send(tz, 2010, 1, 1, 14, 30, 0)
end
def test_timezone_awareness_tsrange
tz = "Pacific Time (US & Canada)"