Skip to content

6.0.0.rc1: ActiveRecord occurs an error when using through and merge #36293

@sinsoku

Description

@sinsoku

Steps to reproduce

# frozen_string_literal: true

require "bundler/inline"

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

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

  if ENV["RAILS_BRANCH"]
    gem "rails", github: "rails/rails", branch: ENV["RAILS_BRANCH"]
  else
    gem "rails", "5.2.3"
  end
  gem "sqlite3"
end

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

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table(:users, force: true)

  create_table :posts, force: true do |t|
    t.integer :user_id
  end

  create_table :comments, force: true do |t|
    t.integer :post_id
  end
end

class User < ActiveRecord::Base
  has_many :posts
  has_many :comments, through: :posts
end

class Post < ActiveRecord::Base
  belongs_to :user
  has_many :comments

  scope :no_comments, lambda {
    left_joins(:comments).where(comments: { id: nil })
  }
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

puts
puts "ActiveRecord.version = #{ActiveRecord.version}"
puts

class BugTest < Minitest::Test
  def test_to_sql
    user = User.create(id: 1)

    expected = 'SELECT "comments".* FROM "comments" INNER JOIN "posts" ON "comments"."post_id" = "posts"."id" LEFT OUTER JOIN "comments" "comments_posts" ON "comments_posts"."post_id" = "posts"."id" WHERE "posts"."user_id" = 1 AND "comments"."id" IS NULL'
    assert_equal expected, user.comments.merge(Post.no_comments).to_sql
  end

  def test_to_execute_sql
    user = User.create(id: 2)

    assert_equal 0, user.comments.merge(Post.no_comments).count
  end
end

v5.2.3

$ ruby issue.rb
ActiveRecord.version = 5.2.3

Run options: --seed 43003

# Running:

D, [2019-05-17T13:54:35.556394 #65246] DEBUG -- :    (0.1ms)  begin transaction
D, [2019-05-17T13:54:35.558423 #65246] DEBUG -- :   User Create (0.3ms)  INSERT INTO "users" ("id") VALUES (?)  [["id", 2]]
D, [2019-05-17T13:54:35.559239 #65246] DEBUG -- :    (0.1ms)  commit transaction
D, [2019-05-17T13:54:35.594663 #65246] DEBUG -- :    (0.3ms)  SELECT COUNT(*) FROM "comments" INNER JOIN "posts" ON "comments"."post_id" = "posts"."id" LEFT OUTER JOIN "comments" "comments_posts" ON "comments_posts"."post_id" = "posts"."id" WHERE "posts"."user_id" = ? AND "comments"."id" IS NULL  [["user_id", 2]]
.D, [2019-05-17T13:54:35.595758 #65246] DEBUG -- :    (0.1ms)  begin transaction
D, [2019-05-17T13:54:35.596739 #65246] DEBUG -- :   User Create (0.2ms)  INSERT INTO "users" ("id") VALUES (?)  [["id", 1]]
D, [2019-05-17T13:54:35.597295 #65246] DEBUG -- :    (0.1ms)  commit transaction
.

Finished in 0.050594s, 39.5304 runs/s, 39.5304 assertions/s.
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

6-0-stable

$ RAILS_BRANCH=6-0-stable ruby issue.rb
ActiveRecord.version = 6.0.0.rc1

Run options: --seed 2131

# Running:

D, [2019-05-17T13:55:29.849955 #65313] DEBUG -- :    (0.1ms)  begin transaction
D, [2019-05-17T13:55:29.850242 #65313] DEBUG -- :   User Create (0.1ms)  INSERT INTO "users" ("id") VALUES (?)  [["id", 2]]
D, [2019-05-17T13:55:29.850610 #65313] DEBUG -- :    (0.1ms)  commit transaction
D, [2019-05-17T13:55:29.885159 #65313] DEBUG -- :    (0.3ms)  SELECT COUNT(*) FROM "comments" LEFT OUTER JOIN "comments" "comments_posts" ON "comments_posts"."post_id" = "posts"."id" INNER JOIN "posts" ON "comments"."post_id" = "posts"."id" WHERE "posts"."user_id" = ? AND "comments"."id" IS NULL  [["user_id", 2]]
E

Error:
BugTest#test_to_execute_sql:
ActiveRecord::StatementInvalid: SQLite3::SQLException: ON clause references tables to its right



rails test issue.rb:68

D, [2019-05-17T13:55:29.886725 #65313] DEBUG -- :    (0.1ms)  begin transaction
D, [2019-05-17T13:55:29.887007 #65313] DEBUG -- :   User Create (0.1ms)  INSERT INTO "users" ("id") VALUES (?)  [["id", 1]]
D, [2019-05-17T13:55:29.887502 #65313] DEBUG -- :    (0.1ms)  commit transaction
F

Failure:
BugTest#test_to_sql [issue.rb:65]:
--- expected
+++ actual
@@ -1 +1 @@
-"SELECT \"comments\".* FROM \"comments\" INNER JOIN \"posts\" ON \"comments\".\"post_id\" = \"posts\".\"id\" LEFT OUTER JOIN \"comments\" \"comments_posts\" ON \"comments_posts\".\"post_id\" = \"posts\".\"id\" WHERE \"posts\".\"user_id\" = 1 AND \"comments\".\"id\" IS NULL"
+"SELECT \"comments\".* FROM \"comments\" LEFT OUTER JOIN \"comments\" \"comments_posts\" ON \"comments_posts\".\"post_id\" = \"posts\".\"id\" INNER JOIN \"posts\" ON \"comments\".\"post_id\" = \"posts\".\"id\" WHERE \"posts\".\"user_id\" = 1 AND \"comments\".\"id\" IS NULL"



rails test issue.rb:61



Finished in 0.080781s, 24.7583 runs/s, 12.3791 assertions/s.
2 runs, 1 assertions, 1 failures, 1 errors, 0 skips

Expected behavior

I expected the same behavior as v5.2.3.

Actual behavior

ActiveRecord builds a query which the order of INNER JOIN and LEFT OUTER JOIN reversed.
This query occurs an error because it can not reference the table.

System configuration

Rails version: 6-0-stable

Ruby version: ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin18]

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions