From f317cc8bc007978d7b135ddd1acdd7e3d1e582a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Schu=CC=88rrer?= <martin@schuerrer.org>
Date: Mon, 3 Mar 2014 17:44:55 +0100
Subject: Make exists? use bound values.

When we build a query with an inline value that is a numeric (e.g.
because it's out of range for an int4) PostgreSQL doesn't use an index
on the column, since it's now comparing numerics and not int4s.

This leads to a _very_ slow query.

When we use bound parameters instead of inline values PostgreSQL
raises numeric_value_out_of_range since no automatic coercion happens.
---
 .../lib/active_record/relation/finder_methods.rb         |  7 ++++++-
 activerecord/test/cases/finder_test.rb                   | 16 ++++++++++++++--
 2 files changed, 20 insertions(+), 3 deletions(-)

(limited to 'activerecord')

diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 7099bdd285..1ba7fc47c0 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -292,7 +292,12 @@ module ActiveRecord
       when Array, Hash
         relation = relation.where(conditions)
       else
-        relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
+        if conditions != :none
+          column = columns_hash[primary_key]
+          substitute = connection.substitute_at(column, bind_values.length)
+          relation = where(table[primary_key].eq(substitute))
+          relation.bind_values += [[column, conditions]]
+        end
       end
 
       connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index b1eded6494..78c4e02434 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -58,15 +58,27 @@ class FinderTest < ActiveRecord::TestCase
     assert_equal false, Topic.exists?(45)
     assert_equal false, Topic.exists?(Topic.new)
 
+    assert_raise(NoMethodError) { Topic.exists?([1,2]) }
+  end
+
+  def test_exists_fails_when_parameter_has_invalid_type
+    begin
+      assert_equal false, Topic.exists?(("9"*53).to_i) # number that's bigger than int
+      flunk if defined? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter and Topic.connection.is_a? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter # PostgreSQL does raise here
+    rescue ActiveRecord::StatementInvalid
+      # PostgreSQL complains that it can't coerce a numeric that's bigger than int into int
+    rescue Exception
+      flunk
+    end
+
     begin
       assert_equal false, Topic.exists?("foo")
+      flunk if defined? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter and Topic.connection.is_a? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter # PostgreSQL does raise here
     rescue ActiveRecord::StatementInvalid
       # PostgreSQL complains about string comparison with integer field
     rescue Exception
       flunk
     end
-
-    assert_raise(NoMethodError) { Topic.exists?([1,2]) }
   end
 
   def test_exists_does_not_select_columns_without_alias
-- 
cgit v1.2.3