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

ActiveRecord::StatementInvalid Error on ActiveRecord::Relation#include? with having condition referencing aliased select values present #41417

Closed
smartygus opened this issue Feb 11, 2021 · 0 comments · May be fixed by freesteph/watchdog#16

Comments

@smartygus
Copy link
Contributor

@smartygus smartygus commented Feb 11, 2021

When calling #include? on an ActiveRecord::Relation that has an aliased select value AND a having condition that references said aliased select value, an ActiveRecord::StatementInvalid error is raised due to not being able to find the aliased column.

Example query that meets these criteria would be:

Author.select('COUNT(*) as total_posts', 'authors.*').joins(:posts).group(:id).having('total_posts > 2').include?(Author.first)

This problem appears to have been introduced with the following commit: 166b63e

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" }

  # Activate the gem you are reporting the issue against.
  #gem "activerecord", "6.0.3.4" # no exception raised
  #gem "activerecord", "6.1.2" # exception from 6.1.x onwards
  gem "rails", github: "rails/rails", branch: "main"
  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 :posts, force: true do |t|

  end

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

  create_table :users, force: true do |t|
  end
end

class Post < ActiveRecord::Base
  has_many :comments
  scope :active, -> { select('COUNT(*) as total_comments, posts.id').joins(:comments).group(:id).having('total_comments > 3') }
end

class Comment < ActiveRecord::Base
  belongs_to :post
  belongs_to :user
end

class BugTest < Minitest::Test
  def test_exception_should_not_be_raised
    post1 = Post.create!
    post2 = Post.create!
    5.times do
      post1.comments << Comment.create!
    end
    3.times do
      post2.comments << Comment.create!
    end

    assert_equal 5, post1.comments.count
    assert_equal 3, post2.comments.count

    # Raises exception
    assert_equal true, Post.active.include?(post1)
    assert_equal false, Post.active.include?(post2)
  end
end

Expected behavior

No exception should be raised, and true or false should be returned based on whether or not the relation contains the record given as parameter

Actual behavior

Error:
BugTest#test_exception_should_not_be_raised:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: total_comments
    /var/lib/gems/2.7.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:147:in `initialize'
    /var/lib/gems/2.7.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:147:in `new'
    /var/lib/gems/2.7.0/gems/sqlite3-1.4.2/lib/sqlite3/database.rb:147:in `prepare'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb:46:in `block (2 levels) in exec_query'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activesupport/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activesupport/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activesupport/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb:43:in `block in exec_query'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb:724:in `block (2 levels) in log'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb:723:in `block in log'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activesupport/lib/active_support/notifications/instrumenter.rb:24:in `instrument'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb:714:in `log'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb:42:in `exec_query'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:547:in `select'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:66:in `select_all'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb:110:in `select_all'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:91:in `select_rows'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/relation/finder_methods.rb:346:in `block in exists?'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/relation.rb:890:in `skip_query_cache_if_necessary'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/relation/finder_methods.rb:346:in `exists?'
    /home/vagrant/.bundle/ruby/2.7.0/rails-9eedc375d909/activerecord/lib/active_record/relation/finder_methods.rb:358:in `include?'
    include_on_having_query_test.rb:67:in `test_exception_should_not_be_raised'

System configuration

Rails version: 6.1.2, main

Ruby version: 2.7.1

Will try my hand at a pull request to fix the issue.

rafaelfranca added a commit that referenced this issue Feb 12, 2021
…clude-with-having

Fix #41417 Handle Relations with having referencing aliased selects in #include?
rafaelfranca added a commit that referenced this issue Feb 12, 2021
…clude-with-having

Fix #41417 Handle Relations with having referencing aliased selects in #include?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment