Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rails 7.1.* Deserializes Date Range with Infinities incorrectly with Postgres adapter #51111

Open
brettwgreen opened this issue Feb 16, 2024 · 2 comments

Comments

@brettwgreen
Copy link

brettwgreen commented Feb 16, 2024

Steps to reproduce

  • Create a table with a postgres date range column (tsrange for example)
  • Create an entry in that table witha date range that has infinity on one end and save it
    range_test = RangeTest.create!(date_range: (DateTime.new(2024,1,1)...DateTime::Infinity.new))
  • Reload that entry from the database (deserialize it)
    range_test.reload
  • Look at the infinite part of the range now and it no longer is infinite... it is nil
    puts range_test.date_rage.end.class returns NilClass

In previous version of rails (7.0.8 for example), it actuallly returned a Float::INFINITY class... this was a little off, but closer to the mark than nil.

At least with that, we could to a check like date_range.end.to_s == 'Infinity' in our logic, which looks reasonable

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  # gem "rails", '= 7.0.8'
  gem "rails", '~> 7.1.3'
  # If you want to test against edge Rails replace the previous line with this:
  # gem "rails", github: "rails/rails", branch: "main"

  gem "pg"
end

require "active_record"
require "minitest/autorun"
require "logger"

# You may need to change your pg settings accordingly
db_config = {
  adapter: 'postgresql',
  database: 'range_test',
  host: '::1', # Change this to your PostgreSQL host
  port: 5432,
  username: 'postgres', # Change this to your PostgreSQL username
  password: 'test'  # Change this to your PostgreSQL password
}

begin
  ActiveRecord::Base.establish_connection(db_config.except(:database))
  ActiveRecord::Base.connection.drop_database(db_config[:database]) rescue nil
  ActiveRecord::Base.connection.create_database(db_config[:database])
end
ActiveRecord::Base.establish_connection(db_config)
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :range_tests do |t|
    t.tsrange :date_range
    t.timestamps
  end
end

class RangeTest < ActiveRecord::Base
end

class BugTest < Minitest::Test
  def test_date_range_infinity_deserialization
    range = (DateTime.new(2024,1,1)...DateTime::Infinity.new)
    range_test = RangeTest.create!(date_range: range)
    assert_equal DateTime::Infinity, range_test.date_range.end.class
    # Need to reload to deserialize the range back out from the DB to see the problem
    range_test.reload
    # Interestingly, even before rails 7.1.3, the end range would deserialize to a Float::INFINITY
    # So we're checking the to_s representation to show the change in behavior
    assert_equal "Infinity", range_test.date_range.end.to_s
  end
end

Expected behavior

Frankly, it was a bit of a bug before in that this would return a Float::INFINITY object before, where it should have returned a DateTime::INFINITY. At least with that you could, for example, check date_range.end.to_s == 'Infinity' for some kind of logical checking for an infinite range start or end.

Returning nil seems to be undocumented change in behavior at the very least.

Actual behavior

The date range returns a nil object for whichever end of the range may be infinite.

System configuration

Rails version: 7.1.3

Ruby version: 3.2.2

@fatkodima
Copy link
Member

That was changed in #45099. So I would say that this is expected behavior - the postgres adapter correctly returns an infinite range. Its just unfortunate that ruby represents one of its ends as nil.

@pean
Copy link

pean commented May 6, 2024

I think this change is for the better, but it is a change. Even though that (Date.today...) and (Date.today...Float::INFINITY) can be the same thing, there is a weird mismatch in types, once is a date and the other is a float. To me the natural way to write unbounded ranges is (1..).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants