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

where clauses in has_* through: scopes are not supported in history #295

Open
tagliala opened this issue May 25, 2024 · 2 comments
Open

Comments

@tagliala
Copy link
Member

tagliala commented May 25, 2024

Models

class Country < ApplicationRecord
  include ChronoModel::TimeMachine

  has_many :cities, dependent: :destroy

  has_one :council
  has_one :first_member, through: :council
end

class Council < ApplicationRecord
  include ChronoModel::TimeMachine

  belongs_to :country

  has_many :members, dependent: :destroy

  # Problem is caused by `where` in the `scope`. `order` does not cause issues
  has_one :first_member, -> { where(active: true) }, class_name: 'Member', dependent: nil, inverse_of: false
end

class Member < ApplicationRecord
  include ChronoModel::TimeMachine

  belongs_to :council
  has_one :country, through: :council
end

Seeds

france = Country.create!(name: 'France')
council = france.create_council! name: 'Council'
council.members.create! name: 'Bob'
council.members.create! name: 'Alice'
council.members.create! name: 'Aaron', active: false

Output in the present

> Country.includes(:first_member).last
  Country Load (0.4ms)  SELECT "countries".* FROM "countries" ORDER BY "countries"."id" DESC LIMIT $1  [["LIMIT", 1]]
  SQL (0.8ms)  SELECT "councils"."id" AS t0_r0, "councils"."country_id" AS t0_r1, "councils"."name" AS t0_r2, "members"."id" AS t1_r0, "members"."council_id" AS t1_r1, "members"."name" AS t1_r2, "members"."active" AS t1_r3 FROM "councils" LEFT OUTER JOIN "members" ON "members"."active" = $1 AND "members"."council_id" = "councils"."id" WHERE "members"."active" = $2 AND "councils"."country_id" = $3  [["active", true], ["active", true], ["country_id", 1]]
=> #<Country:0x000000012d99f3d0 id: 1, name: "France", created_at: Sat, 25 May 2024 15:56:06.666591000 UTC +00:00, updated_at: Sat, 25 May 2024 15:56:06.666591000 UTC +00:00>
SELECT 
  "countries".* 
FROM 
  "countries" 
ORDER BY 
  "countries"."id" DESC 
LIMIT 
  $1 [[ "LIMIT", 
  1]]

SELECT 
  "councils"."id" AS t0_r0, 
  "councils"."country_id" AS t0_r1, 
  "councils"."name" AS t0_r2, 
  "members"."id" AS t1_r0, 
  "members"."council_id" AS t1_r1, 
  "members"."name" AS t1_r2, 
  "members"."active" AS t1_r3 
FROM 
  "councils" 
  LEFT OUTER JOIN "members" ON "members"."active" = $1 
  AND "members"."council_id" = "councils"."id" 
WHERE 
  "members"."active" = $2 
  AND "councils"."country_id" = $3 [[ "active", 
  true], 
  [ "active", 
  true], 
  [ "country_id", 
  1]]

Output in history

> Country.as_of(1.second.ago).includes(:first_member).last
  Country Load (1.4ms)  SELECT "countries".* FROM (SELECT "history"."countries".* FROM "history"."countries" WHERE ( '2024-05-25 16:03:39.485139'::timestamp <@ history.countries.validity )) "countries" ORDER BY "countries"."id" DESC LIMIT $1  [["LIMIT", 1]]
  SQL (1.1ms)  SELECT "councils"."id" AS t0_r0, "councils"."country_id" AS t0_r1, "councils"."name" AS t0_r2, "members"."id" AS t1_r0, "members"."council_id" AS t1_r1, "members"."name" AS t1_r2, "members"."active" AS t1_r3 FROM (SELECT "history"."councils".* FROM "history"."councils" WHERE "members"."active" = TRUE AND ( '2024-05-25 16:03:39.485139'::timestamp <@ history.councils.validity )) "councils" LEFT OUTER JOIN "members" ON "members"."active" = $1 AND "members"."council_id" = "councils"."id" WHERE "members"."active" = $2 AND "councils"."country_id" = $3  [["active", true], ["active", true], ["country_id", 1]]
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "members"
LINE 1: ...ory"."councils".* FROM "history"."councils" WHERE "members"....
                                                             ^

from ~/.rvm/gems/ruby-3.3.1/gems/activerecord-7.1.3.3/lib/active_record/connection_adapters/postgresql_adapter.rb:894:in `exec_params'
Caused by PG::UndefinedTable: ERROR:  missing FROM-clause entry for table "members"
LINE 1: ...ory"."councils".* FROM "history"."councils" WHERE "members"....
                                                             ^

from ~/.rvm/gems/ruby-3.3.1/gems/activerecord-7.1.3.3/lib/active_record/connection_adapters/postgresql_adapter.rb:894:in `exec_params'
SELECT 
  "countries".* 
FROM 
  (
    SELECT 
      "history"."countries".* 
    FROM 
      "history"."countries" 
    WHERE 
      (
        '2024-05-25 16:03:39.485139' :: timestamp < @ history.countries.validity
      )
  ) "countries" 
ORDER BY 
  "countries"."id" DESC 
LIMIT 
  $1 [[ "LIMIT", 
  1]]

SELECT 
  "councils"."id" AS t0_r0, 
  "councils"."country_id" AS t0_r1, 
  "councils"."name" AS t0_r2, 
  "members"."id" AS t1_r0, 
  "members"."council_id" AS t1_r1, 
  "members"."name" AS t1_r2, 
  "members"."active" AS t1_r3 
FROM 
  (
    SELECT 
      "history"."councils".* 
    FROM 
      "history"."councils" 
    WHERE 
      "members"."active" = TRUE 
      AND (
        '2024-05-25 16:03:39.485139' :: timestamp < @ history.councils.validity
      )
  ) "councils" 
  LEFT OUTER JOIN "members" ON "members"."active" = $1 
  AND "members"."council_id" = "councils"."id" 
WHERE 
  "members"."active" = $2 
  AND "councils"."country_id" = $3 [[ "active", 
  true], 
  [ "active", 
  true], 
  [ "country_id", 
  1]]
Test case
# frozen_string_literal: true

require 'bundler/inline'

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

  gem 'chrono_model'
  # Test against latest Chronomodel:
  # gem 'chrono_model', github: 'ifad/chronomodel'

  gem 'pg'
end

require 'chrono_model'
require 'minitest/autorun'
require 'logger'

# Needs a database called `chronomodel_test`

ActiveRecord::Base.establish_connection(adapter: 'chronomodel', database: 'chronomodel_test')
ActiveRecord::Base.logger = Logger.new($stdout)

ActiveRecord::Schema.define do
  enable_extension :btree_gist

  create_table :countries, temporal: true, force: true do |t|
    t.string :name
    t.timestamps
  end

  create_table :councils, temporal: true, force: true do |t|
    t.references :country
    t.string :name

    t.timestamps
  end

  create_table :members, temporal: true, force: true do |t|
    t.references :council
    t.string :name
    t.boolean :active, default: true

    t.timestamps
  end
end

class Country < ActiveRecord::Base
  include ChronoModel::TimeMachine

  has_many :cities, dependent: :destroy

  has_one :council
  has_one :first_member, through: :council
end

class Council < ActiveRecord::Base
  include ChronoModel::TimeMachine

  belongs_to :country

  has_many :members, dependent: :destroy

  # Problem is caused by `where` in the `scope`. `order` does not cause issues
  has_one :first_member, -> { where(active: true) }, class_name: 'Member', dependent: nil, inverse_of: false
end

class Member < ActiveRecord::Base
  include ChronoModel::TimeMachine

  belongs_to :council
  has_one :country, through: :council
end

france = Country.create!(name: 'France')
council = france.create_council! name: 'Council'
council.members.create! name: 'Bob'
council.members.create! name: 'Alice'
council.members.create! name: 'Aaron', active: false

class BugTest < Minitest::Test
  def test_historical_has_through_with_where_clause
    assert Country.as_of(Time.now).includes(:first_member).last
  end
end
@tagliala tagliala added bug CRITICAL Critical issues labels May 25, 2024
@tagliala tagliala changed the title Where clauses in has_one through is not supported in history Where clauses in has_* through: is not supported in history May 25, 2024
@tagliala tagliala changed the title Where clauses in has_* through: is not supported in history Where clauses in has_* through: are not supported in history May 25, 2024
tagliala added a commit that referenced this issue May 25, 2024
tagliala added a commit that referenced this issue May 25, 2024
@tagliala tagliala changed the title Where clauses in has_* through: are not supported in history where clauses in has_* through: scopes are not supported in history May 27, 2024
@tagliala
Copy link
Member Author

tagliala commented Jun 1, 2024

Looks like that the preloader is creating this issue:

ActiveRecord::Associations::Preloader.new(
records: records, associations: associations, scope: scope, model: model, as_of_time: as_of_time
).call

SELECT 
  "members".* 
FROM 
  (
    SELECT 
      "history"."members".* 
    FROM 
      "history"."members" 
    WHERE 
      (
        '2024-06-01 17:35:36.306466' :: timestamp < @ history.members.validity
      )
  ) "members" 
  INNER JOIN "councils" ON "members"."council_id" = "councils"."id" 
WHERE 
  "councils"."country_id" = $1 
  AND "members"."active" = $2 
LIMIT 
  $3 [[ "country_id", 
  1], 
  [ "active", 
  true], 
  [ "LIMIT", 
  1]]

@tagliala
Copy link
Member Author

tagliala commented Jun 1, 2024

Do we need a patch for active_record/associations/preloader/branch.rb ?

The problem is somewhere in the Preloader

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

1 participant