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

PostgreSQL: Fix GROUP BY with ORDER BY virtual count attribute #36506

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,9 @@
* PostgreSQL: Fix GROUP BY with ORDER BY virtual count attribute.

Fixes #36022.

*Ryuta Kamizono*

* Make ActiveRecord `ConnectionPool.connections` method thread-safe.

Fixes #36465.
Expand Down
25 changes: 15 additions & 10 deletions activerecord/lib/active_record/relation/query_methods.rb
Expand Up @@ -1159,8 +1159,9 @@ def arel_columns(columns)
columns.flat_map do |field|
case field
when Symbol
field = field.to_s
arel_column(field, &connection.method(:quote_table_name))
arel_column(field.to_s) do |attr_name|
connection.quote_table_name(attr_name)
end
when String
arel_column(field, &:itself)
when Proc
Expand Down Expand Up @@ -1268,20 +1269,14 @@ def preprocess_order_args(order_args)
order_args.map! do |arg|
case arg
when Symbol
arg = arg.to_s
arel_column(arg) {
Arel.sql(connection.quote_table_name(arg))
}.asc
order_column(arg.to_s).asc
when Hash
arg.map { |field, dir|
case field
when Arel::Nodes::SqlLiteral
field.send(dir.downcase)
else
field = field.to_s
arel_column(field) {
Arel.sql(connection.quote_table_name(field))
}.send(dir.downcase)
order_column(field.to_s).send(dir.downcase)
end
}
else
Expand All @@ -1290,6 +1285,16 @@ def preprocess_order_args(order_args)
end.flatten!
end

def order_column(field)
arel_column(field) do |attr_name|
if attr_name == "count" && !group_values.empty?
arel_attribute(attr_name)
else
Arel.sql(connection.quote_table_name(attr_name))
end
end
end

# Checks to make sure that the arguments are not blank. Note that if some
# blank-like object were initially passed into the query method, then this
# method will not raise an error.
Expand Down
6 changes: 6 additions & 0 deletions activerecord/test/cases/calculations_test.rb
Expand Up @@ -767,6 +767,12 @@ def test_pluck_with_join
assert_equal [[2, 2], [4, 4]], Reply.includes(:topic).pluck(:id, :"topics.id")
end

def test_group_by_with_order_by_virtual_count_attribute
expected = { "SpecialPost" => 1, "StiPost" => 2 }
actual = Post.group(:type).order(:count).limit(2).maximum(:comments_count)
assert_equal expected, actual
end if current_adapter?(:PostgreSQLAdapter)

def test_group_by_with_limit
expected = { "Post" => 8, "SpecialPost" => 1 }
actual = Post.includes(:comments).group(:type).order(:type).limit(2).count("comments.id")
Expand Down
4 changes: 2 additions & 2 deletions activerecord/test/cases/relations_test.rb
Expand Up @@ -1955,8 +1955,8 @@ def test_destroy_by
test "joins with order by custom attribute" do
companies = Company.create!([{ name: "test1" }, { name: "test2" }])
companies.each { |company| company.contracts.create! }
assert_equal companies, Company.joins(:contracts).order(:metadata)
assert_equal companies.reverse, Company.joins(:contracts).order(metadata: :desc)
assert_equal companies, Company.joins(:contracts).order(:metadata, :count)
assert_equal companies.reverse, Company.joins(:contracts).order(metadata: :desc, count: :desc)
end

test "delegations do not leak to other classes" do
Expand Down
1 change: 1 addition & 0 deletions activerecord/test/schema/schema.rb
Expand Up @@ -261,6 +261,7 @@
t.references :developer, index: false
t.references :company, index: false
t.string :metadata
t.integer :count
end

create_table :customers, force: true do |t|
Expand Down