activerecord-oracle_enhanced-adapter 6.0.5 → 6.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +109 -4
  3. data/README.md +12 -1
  4. data/VERSION +1 -1
  5. data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +3 -4
  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 +9 -7
  9. data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +8 -9
  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 +15 -3
  18. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +43 -40
  19. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +19 -19
  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 +52 -33
  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 +217 -0
  25. data/lib/arel/visitors/oracle12.rb +124 -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 +5 -0
  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 +122 -0
  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. metadata +22 -20
@@ -5,7 +5,6 @@ module ActiveRecord
5
5
  module OracleEnhanced
6
6
  class Boolean < ActiveModel::Type::Boolean # :nodoc:
7
7
  private
8
-
9
8
  def cast_value(value)
10
9
  # Kind of adding 'n' and 'N' to `FALSE_VALUES`
11
10
  if ["n", "N"].include?(value)
@@ -5,7 +5,6 @@ module ActiveRecord
5
5
  module OracleEnhanced
6
6
  class Integer < ActiveModel::Type::Integer # :nodoc:
7
7
  private
8
-
9
8
  def max_value
10
9
  ("9" * 38).to_i
11
10
  end
@@ -0,0 +1,217 @@
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
+ separator =
118
+ if o.type == :in
119
+ " OR "
120
+ else
121
+ " AND "
122
+ end
123
+ collector << "("
124
+ values.each_slice(in_clause_length).each_with_index do |valuez, i|
125
+ collector << separator unless i == 0
126
+ collector << column_name
127
+ collector << operator
128
+ collector << valuez.join(",")
129
+ collector << ")"
130
+ end
131
+ collector << ")"
132
+ end
133
+
134
+ collector
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
+ ###
150
+ # Hacks for the order clauses specific to Oracle
151
+ def order_hacks(o)
152
+ return o if o.orders.empty?
153
+ return o unless o.cores.any? do |core|
154
+ core.projections.any? do |projection|
155
+ /FIRST_VALUE/ === projection
156
+ end
157
+ end
158
+ # Previous version with join and split broke ORDER BY clause
159
+ # if it contained functions with several arguments (separated by ',').
160
+ #
161
+ # orders = o.orders.map { |x| visit x }.join(', ').split(',')
162
+ orders = o.orders.map do |x|
163
+ string = visit(x, Arel::Collectors::SQLString.new).value
164
+ if string.include?(",")
165
+ split_order_string(string)
166
+ else
167
+ string
168
+ end
169
+ end.flatten
170
+ o.orders = []
171
+ orders.each_with_index do |order, i|
172
+ o.orders <<
173
+ Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i.match?(order)}")
174
+ end
175
+ o
176
+ end
177
+
178
+ # Split string by commas but count opening and closing brackets
179
+ # and ignore commas inside brackets.
180
+ def split_order_string(string)
181
+ array = []
182
+ i = 0
183
+ string.split(",").each do |part|
184
+ if array[i]
185
+ array[i] << "," << part
186
+ else
187
+ # to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral
188
+ array[i] = part.to_s
189
+ end
190
+ i += 1 if array[i].count("(") == array[i].count(")")
191
+ end
192
+ array
193
+ end
194
+
195
+ def visit_Arel_Nodes_BindParam(o, collector)
196
+ collector.add_bind(o.value) { |i| ":a#{i}" }
197
+ end
198
+
199
+ def is_distinct_from(o, collector)
200
+ collector << "DECODE("
201
+ collector = visit [o.left, o.right, 0, 1], collector
202
+ collector << ")"
203
+ end
204
+
205
+ # Oracle will occur an error `ORA-00907: missing right parenthesis`
206
+ # when using `ORDER BY` in `UPDATE` or `DELETE`'s subquery.
207
+ #
208
+ # This method has been overridden based on the following code.
209
+ # https://github.com/rails/rails/blob/v6.1.0.rc1/activerecord/lib/arel/visitors/to_sql.rb#L815-L825
210
+ def build_subselect(key, o)
211
+ stmt = super
212
+ stmt.orders = [] # `orders` will never be set to prevent `ORA-00907`.
213
+ stmt
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,124 @@
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
+ separator =
71
+ if o.type == :in
72
+ " OR "
73
+ else
74
+ " AND "
75
+ end
76
+ collector << "("
77
+ values.each_slice(in_clause_length).each_with_index do |valuez, i|
78
+ collector << separator unless i == 0
79
+ collector << column_name
80
+ collector << operator
81
+ collector << valuez.join(",")
82
+ collector << ")"
83
+ end
84
+ collector << ")"
85
+ end
86
+
87
+ collector
88
+ end
89
+
90
+ def visit_Arel_Nodes_UpdateStatement(o, collector)
91
+ # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
92
+ if o.orders.any? && o.limit.nil?
93
+ # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
94
+ # otherwise let the user deal with the error
95
+ o = o.dup
96
+ o.orders = []
97
+ end
98
+
99
+ super
100
+ end
101
+
102
+ def visit_Arel_Nodes_BindParam(o, collector)
103
+ collector.add_bind(o.value) { |i| ":a#{i}" }
104
+ end
105
+
106
+ def is_distinct_from(o, collector)
107
+ collector << "DECODE("
108
+ collector = visit [o.left, o.right, 0, 1], collector
109
+ collector << ")"
110
+ end
111
+
112
+ # Oracle will occur an error `ORA-00907: missing right parenthesis`
113
+ # when using `ORDER BY` in `UPDATE` or `DELETE`'s subquery.
114
+ #
115
+ # This method has been overridden based on the following code.
116
+ # https://github.com/rails/rails/blob/v6.1.0.rc1/activerecord/lib/arel/visitors/to_sql.rb#L815-L825
117
+ def build_subselect(key, o)
118
+ stmt = super
119
+ stmt.orders = [] # `orders` will never be set to prevent `ORA-00907`.
120
+ stmt
121
+ end
122
+ end
123
+ end
124
+ 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
 
@@ -20,6 +20,11 @@ describe "Oracle Enhanced adapter database tasks" do
20
20
  query = "SELECT COUNT(*) FROM dba_users WHERE UPPER(username) = '#{new_user_config[:username].upcase}'"
21
21
  expect(ActiveRecord::Base.connection.select_value(query)).to eq(1)
22
22
  end
23
+ it "grants permissions defined by OracleEnhancedAdapter.persmissions" do
24
+ query = "SELECT COUNT(*) FROM DBA_SYS_PRIVS WHERE GRANTEE = '#{new_user_config[:username].upcase}'"
25
+ permissions_count = ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.permissions.size
26
+ expect(ActiveRecord::Base.connection.select_value(query)).to eq(permissions_count)
27
+ end
23
28
  after do
24
29
  ActiveRecord::Base.connection.execute("DROP USER #{new_user_config[:username]}")
25
30
  end
@@ -133,7 +133,6 @@ describe "OracleEnhancedAdapter custom methods for create, update and destroy" d
133
133
  end
134
134
 
135
135
  private
136
-
137
136
  def raise_make_transaction_rollback
138
137
  raise "Make the transaction rollback"
139
138
  end
@@ -305,6 +305,33 @@ describe "OracleEnhancedAdapter schema dump" do
305
305
  end
306
306
  end
307
307
 
308
+ describe "context indexes" do
309
+ before(:each) do
310
+ schema_define do
311
+ create_table :test_context_indexed_posts, force: true do |t|
312
+ t.string :title
313
+ t.string :body
314
+ t.index :title
315
+ end
316
+ add_context_index :test_context_indexed_posts, :body, sync: "ON COMMIT"
317
+ end
318
+ end
319
+
320
+ after(:each) do
321
+ schema_define do
322
+ drop_table :test_context_indexed_posts
323
+ end
324
+ end
325
+
326
+ it "should dump the context index" do
327
+ expect(standard_dump).to include(%(add_context_index "test_context_indexed_posts", ["body"]))
328
+ end
329
+
330
+ it "dumps the sync option" do
331
+ expect(standard_dump).to include(%(sync: "ON COMMIT"))
332
+ end
333
+ end
334
+
308
335
  describe "virtual columns" do
309
336
  before(:all) do
310
337
  skip "Not supported in this database version" unless @oracle11g_or_higher