From a50eacb03cf3df1d1cbc227ea0115901e0327f1c Mon Sep 17 00:00:00 2001
From: Ryuta Kamizono <kamipo@gmail.com>
Date: Sun, 9 Sep 2018 03:30:45 +0900
Subject: Eager loading/preloading should be worked regardless of large number
 of records

Since 213796f, bind params are used for IN clause if enabled prepared
statements.

Unfortunately, most adapter modules have a limitation for # of bind
params (mysql2 65535, pg 65535, sqlite3 250000). So if eager loading
large number of records at once, that query couldn't be sent to the
database.

Since eager loading/preloading queries are auto-generated by Active
Record itself, so it should be worked regardless of large number of
records like as before.

Fixes #33702.
---
 .../lib/active_record/relation/predicate_builder.rb         |  6 +++++-
 .../relation/predicate_builder/array_handler.rb             |  9 +++++----
 activerecord/test/cases/associations/eager_test.rb          | 13 +++++++++++++
 activerecord/test/fixtures/citations.yml                    |  4 ++++
 activerecord/test/models/citation.rb                        |  1 +
 activerecord/test/schema/schema.rb                          |  1 +
 6 files changed, 29 insertions(+), 5 deletions(-)
 create mode 100644 activerecord/test/fixtures/citations.yml

(limited to 'activerecord')

diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index f734cd0ad8..cb70b8bcde 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -57,10 +57,14 @@ module ActiveRecord
     end
 
     def build_bind_attribute(column_name, value)
-      attr = Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
+      attr = build_query_attribute(column_name, value)
       Arel::Nodes::BindParam.new(attr)
     end
 
+    def build_query_attribute(column_name, value)
+      Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
+    end
+
     protected
       def expand_from_hash(attributes)
         return ["1=0"] if attributes.empty?
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index e5191fa38a..0895b9fba6 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -21,10 +21,11 @@ module ActiveRecord
           when 0 then NullPredicate
           when 1 then predicate_builder.build(attribute, values.first)
           else
-            bind_values = values.map do |v|
-              predicate_builder.build_bind_attribute(attribute.name, v)
-            end
-            attribute.in(bind_values)
+            values.map! do |v|
+              attr = predicate_builder.build_query_attribute(attribute.name, v)
+              attr.value_for_database if attr.boundable?
+            end.compact!
+            values.empty? ? NullPredicate : attribute.in(values)
           end
 
         unless nils.empty?
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index a1fba8dc66..ca902131f4 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -18,6 +18,7 @@ require "models/job"
 require "models/subscriber"
 require "models/subscription"
 require "models/book"
+require "models/citation"
 require "models/developer"
 require "models/computer"
 require "models/project"
@@ -29,6 +30,18 @@ require "models/sponsor"
 require "models/mentor"
 require "models/contract"
 
+class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
+  fixtures :citations
+
+  def test_preloading_too_many_ids
+    assert_equal Citation.count, Citation.preload(:citations).to_a.size
+  end
+
+  def test_eager_loading_too_may_ids
+    assert_equal Citation.count, Citation.eager_load(:citations).offset(0).size
+  end
+end
+
 class EagerAssociationTest < ActiveRecord::TestCase
   fixtures :posts, :comments, :authors, :essays, :author_addresses, :categories, :categories_posts,
             :companies, :accounts, :tags, :taggings, :people, :readers, :categorizations,
diff --git a/activerecord/test/fixtures/citations.yml b/activerecord/test/fixtures/citations.yml
new file mode 100644
index 0000000000..d31cb8efa1
--- /dev/null
+++ b/activerecord/test/fixtures/citations.yml
@@ -0,0 +1,4 @@
+<% 65536.times do |i| %>
+fixture_no_<%= i %>:
+  id: <%= i %>
+<% end %>
diff --git a/activerecord/test/models/citation.rb b/activerecord/test/models/citation.rb
index 3d786f27eb..cee3d18173 100644
--- a/activerecord/test/models/citation.rb
+++ b/activerecord/test/models/citation.rb
@@ -2,4 +2,5 @@
 
 class Citation < ActiveRecord::Base
   belongs_to :reference_of, class_name: "Book", foreign_key: :book2_id
+  has_many :citations
 end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 266e55f682..27c7348f5c 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -160,6 +160,7 @@ ActiveRecord::Schema.define do
   create_table :citations, force: true do |t|
     t.column :book1_id, :integer
     t.column :book2_id, :integer
+    t.references :citation
   end
 
   create_table :clubs, force: true do |t|
-- 
cgit v1.2.3