From 07dcd99a5a5b0ccbaa91e0d18e3b7b165fecffb1 Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Fri, 8 Feb 2019 21:28:50 +0900
Subject: Fix `relation.exists?` with giving both `distinct` and `offset`

The `distinct` affects (reduces) rows of the result, so it is important
part when both `distinct` and `offset` are given.

Replacing SELECT clause to `1 AS one` and removing `distinct` and
`order` is just optimization for the `exists?`, we should not apply the
optimization for that case.

Fixes #35191.
---
 activerecord/lib/active_record/relation/finder_methods.rb | 12 ++++++++----
 activerecord/test/cases/finder_test.rb                    | 10 ++++++++++
 2 files changed, 18 insertions(+), 4 deletions(-)

diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 8f1065c1e7..91cfc4e849 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -307,8 +307,6 @@ module ActiveRecord
 
       return false if !conditions || limit_value == 0
 
-      conditions = sanitize_forbidden_attributes(conditions)
-
       if eager_loading?
         relation = apply_join_dependency(eager_loading: false)
         return relation.exists?(conditions)
@@ -316,7 +314,7 @@ module ActiveRecord
 
       relation = construct_relation_for_exists(conditions)
 
-      skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false
+      skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists") } ? true : false
     end
 
     # This method is called whenever no records are found with either a single
@@ -354,7 +352,13 @@ module ActiveRecord
       end
 
       def construct_relation_for_exists(conditions)
-        relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
+        conditions = sanitize_forbidden_attributes(conditions)
+
+        if distinct_value && offset_value
+          relation = limit(1)
+        else
+          relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
+        end
 
         case conditions
         when Array, Hash
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 6af2a43c7f..4040682280 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -271,6 +271,16 @@ class FinderTest < ActiveRecord::TestCase
     assert_equal true, Topic.exists?({})
   end
 
+  def test_exists_with_distinct_and_offset_and_joins
+    assert Post.left_joins(:comments).distinct.offset(10).exists?
+    assert_not Post.left_joins(:comments).distinct.offset(11).exists?
+  end
+
+  def test_exists_with_distinct_and_offset_and_select
+    assert Post.select(:body).distinct.offset(3).exists?
+    assert_not Post.select(:body).distinct.offset(4).exists?
+  end
+
   # Ensure +exists?+ runs without an error by excluding distinct value.
   # See https://github.com/rails/rails/pull/26981.
   def test_exists_with_order_and_distinct
-- 
cgit v1.2.3