Skip to content

Commit

Permalink
Merge pull request #41419 from smartygus/activerecord-findermethod-in…
Browse files Browse the repository at this point in the history
…clude-with-having

Fix #41417 Handle Relations with having referencing aliased selects in #include?
  • Loading branch information
rafaelfranca committed Feb 12, 2021
2 parents 5b0fe37 + 644d694 commit afa58dc
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 1 deletion.
31 changes: 31 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,34 @@
* Skip optimised #exist? query when #include? is called on a relation
with a having clause

Relations that have aliased select values AND a having clause that
references an aliased select value would generate an error when
#include? was called, due to an optimisation that would generate
call #exists? on the relation instead, which effectively alters
the select values of the query (and thus removes the aliased select
values), but leaves the having clause intact. Because the having
clause is then referencing an aliased column that is no longer
present in the simplified query, an ActiveRecord::InvalidStatement
error was raised.

An sample query affected by this problem:

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

This change adds an addition check to the condition that skips the
simplified #exists? query, which simply checks for the presence of
a having clause.

Fixes #41417

*Michael Smart*

* Increment postgres prepared statement counter before making a prepared statement, so if the statement is aborted
without Rails knowledge (e.g., if app gets kill -9d during long-running query or due to Rack::Timeout), app won't end
up in perpetual crash state for being inconsistent with Postgres.
Expand Down
2 changes: 1 addition & 1 deletion activerecord/lib/active_record/relation/finder_methods.rb
Expand Up @@ -352,7 +352,7 @@ def exists?(conditions = :none)
# compared to the records in memory. If the relation is unloaded, an
# efficient existence query is performed, as in #exists?.
def include?(record)
if loaded? || offset_value || limit_value
if loaded? || offset_value || limit_value || having_clause.any?
records.include?(record)
else
record.is_a?(klass) && exists?(record.id)
Expand Down
9 changes: 9 additions & 0 deletions activerecord/test/cases/finder_test.rb
Expand Up @@ -412,6 +412,15 @@ def test_include_on_unloaded_relation_with_limit
assert_equal true, Customer.order(id: :desc).limit(2).include?(mary)
end

def test_include_on_unloaded_relation_with_having_referencing_aliased_select
skip if current_adapter?(:PostgreSQLAdapter)
bob = authors(:bob)
mary = authors(:mary)

assert_equal false, Author.select("COUNT(*) as total_posts", "authors.*").joins(:posts).group(:id).having("total_posts > 2").include?(bob)
assert_equal true, Author.select("COUNT(*) as total_posts", "authors.*").joins(:posts).group(:id).having("total_posts > 2").include?(mary)
end

def test_include_on_loaded_relation_with_match
customers = Customer.where(name: "David").load
david = customers(:david)
Expand Down

0 comments on commit afa58dc

Please sign in to comment.