aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorRyuta Kamizono <kamipo@gmail.com>2017-07-19 18:10:04 +0900
committerRyuta Kamizono <kamipo@gmail.com>2017-07-22 08:40:16 +0900
commita265d4b29cf9c1be84603ec53a6f8b17b53321a9 (patch)
tree1a6201eaaedb9c03d3c6b388229731268f089998 /activerecord
parentaf08044d6a6aa87d3b389f63c78564be2f60b1ab (diff)
downloadrails-a265d4b29cf9c1be84603ec53a6f8b17b53321a9.tar.gz
rails-a265d4b29cf9c1be84603ec53a6f8b17b53321a9.tar.bz2
rails-a265d4b29cf9c1be84603ec53a6f8b17b53321a9.zip
Fix `COUNT(DISTINCT ...)` with `ORDER BY` and `LIMIT`
Since #26972, `ORDER BY` is kept if `LIMIT` is presented for performance. But in most SQL servers (e.g. PostgreSQL, SQL Server, etc), `ORDER BY` expressions must appear in select list for `SELECT DISTINCT`. We should not replace existing select list in that case.
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md4
-rw-r--r--activerecord/lib/active_record/collection_cache_key.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb29
-rw-r--r--activerecord/test/cases/calculations_test.rb12
5 files changed, 40 insertions, 11 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index ea9f860b95..afc459ef68 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Fix `COUNT(DISTINCT ...)` with `ORDER BY` and `LIMIT` to keep the existing select list.
+
+ *Ryuta Kamizono*
+
* When a `has_one` association is destroyed by `dependent: destroy`,
`destroyed_by_association` will now be set to the reflection, matching the
behaviour of `has_many` associations.
diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb
index 62948225af..c94897580c 100644
--- a/activerecord/lib/active_record/collection_cache_key.rb
+++ b/activerecord/lib/active_record/collection_cache_key.rb
@@ -16,7 +16,7 @@ module ActiveRecord
column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
- if collection.limit_value || collection.offset_value
+ if collection.has_limit_or_offset?
query = collection.spawn
query.select_values = [column]
subquery_alias = "subquery_for_cache_key"
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 90e4fe98d5..7b4e44f61c 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -648,6 +648,10 @@ module ActiveRecord
@values == klass.unscoped.values
end
+ def has_limit_or_offset? # :nodoc:
+ limit_value || offset_value
+ end
+
protected
def load_records(records)
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index c105abe774..a4714f138a 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -131,7 +131,7 @@ module ActiveRecord
def calculate(operation, column_name)
if has_include?(column_name)
relation = construct_relation_for_association_calculations
- relation = relation.distinct if operation.to_s.downcase == "count"
+ relation.distinct! if operation.to_s.downcase == "count"
relation.calculate(operation, column_name)
else
@@ -214,8 +214,13 @@ module ActiveRecord
if operation == "count"
column_name ||= select_for_count
- column_name = primary_key if column_name == :all && distinct
- distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
+ if column_name == :all
+ if distinct && !(has_limit_or_offset? && order_values.any?)
+ column_name = primary_key
+ end
+ elsif column_name =~ /\s*DISTINCT[\s(]+/i
+ distinct = nil
+ end
end
if group_values.any?
@@ -242,7 +247,7 @@ module ActiveRecord
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
column_alias = column_name
- if operation == "count" && (limit_value || offset_value)
+ if operation == "count" && has_limit_or_offset?
# Shortcut when limit is zero.
return 0 if limit_value == 0
@@ -381,14 +386,18 @@ module ActiveRecord
end
def build_count_subquery(relation, column_name, distinct)
- column_alias = Arel.sql("count_column")
- subquery_alias = Arel.sql("subquery_for_count")
+ relation.select_values = [
+ if column_name == :all
+ distinct ? Arel.star : Arel.sql("1")
+ else
+ column_alias = Arel.sql("count_column")
+ aggregate_column(column_name).as(column_alias)
+ end
+ ]
- aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
- relation.select_values = [aliased_column]
- subquery = relation.arel.as(subquery_alias)
+ subquery = relation.arel.as(Arel.sql("subquery_for_count"))
+ select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
- select_value = operation_over_aggregate_column(column_alias, "count", distinct)
Arel::SelectManager.new(subquery).project(select_value)
end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index dd6815b149..0541b78f0f 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -236,6 +236,18 @@ class CalculationsTest < ActiveRecord::TestCase
end
end
+ def test_distinct_count_with_order_and_limit
+ assert_equal 4, Account.distinct.order(:firm_id).limit(4).count
+ end
+
+ def test_distinct_count_with_order_and_offset
+ assert_equal 4, Account.distinct.order(:firm_id).offset(2).count
+ end
+
+ def test_distinct_count_with_order_and_limit_and_offset
+ assert_equal 4, Account.distinct.order(:firm_id).limit(4).offset(2).count
+ end
+
def test_should_group_by_summed_field_having_condition
c = Account.group(:firm_id).having("sum(credit_limit) > 50").sum(:credit_limit)
assert_nil c[1]