Skip to content

Commit b6ab786

Browse files
authored
Merge pull request #2780 from yahonda/move-homogeneous-in-to-common
Move visit_Arel_Nodes_HomogeneousIn into Arel::Visitors::OracleCommon (#2663 task A)
2 parents b7eaa60 + affd462 commit b6ab786

6 files changed

Lines changed: 128 additions & 118 deletions

File tree

lib/arel/visitors/oracle.rb

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -107,54 +107,6 @@ def value_before_type_cast(expr)
107107
end
108108
end
109109

110-
##
111-
# To avoid ORA-01795: maximum number of expressions in a list is 1000
112-
# tell ActiveRecord to limit us to 1000 ids at a time
113-
def visit_Arel_Nodes_HomogeneousIn(o, collector)
114-
collector.preparable = false
115-
in_clause_length = @connection.in_clause_length
116-
values = o.casted_values.map { |v| @connection.quote(v) }
117-
operator =
118-
if o.type == :in
119-
" IN ("
120-
else
121-
" NOT IN ("
122-
end
123-
124-
if !Array === values || values.length <= in_clause_length
125-
visit o.left, collector
126-
collector << operator
127-
128-
expr =
129-
if values.empty?
130-
@connection.quote(nil)
131-
else
132-
values.join(",")
133-
end
134-
135-
collector << expr
136-
collector << ")"
137-
else
138-
separator =
139-
if o.type == :in
140-
" OR "
141-
else
142-
" AND "
143-
end
144-
collector << "("
145-
values.each_slice(in_clause_length).each_with_index do |valuez, i|
146-
collector << separator unless i == 0
147-
visit o.left, collector
148-
collector << operator
149-
collector << valuez.join(",")
150-
collector << ")"
151-
end
152-
collector << ")"
153-
end
154-
155-
collector
156-
end
157-
158110
def visit_Arel_Nodes_UpdateStatement(o, collector)
159111
# Oracle does not allow ORDER BY/LIMIT in UPDATEs.
160112
if o.orders.any? && o.limit.nil?

lib/arel/visitors/oracle12.rb

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -55,52 +55,6 @@ def visit_Arel_Nodes_Except(o, collector)
5555
collector << " )"
5656
end
5757

58-
##
59-
# To avoid ORA-01795: maximum number of expressions in a list is 1000
60-
# tell ActiveRecord to limit us to 1000 ids at a time
61-
def visit_Arel_Nodes_HomogeneousIn(o, collector)
62-
collector.preparable = false
63-
in_clause_length = @connection.in_clause_length
64-
values = o.casted_values.map { |v| @connection.quote(v) }
65-
operator =
66-
if o.type == :in
67-
" IN ("
68-
else
69-
" NOT IN ("
70-
end
71-
72-
if !Array === values || values.length <= in_clause_length
73-
visit o.left, collector
74-
collector << operator
75-
76-
expr =
77-
if values.empty?
78-
@connection.quote(nil)
79-
else
80-
values.join(",")
81-
end
82-
83-
collector << expr
84-
collector << ")"
85-
else
86-
separator =
87-
if o.type == :in
88-
" OR "
89-
else
90-
" AND "
91-
end
92-
collector << "("
93-
values.each_slice(in_clause_length).each_with_index do |valuez, i|
94-
collector << separator unless i == 0
95-
visit o.left, collector
96-
collector << operator
97-
collector << valuez.join(",")
98-
collector << ")"
99-
end
100-
collector << ")"
101-
end
102-
end
103-
10458
def visit_Arel_Nodes_UpdateStatement(o, collector)
10559
# Oracle does not allow ORDER BY/LIMIT in UPDATEs.
10660
if o.orders.any? && o.limit.nil?

lib/arel/visitors/oracle_common.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,53 @@ def split_order_string(string)
9898
array
9999
end
100100

101+
# To avoid ORA-01795: maximum number of expressions in a list is 1000
102+
# tell ActiveRecord to limit us to 1000 ids at a time
103+
def visit_Arel_Nodes_HomogeneousIn(o, collector)
104+
collector.preparable = false
105+
in_clause_length = @connection.in_clause_length
106+
values = o.casted_values.map { |v| @connection.quote(v) }
107+
operator =
108+
if o.type == :in
109+
" IN ("
110+
else
111+
" NOT IN ("
112+
end
113+
114+
if values.length <= in_clause_length
115+
visit o.left, collector
116+
collector << operator
117+
118+
expr =
119+
if values.empty?
120+
@connection.quote(nil)
121+
else
122+
values.join(",")
123+
end
124+
125+
collector << expr
126+
collector << ")"
127+
else
128+
separator =
129+
if o.type == :in
130+
" OR "
131+
else
132+
" AND "
133+
end
134+
collector << "("
135+
values.each_slice(in_clause_length).each_with_index do |valuez, i|
136+
collector << separator unless i == 0
137+
visit o.left, collector
138+
collector << operator
139+
collector << valuez.join(",")
140+
collector << ")"
141+
end
142+
collector << ")"
143+
end
144+
145+
collector
146+
end
147+
101148
def visit_Arel_Nodes_In(o, collector)
102149
attr, values = o.left, o.right
103150
return super unless values.is_a?(Array)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe "Arel::Visitors::OracleCommon#visit_Arel_Nodes_HomogeneousIn" do
4+
before(:all) do
5+
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
6+
end
7+
8+
before(:each) do
9+
@visitor = Arel::Visitors::Oracle12.new(ActiveRecord::Base.connection)
10+
type_caster = Class.new { def type_for_attribute(_name) = ActiveRecord::Type::Value.new }.new
11+
@table = Arel::Table.new(:users, type_caster: type_caster)
12+
end
13+
14+
def compile(node, visitor: @visitor)
15+
visitor.accept(node, Arel::Collectors::SQLString.new).value
16+
end
17+
18+
it "marks the collector as not preparable" do
19+
node = Arel::Nodes::HomogeneousIn.new([1, 2, 3], @table[:id], :in)
20+
collector = Arel::Collectors::SQLString.new
21+
collector.preparable = true
22+
@visitor.accept(node, collector)
23+
expect(collector.preparable).to be(false)
24+
end
25+
26+
it "renders :in as a literal IN list" do
27+
node = Arel::Nodes::HomogeneousIn.new([1, 2, 3], @table[:id], :in)
28+
expect(compile(node)).to match(/"USERS"\."ID"\s+IN\s+\(1,2,3\)/)
29+
end
30+
31+
it "renders :notin as a literal NOT IN list" do
32+
node = Arel::Nodes::HomogeneousIn.new([1, 2, 3], @table[:id], :notin)
33+
expect(compile(node)).to match(/"USERS"\."ID"\s+NOT IN\s+\(1,2,3\)/)
34+
end
35+
36+
it "falls back to NULL when :in has an empty values array" do
37+
node = Arel::Nodes::HomogeneousIn.new([], @table[:id], :in)
38+
expect(compile(node)).to match(/"USERS"\."ID"\s+IN\s+\(NULL\)/)
39+
end
40+
41+
it "falls back to NULL when :notin has an empty values array" do
42+
node = Arel::Nodes::HomogeneousIn.new([], @table[:id], :notin)
43+
expect(compile(node)).to match(/"USERS"\."ID"\s+NOT IN\s+\(NULL\)/)
44+
end
45+
46+
it "chunks :in into multiple IN groups joined by OR when values exceed in_clause_length" do
47+
node = Arel::Nodes::HomogeneousIn.new((1..1001).to_a, @table[:id], :in)
48+
sql = compile(node)
49+
expect(sql.scan(/"USERS"\."ID" IN \(/).size).to eq(2)
50+
expect(sql).to include(" OR ")
51+
expect(sql).to start_with("(")
52+
expect(sql).to end_with(")")
53+
expect(sql).to include("IN (1001)")
54+
end
55+
56+
it "chunks :notin into multiple NOT IN groups joined by AND when values exceed in_clause_length" do
57+
node = Arel::Nodes::HomogeneousIn.new((1..1001).to_a, @table[:id], :notin)
58+
sql = compile(node)
59+
expect(sql.scan(/"USERS"\."ID" NOT IN \(/).size).to eq(2)
60+
expect(sql).to include(" AND ")
61+
expect(sql).to start_with("(")
62+
expect(sql).to end_with(")")
63+
expect(sql).to include("NOT IN (1001)")
64+
end
65+
66+
it "chunks :in the same way via Arel::Visitors::Oracle" do
67+
oracle = Arel::Visitors::Oracle.new(ActiveRecord::Base.connection)
68+
node = Arel::Nodes::HomogeneousIn.new((1..1001).to_a, @table[:id], :in)
69+
sql = compile(node, visitor: oracle)
70+
expect(sql.scan(/"USERS"\."ID" IN \(/).size).to eq(2)
71+
expect(sql).to include(" OR ")
72+
end
73+
74+
it "chunks :notin the same way via Arel::Visitors::Oracle" do
75+
oracle = Arel::Visitors::Oracle.new(ActiveRecord::Base.connection)
76+
node = Arel::Nodes::HomogeneousIn.new((1..1001).to_a, @table[:id], :notin)
77+
sql = compile(node, visitor: oracle)
78+
expect(sql.scan(/"USERS"\."ID" NOT IN \(/).size).to eq(2)
79+
expect(sql).to include(" AND ")
80+
end
81+
end

spec/active_record/connection_adapters/oracle_enhanced/arel/oracle12_spec.rb

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,16 +116,4 @@ def compile(node)
116116
expect(sql).to be_like %{ "USERS"."NAME" IS NOT NULL }
117117
end
118118
end
119-
120-
describe "visit_Arel_Nodes_HomogeneousIn" do
121-
it "marks the collector as not preparable" do
122-
type_caster = Class.new { def type_for_attribute(_name) = ActiveRecord::Type::Value.new }.new
123-
table = Arel::Table.new(:users, type_caster: type_caster)
124-
node = Arel::Nodes::HomogeneousIn.new([1, 2, 3], table[:id], :in)
125-
collector = Arel::Collectors::SQLString.new
126-
collector.preparable = true
127-
@visitor.accept(node, collector)
128-
expect(collector.preparable).to be(false)
129-
end
130-
end
131119
end

spec/active_record/connection_adapters/oracle_enhanced/arel/oracle_spec.rb

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -302,16 +302,4 @@ def select_with_order(order_clause)
302302
}
303303
end
304304
end
305-
306-
describe "visit_Arel_Nodes_HomogeneousIn" do
307-
it "marks the collector as not preparable" do
308-
type_caster = Class.new { def type_for_attribute(_name) = ActiveRecord::Type::Value.new }.new
309-
table = Arel::Table.new(:users, type_caster: type_caster)
310-
node = Arel::Nodes::HomogeneousIn.new([1, 2, 3], table[:id], :in)
311-
collector = Arel::Collectors::SQLString.new
312-
collector.preparable = true
313-
@visitor.accept(node, collector)
314-
expect(collector.preparable).to be(false)
315-
end
316-
end
317305
end

0 commit comments

Comments
 (0)