Skip to content

Commit

Permalink
Merge pull request #26121 from MaxLap/fix_count_with_left_joins
Browse files Browse the repository at this point in the history
Fix count which would sometimes force a DISTINCT
  • Loading branch information
rafaelfranca committed Aug 17, 2016
2 parents af4dfa9 + 0aae780 commit 2af63c9
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 16 deletions.
5 changes: 5 additions & 0 deletions activerecord/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
* Doing count on relations that contain LEFT OUTER JOIN Arel node no longer
force a DISTINCT. This solves issues when using count after a left_joins.

*Maxime Handfield Lapointe*

* RecordNotFound raised by association.find exposes `id`, `primary_key` and
`model` methods to be consistent with RecordNotFound raised by Record.find.

Expand Down
10 changes: 4 additions & 6 deletions activerecord/lib/active_record/relation/calculations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ def calculate(operation, column_name)
end

if has_include?(column_name)
construct_relation_for_association_calculations.calculate(operation, column_name)
relation = construct_relation_for_association_calculations
relation = relation.distinct if operation.to_s.downcase == "count"

relation.calculate(operation, column_name)
else
perform_calculation(operation, column_name)
end
Expand Down Expand Up @@ -198,11 +201,6 @@ def perform_calculation(operation, column_name)

if operation == "count"
column_name ||= select_for_count

unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
distinct = true
end

column_name = primary_key if column_name == :all && distinct
distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,32 @@ def test_construct_finder_sql_does_not_table_name_collide_on_duplicate_associati
end
end

def test_construct_finder_sql_executes_a_left_outer_join
assert_not_equal Author.count, Author.joins(:posts).count
assert_equal Author.count, Author.left_outer_joins(:posts).count
def test_left_outer_joins_count_is_same_as_size_of_loaded_results
assert_equal 17, Post.left_outer_joins(:comments).to_a.size
assert_equal 17, Post.left_outer_joins(:comments).count
end

def test_left_outer_join_by_left_joins
assert_not_equal Author.count, Author.joins(:posts).count
assert_equal Author.count, Author.left_joins(:posts).count
def test_left_joins_aliases_left_outer_joins
assert_equal Post.left_outer_joins(:comments).to_sql, Post.left_joins(:comments).to_sql
end

def test_left_outer_joins_return_has_value_for_every_comment
all_post_ids = Post.pluck(:id)
assert_equal all_post_ids, all_post_ids & Post.left_outer_joins(:comments).pluck(:id)
end

def test_left_outer_joins_actually_does_a_left_outer_join
queries = capture_sql { Author.left_outer_joins(:posts).to_a }
assert queries.any? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end

def test_construct_finder_sql_ignores_empty_left_outer_joins_hash
queries = capture_sql { Author.left_outer_joins({}) }
queries = capture_sql { Author.left_outer_joins({}).to_a }
assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end

def test_construct_finder_sql_ignores_empty_left_outer_joins_array
queries = capture_sql { Author.left_outer_joins([]) }
queries = capture_sql { Author.left_outer_joins([]).to_a }
assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end

Expand Down
4 changes: 2 additions & 2 deletions activerecord/test/cases/relation_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def test_merging_readonly_false
def test_relation_merging_with_merged_joins_as_symbols
special_comments_with_ratings = SpecialComment.joins(:ratings)
posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
assert_equal({ 2=>1, 4=>3, 5=>1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
end

def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent
Expand Down Expand Up @@ -273,7 +273,7 @@ def test_relation_merging_with_merged_joins_as_strings
join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id"
special_comments_with_ratings = SpecialComment.joins join_string
posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
assert_equal({ 2=>1, 4=>3, 5=>1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
end

class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value
Expand Down

0 comments on commit 2af63c9

Please sign in to comment.