Avoid query from calculations on contradictory relation

Previously calculations would make a query even when passed a
contradiction, like `User.where(id: []).count`. This commit optimzes
that to avoid an unnecessary query to the database.

Co-authored-by: John Hawthorn <jhawthorn@github.com>
Co-authored-by: Daniel Colson <composerinteralia@github.com>
This commit is contained in:
Luan Vieira 2022-05-05 10:56:36 -07:00
parent 4b5410a6aa
commit a1f76dd712
3 changed files with 48 additions and 2 deletions

View File

@ -1,3 +1,14 @@
* Avoid queries when performing calculations on contradictory relations.
Previously calculations would make a query even when passed a
contradiction, such as `User.where(id: []).count`. We no longer perform a
query in that scenario.
This applies to the following calculations: `count`, `sum`, `average`,
`minimum` and `maximum`
*Luan Vieira, John Hawthorn and Daniel Colson*
* Allow using aliased attributes with `insert_all`/`upsert_all`.
```ruby

View File

@ -342,6 +342,7 @@ module ActiveRecord
# Shortcut when limit is zero.
return 0 if limit_value == 0
relation = self
query_builder = build_count_subquery(spawn, column_name, distinct)
else
# PostgreSQL doesn't like ORDER BY when there are no GROUP BY
@ -356,8 +357,12 @@ module ActiveRecord
query_builder = relation.arel
end
query_result = skip_query_cache_if_necessary do
@klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
query_result = if relation.where_clause.contradiction?
ActiveRecord::Result.empty
else
skip_query_cache_if_necessary do
@klass.connection.select_all(query_builder, "#{@klass.name} #{operation.capitalize}", async: @async)
end
end
query_result.then do |result|

View File

@ -717,6 +717,36 @@ class CalculationsTest < ActiveRecord::TestCase
Account.where("credit_limit > 50").from("accounts").maximum(:credit_limit)
end
def test_no_queries_for_empty_relation_on_count
assert_queries(0) do
assert_equal 0, Post.where(id: []).count
end
end
def test_no_queries_for_empty_relation_on_sum
assert_queries(0) do
assert_equal 0, Post.where(id: []).sum(:tags_count)
end
end
def test_no_queries_for_empty_relation_on_average
assert_queries(0) do
assert_nil Post.where(id: []).average(:tags_count)
end
end
def test_no_queries_for_empty_relation_on_minimum
assert_queries(0) do
assert_nil Account.where(id: []).minimum(:id)
end
end
def test_no_queries_for_empty_relation_on_maximum
assert_queries(0) do
assert_nil Account.where(id: []).maximum(:id)
end
end
def test_maximum_with_not_auto_table_name_prefix_if_column_included
Company.create!(name: "test", contracts: [Contract.new(developer_id: 7)])