activerecord-oracle_enhanced-adapter 6.0.6 → 6.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +80 -4
  3. data/README.md +1 -1
  4. data/VERSION +1 -1
  5. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +2 -3
  6. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +0 -1
  7. data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +0 -9
  8. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +7 -9
  9. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +4 -5
  10. data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +0 -1
  11. data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +3 -4
  12. data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +1 -2
  13. data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +2 -3
  14. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +0 -1
  15. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +2 -3
  16. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +2 -3
  17. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +16 -4
  18. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +55 -55
  19. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +35 -39
  20. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +2 -1
  21. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +58 -40
  22. data/lib/active_record/type/oracle_enhanced/boolean.rb +0 -1
  23. data/lib/active_record/type/oracle_enhanced/integer.rb +0 -1
  24. data/lib/arel/visitors/oracle.rb +253 -0
  25. data/lib/arel/visitors/oracle12.rb +160 -0
  26. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +9 -3
  27. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +6 -1
  28. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +0 -1
  29. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +27 -0
  30. data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +2 -2
  31. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +30 -60
  32. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +4 -2
  33. data/spec/spec_config.yaml.template +2 -2
  34. data/spec/spec_helper.rb +13 -2
  35. data/spec/support/stats.sql +3 -0
  36. metadata +31 -27
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Visitors
5
+ class Oracle < Arel::Visitors::ToSql
6
+ private
7
+ def visit_Arel_Nodes_SelectStatement(o, collector)
8
+ o = order_hacks(o)
9
+
10
+ # if need to select first records without ORDER BY and GROUP BY and without DISTINCT
11
+ # then can use simple ROWNUM in WHERE clause
12
+ if o.limit && o.orders.empty? && o.cores.first.groups.empty? && !o.offset && !o.cores.first.set_quantifier.class.to_s.match?(/Distinct/)
13
+ o.cores.last.wheres.push Nodes::LessThanOrEqual.new(
14
+ Nodes::SqlLiteral.new("ROWNUM"), o.limit.expr
15
+ )
16
+ return super
17
+ end
18
+
19
+ if o.limit && o.offset
20
+ o = o.dup
21
+ limit = o.limit.expr
22
+ offset = o.offset
23
+ o.offset = nil
24
+ collector << "
25
+ SELECT * FROM (
26
+ SELECT raw_sql_.*, rownum raw_rnum_
27
+ FROM ("
28
+
29
+ collector = super(o, collector)
30
+
31
+ if offset.expr.is_a? Nodes::BindParam
32
+ collector << ") raw_sql_ WHERE rownum <= ("
33
+ collector = visit offset.expr, collector
34
+ collector << " + "
35
+ collector = visit limit, collector
36
+ collector << ") ) WHERE raw_rnum_ > "
37
+ collector = visit offset.expr, collector
38
+ return collector
39
+ else
40
+ collector << ") raw_sql_
41
+ WHERE rownum <= #{offset.expr.to_i + limit}
42
+ )
43
+ WHERE "
44
+ return visit(offset, collector)
45
+ end
46
+ end
47
+
48
+ if o.limit
49
+ o = o.dup
50
+ limit = o.limit.expr
51
+ collector << "SELECT * FROM ("
52
+ collector = super(o, collector)
53
+ collector << ") WHERE ROWNUM <= "
54
+ return visit limit, collector
55
+ end
56
+
57
+ if o.offset
58
+ o = o.dup
59
+ offset = o.offset
60
+ o.offset = nil
61
+ collector << "SELECT * FROM (
62
+ SELECT raw_sql_.*, rownum raw_rnum_
63
+ FROM ("
64
+ collector = super(o, collector)
65
+ collector << ") raw_sql_
66
+ )
67
+ WHERE "
68
+ return visit offset, collector
69
+ end
70
+
71
+ super
72
+ end
73
+
74
+ def visit_Arel_Nodes_Limit(o, collector)
75
+ collector
76
+ end
77
+
78
+ def visit_Arel_Nodes_Offset(o, collector)
79
+ collector << "raw_rnum_ > "
80
+ visit o.expr, collector
81
+ end
82
+
83
+ def visit_Arel_Nodes_Except(o, collector)
84
+ collector << "( "
85
+ collector = infix_value o, collector, " MINUS "
86
+ collector << " )"
87
+ end
88
+
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
+
131
+ def visit_Arel_Nodes_In(o, collector)
132
+ attr, values = o.left, o.right
133
+
134
+ if Array === values
135
+ unless values.empty?
136
+ values.delete_if { |value| unboundable?(value) }
137
+ end
138
+
139
+ return collector << "1=0" if values.empty?
140
+ end
141
+
142
+ in_clause_length = @connection.in_clause_length
143
+
144
+ if !Array === values || values.length <= in_clause_length
145
+ visit(attr, collector) << " IN ("
146
+ visit(values, collector) << ")"
147
+ else
148
+ collector << "("
149
+ values.each_slice(in_clause_length).each_with_index do |valuez, i|
150
+ collector << " OR " unless i == 0
151
+ visit(attr, collector) << " IN ("
152
+ visit(valuez, collector) << ")"
153
+ end
154
+ collector << ")"
155
+ end
156
+ end
157
+
158
+ def visit_Arel_Nodes_NotIn(o, collector)
159
+ attr, values = o.left, o.right
160
+
161
+ if Array === values
162
+ unless values.empty?
163
+ values.delete_if { |value| unboundable?(value) }
164
+ end
165
+
166
+ return collector << "1=1" if values.empty?
167
+ end
168
+
169
+ in_clause_length = @connection.in_clause_length
170
+
171
+ if !Array === values || values.length <= in_clause_length
172
+ visit(attr, collector) << " NOT IN ("
173
+ visit(values, collector) << ")"
174
+ else
175
+ values.each_slice(in_clause_length).each_with_index do |valuez, i|
176
+ collector << " AND " unless i == 0
177
+ visit(attr, collector) << " NOT IN ("
178
+ visit(valuez, collector) << ")"
179
+ end
180
+ collector
181
+ end
182
+ end
183
+
184
+ def visit_Arel_Nodes_UpdateStatement(o, collector)
185
+ # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
186
+ if o.orders.any? && o.limit.nil?
187
+ # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
188
+ # otherwise let the user deal with the error
189
+ o = o.dup
190
+ o.orders = []
191
+ end
192
+
193
+ super
194
+ end
195
+
196
+ ###
197
+ # Hacks for the order clauses specific to Oracle
198
+ def order_hacks(o)
199
+ return o if o.orders.empty?
200
+ return o unless o.cores.any? do |core|
201
+ core.projections.any? do |projection|
202
+ /FIRST_VALUE/ === projection
203
+ end
204
+ end
205
+ # Previous version with join and split broke ORDER BY clause
206
+ # if it contained functions with several arguments (separated by ',').
207
+ #
208
+ # orders = o.orders.map { |x| visit x }.join(', ').split(',')
209
+ orders = o.orders.map do |x|
210
+ string = visit(x, Arel::Collectors::SQLString.new).value
211
+ if string.include?(",")
212
+ split_order_string(string)
213
+ else
214
+ string
215
+ end
216
+ end.flatten
217
+ o.orders = []
218
+ orders.each_with_index do |order, i|
219
+ o.orders <<
220
+ Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i.match?(order)}")
221
+ end
222
+ o
223
+ end
224
+
225
+ # Split string by commas but count opening and closing brackets
226
+ # and ignore commas inside brackets.
227
+ def split_order_string(string)
228
+ array = []
229
+ i = 0
230
+ string.split(",").each do |part|
231
+ if array[i]
232
+ array[i] << "," << part
233
+ else
234
+ # to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral
235
+ array[i] = part.to_s
236
+ end
237
+ i += 1 if array[i].count("(") == array[i].count(")")
238
+ end
239
+ array
240
+ end
241
+
242
+ def visit_Arel_Nodes_BindParam(o, collector)
243
+ collector.add_bind(o.value) { |i| ":a#{i}" }
244
+ end
245
+
246
+ def is_distinct_from(o, collector)
247
+ collector << "DECODE("
248
+ collector = visit [o.left, o.right, 0, 1], collector
249
+ collector << ")"
250
+ end
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Arel # :nodoc: all
4
+ module Visitors
5
+ class Oracle12 < Arel::Visitors::ToSql
6
+ private
7
+ def visit_Arel_Nodes_SelectStatement(o, collector)
8
+ # Oracle does not allow LIMIT clause with select for update
9
+ if o.limit && o.lock
10
+ raise ArgumentError, <<~MSG
11
+ Combination of limit and lock is not supported. Because generated SQL statements
12
+ `SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014.
13
+ MSG
14
+ end
15
+ super
16
+ end
17
+
18
+ def visit_Arel_Nodes_SelectOptions(o, collector)
19
+ collector = maybe_visit o.offset, collector
20
+ collector = maybe_visit o.limit, collector
21
+ maybe_visit o.lock, collector
22
+ end
23
+
24
+ def visit_Arel_Nodes_Limit(o, collector)
25
+ collector << "FETCH FIRST "
26
+ collector = visit o.expr, collector
27
+ collector << " ROWS ONLY"
28
+ end
29
+
30
+ def visit_Arel_Nodes_Offset(o, collector)
31
+ collector << "OFFSET "
32
+ visit o.expr, collector
33
+ collector << " ROWS"
34
+ end
35
+
36
+ def visit_Arel_Nodes_Except(o, collector)
37
+ collector << "( "
38
+ collector = infix_value o, collector, " MINUS "
39
+ collector << " )"
40
+ end
41
+
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
+
84
+ def visit_Arel_Nodes_In(o, collector)
85
+ attr, values = o.left, o.right
86
+
87
+ if Array === values
88
+ unless values.empty?
89
+ values.delete_if { |value| unboundable?(value) }
90
+ end
91
+
92
+ return collector << "1=0" if values.empty?
93
+ end
94
+
95
+ in_clause_length = @connection.in_clause_length
96
+
97
+ if !Array === values || values.length <= in_clause_length
98
+ visit(attr, collector) << " IN ("
99
+ visit(values, collector) << ")"
100
+ else
101
+ collector << "("
102
+ values.each_slice(in_clause_length).each_with_index do |valuez, i|
103
+ collector << " OR " unless i == 0
104
+ visit(attr, collector) << " IN ("
105
+ visit(valuez, collector) << ")"
106
+ end
107
+ collector << ")"
108
+ end
109
+ end
110
+
111
+ def visit_Arel_Nodes_NotIn(o, collector)
112
+ attr, values = o.left, o.right
113
+
114
+ if Array === values
115
+ unless values.empty?
116
+ values.delete_if { |value| unboundable?(value) }
117
+ end
118
+
119
+ return collector << "1=1" if values.empty?
120
+ end
121
+
122
+ in_clause_length = @connection.in_clause_length
123
+
124
+ if !Array === values || values.length <= in_clause_length
125
+ visit(attr, collector) << " NOT IN ("
126
+ visit(values, collector) << ")"
127
+ else
128
+ values.each_slice(in_clause_length).each_with_index do |valuez, i|
129
+ collector << " AND " unless i == 0
130
+ visit(attr, collector) << " NOT IN ("
131
+ visit(valuez, collector) << ")"
132
+ end
133
+ collector
134
+ end
135
+ end
136
+
137
+ def visit_Arel_Nodes_UpdateStatement(o, collector)
138
+ # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
139
+ if o.orders.any? && o.limit.nil?
140
+ # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
141
+ # otherwise let the user deal with the error
142
+ o = o.dup
143
+ o.orders = []
144
+ end
145
+
146
+ super
147
+ end
148
+
149
+ def visit_Arel_Nodes_BindParam(o, collector)
150
+ collector.add_bind(o.value) { |i| ":a#{i}" }
151
+ end
152
+
153
+ def is_distinct_from(o, collector)
154
+ collector << "DECODE("
155
+ collector = visit [o.left, o.right, 0, 1], collector
156
+ collector << ")"
157
+ end
158
+ end
159
+ end
160
+ end
@@ -40,6 +40,12 @@ describe "OracleEnhancedAdapter establish connection" do
40
40
  ActiveRecord::Base.establish_connection(SYSTEM_CONNECTION_PARAMS.merge(cursor_sharing: :exact))
41
41
  expect(ActiveRecord::Base.connection.select_value("select value from v$parameter where name = 'cursor_sharing'")).to eq("EXACT")
42
42
  end
43
+
44
+ it "should connect to database using service_name" do
45
+ ActiveRecord::Base.establish_connection(SERVICE_NAME_CONNECTION_PARAMS)
46
+ expect(ActiveRecord::Base.connection).not_to be_nil
47
+ expect(ActiveRecord::Base.connection.class).to eq(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter)
48
+ end
43
49
  end
44
50
 
45
51
  describe "OracleEnhancedConnection" do
@@ -81,13 +87,13 @@ describe "OracleEnhancedConnection" do
81
87
  expect(ActiveRecord::Base.connection).to be_active
82
88
  end
83
89
 
84
- it "should swith to specified schema" do
90
+ it "should switch to specified schema" do
85
91
  ActiveRecord::Base.establish_connection(CONNECTION_WITH_SCHEMA_PARAMS)
86
92
  expect(ActiveRecord::Base.connection.current_schema).to eq(CONNECTION_WITH_SCHEMA_PARAMS[:schema].upcase)
87
93
  expect(ActiveRecord::Base.connection.current_user).to eq(CONNECTION_WITH_SCHEMA_PARAMS[:username].upcase)
88
94
  end
89
95
 
90
- it "should swith to specified schema after reset" do
96
+ it "should switch to specified schema after reset" do
91
97
  ActiveRecord::Base.connection.reset!
92
98
  expect(ActiveRecord::Base.connection.current_schema).to eq(CONNECTION_WITH_SCHEMA_PARAMS[:schema].upcase)
93
99
  end
@@ -180,7 +186,7 @@ describe "OracleEnhancedConnection" do
180
186
  describe "with slash-prefixed database name (service name)" do
181
187
  before(:all) do
182
188
  params = CONNECTION_PARAMS.dup
183
- params[:database] = "/#{params[:database]}" unless params[:database].match(/^\//)
189
+ params[:database] = "/#{params[:database]}" unless params[:database].start_with?("/")
184
190
  @conn = ActiveRecord::ConnectionAdapters::OracleEnhanced::Connection.create(params)
185
191
  end
186
192