Skip to content

Commit 7bfdf9b

Browse files
Limit number of expressions in a list during a "homogenous in" operation (#2013)
* Limit number of expressions in a list during a "homogenous in" operation - ActiveRecord version 6.1.0.alpha - This fixes the following test failure: OracleEnhancedAdapter eager loading should load included association with more than 1000 records - The solution was to implement visit_Arel_Nodes_HomogeneousIn - See rails/rails@72fd0ba for the "homogenous in" explanation * Fix typo * Code formatting to placate Rubocop. * Bug fix: String#<< is an assignment operator, not a simple concatenation * Again, style changes to appease RuboCop. * Create a new test to try >1000 items in non-homogenous lists. * More style tips, sigh. Co-authored-by: James Nakagawa <[email protected]>
1 parent 64e484c commit 7bfdf9b

File tree

4 files changed

+126
-2
lines changed

4 files changed

+126
-2
lines changed

lib/arel/visitors/oracle.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,48 @@ def visit_Arel_Nodes_Except(o, collector)
8686
collector << " )"
8787
end
8888

89+
##
90+
# To avoid ORA-01795: maximum number of expressions in a list is 1000
91+
# tell ActiveRecord to limit us to 1000 ids at a time
92+
def visit_Arel_Nodes_HomogeneousIn(o, collector)
93+
in_clause_length = @connection.in_clause_length
94+
values = o.casted_values.map { |v| @connection.quote(v) }
95+
column_name = quote_table_name(o.table_name) + "." + quote_column_name(o.column_name)
96+
operator =
97+
if o.type == :in
98+
"IN ("
99+
else
100+
"NOT IN ("
101+
end
102+
103+
if !Array === values || values.length <= in_clause_length
104+
collector << column_name
105+
collector << operator
106+
107+
expr =
108+
if values.empty?
109+
@connection.quote(nil)
110+
else
111+
values.join(",")
112+
end
113+
114+
collector << expr
115+
collector << ")"
116+
else
117+
collector << "("
118+
values.each_slice(in_clause_length).each_with_index do |valuez, i|
119+
collector << " OR " unless i == 0
120+
collector << column_name
121+
collector << operator
122+
collector << valuez.join(",")
123+
collector << ")"
124+
end
125+
collector << ")"
126+
end
127+
128+
collector
129+
end
130+
89131
def visit_Arel_Nodes_In(o, collector)
90132
attr, values = o.left, o.right
91133

lib/arel/visitors/oracle12.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,48 @@ def visit_Arel_Nodes_Except(o, collector)
3939
collector << " )"
4040
end
4141

42+
##
43+
# To avoid ORA-01795: maximum number of expressions in a list is 1000
44+
# tell ActiveRecord to limit us to 1000 ids at a time
45+
def visit_Arel_Nodes_HomogeneousIn(o, collector)
46+
in_clause_length = @connection.in_clause_length
47+
values = o.casted_values.map { |v| @connection.quote(v) }
48+
column_name = quote_table_name(o.table_name) + "." + quote_column_name(o.column_name)
49+
operator =
50+
if o.type == :in
51+
"IN ("
52+
else
53+
"NOT IN ("
54+
end
55+
56+
if !Array === values || values.length <= in_clause_length
57+
collector << column_name
58+
collector << operator
59+
60+
expr =
61+
if values.empty?
62+
@connection.quote(nil)
63+
else
64+
values.join(",")
65+
end
66+
67+
collector << expr
68+
collector << ")"
69+
else
70+
collector << "("
71+
values.each_slice(in_clause_length).each_with_index do |valuez, i|
72+
collector << " OR " unless i == 0
73+
collector << column_name
74+
collector << operator
75+
collector << valuez.join(",")
76+
collector << ")"
77+
end
78+
collector << ")"
79+
end
80+
81+
collector
82+
end
83+
4284
def visit_Arel_Nodes_In(o, collector)
4385
attr, values = o.left, o.right
4486

spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@
8181
expect(ActiveRecord::Base.connection).to be_active
8282
end
8383

84-
it "should swith to specified schema" do
84+
it "should switch to specified schema" do
8585
ActiveRecord::Base.establish_connection(CONNECTION_WITH_SCHEMA_PARAMS)
8686
expect(ActiveRecord::Base.connection.current_schema).to eq(CONNECTION_WITH_SCHEMA_PARAMS[:schema].upcase)
8787
expect(ActiveRecord::Base.connection.current_user).to eq(CONNECTION_WITH_SCHEMA_PARAMS[:username].upcase)
8888
end
8989

90-
it "should swith to specified schema after reset" do
90+
it "should switch to specified schema after reset" do
9191
ActiveRecord::Base.connection.reset!
9292
expect(ActiveRecord::Base.connection.current_schema).to eq(CONNECTION_WITH_SCHEMA_PARAMS[:schema].upcase)
9393
end

spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,46 @@ class ::TestComment < ActiveRecord::Base
193193
end
194194
end
195195

196+
describe "lists" do
197+
before(:all) do
198+
schema_define do
199+
create_table :test_posts do |t|
200+
t.string :title
201+
end
202+
end
203+
class ::TestPost < ActiveRecord::Base
204+
has_many :test_comments
205+
end
206+
@ids = (1..1010).to_a
207+
TestPost.transaction do
208+
@ids.each do |id|
209+
TestPost.create!(id: id, title: "Title #{id}")
210+
end
211+
end
212+
end
213+
214+
after(:all) do
215+
schema_define do
216+
drop_table :test_posts
217+
end
218+
Object.send(:remove_const, "TestPost")
219+
ActiveRecord::Base.clear_cache!
220+
end
221+
222+
##
223+
# See this GitHub issue for an explanation of homogenous lists.
224+
# https://github.com/rails/rails/commit/72fd0bae5948c1169411941aeea6fef4c58f34a9
225+
it "should allow more than 1000 items in a list where the list is homogenous" do
226+
posts = TestPost.where(id: @ids).to_a
227+
expect(posts.size).to eq(@ids.size)
228+
end
229+
230+
it "should allow more than 1000 items in a list where the list is non-homogenous" do
231+
posts = TestPost.where(id: [*@ids, nil]).to_a
232+
expect(posts.size).to eq(@ids.size)
233+
end
234+
end
235+
196236
describe "with statement pool" do
197237
before(:all) do
198238
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS.merge(statement_limit: 3))

0 commit comments

Comments
 (0)