activerecord-oracle_enhanced-adapter 6.0.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +112 -0
  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 +64 -47
  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 +12 -7
  14. data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +0 -1
  15. data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +38 -3
  16. data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +3 -4
  17. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +1 -1
  18. data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +16 -4
  19. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +65 -59
  20. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +35 -34
  21. data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +2 -1
  22. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +64 -21
  23. data/lib/active_record/type/oracle_enhanced/boolean.rb +0 -1
  24. data/lib/active_record/type/oracle_enhanced/integer.rb +0 -1
  25. data/lib/arel/visitors/oracle.rb +217 -0
  26. data/lib/arel/visitors/oracle12.rb +124 -0
  27. data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +35 -3
  28. data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +6 -1
  29. data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +0 -1
  30. data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +28 -1
  31. data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +2 -1
  32. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +122 -0
  33. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +4 -2
  34. data/spec/spec_config.yaml.template +2 -2
  35. data/spec/spec_helper.rb +13 -4
  36. metadata +28 -26
@@ -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
 
@@ -387,10 +393,22 @@ describe "OracleEnhancedConnection" do
387
393
  end
388
394
 
389
395
  describe "auto reconnection" do
396
+ include SchemaSpecHelper
397
+
390
398
  before(:all) do
391
399
  ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
392
400
  @conn = ActiveRecord::Base.connection.instance_variable_get("@connection")
393
401
  @sys_conn = ActiveRecord::ConnectionAdapters::OracleEnhanced::Connection.create(SYS_CONNECTION_PARAMS)
402
+ schema_define do
403
+ create_table :posts, force: true
404
+ end
405
+ class ::Post < ActiveRecord::Base
406
+ end
407
+ end
408
+
409
+ after(:all) do
410
+ Object.send(:remove_const, "Post")
411
+ ActiveRecord::Base.clear_cache!
394
412
  end
395
413
 
396
414
  before(:each) do
@@ -440,6 +458,20 @@ describe "OracleEnhancedConnection" do
440
458
  expect { @conn.select("SELECT * FROM dual") }.to raise_error(OCIError)
441
459
  end
442
460
  end
461
+
462
+ it "should reconnect and execute query if connection is lost and auto retry is enabled" do
463
+ Post.create!
464
+ ActiveRecord::Base.connection.auto_retry = true
465
+ kill_current_session
466
+ expect(Post.take).not_to be_nil
467
+ end
468
+
469
+ it "should not reconnect and execute query if connection is lost and auto retry is disabled" do
470
+ Post.create!
471
+ ActiveRecord::Base.connection.auto_retry = false
472
+ kill_current_session
473
+ expect { Post.take }.to raise_error(ActiveRecord::StatementInvalid)
474
+ end
443
475
  end
444
476
 
445
477
  describe "describe table" do
@@ -16,10 +16,15 @@ describe "Oracle Enhanced adapter database tasks" do
16
16
  ActiveRecord::Tasks::DatabaseTasks.create(new_user_config)
17
17
  end
18
18
  end
19
- it "creates user" do
19
+ xit "creates user" 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
+ xit "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
@@ -21,7 +21,7 @@ describe "OracleEnhancedAdapter schema dump" do
21
21
  def create_test_posts_table(options = {})
22
22
  options[:force] = true
23
23
  schema_define do
24
- create_table :test_posts, options do |t|
24
+ create_table :test_posts, **options do |t|
25
25
  t.string :title
26
26
  t.timestamps null: true
27
27
  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
@@ -80,7 +80,8 @@ describe "OracleEnhancedAdapter schema definition" do
80
80
  describe "sequence creation parameters" do
81
81
  def create_test_employees_table(sequence_start_value = nil)
82
82
  schema_define do
83
- create_table :test_employees, sequence_start_value ? { sequence_start_value: sequence_start_value } : {} do |t|
83
+ options = sequence_start_value ? { sequence_start_value: sequence_start_value } : {}
84
+ create_table :test_employees, **options do |t|
84
85
  t.string :first_name
85
86
  t.string :last_name
86
87
  end
@@ -150,6 +150,45 @@ describe "OracleEnhancedAdapter" do
150
150
  end
151
151
  end
152
152
 
153
+ describe "`has_many` assoc has `dependent: :delete_all` with `order`" do
154
+ before(:all) do
155
+ schema_define do
156
+ create_table :test_posts do |t|
157
+ t.string :title
158
+ end
159
+ create_table :test_comments do |t|
160
+ t.integer :test_post_id
161
+ t.string :description
162
+ end
163
+ add_index :test_comments, :test_post_id
164
+ end
165
+ class ::TestPost < ActiveRecord::Base
166
+ has_many :test_comments, -> { order(:id) }, dependent: :delete_all
167
+ end
168
+ class ::TestComment < ActiveRecord::Base
169
+ belongs_to :test_post
170
+ end
171
+ TestPost.transaction do
172
+ post = TestPost.create!(title: "Title")
173
+ TestComment.create!(test_post_id: post.id, description: "Description")
174
+ end
175
+ end
176
+
177
+ after(:all) do
178
+ schema_define do
179
+ drop_table :test_comments
180
+ drop_table :test_posts
181
+ end
182
+ Object.send(:remove_const, "TestPost")
183
+ Object.send(:remove_const, "TestComment")
184
+ ActiveRecord::Base.clear_cache!
185
+ end
186
+
187
+ it "should not occur `ActiveRecord::StatementInvalid: OCIError: ORA-00907: missing right parenthesis`" do
188
+ expect { TestPost.first.destroy }.not_to raise_error
189
+ end
190
+ end
191
+
153
192
  describe "eager loading" do
154
193
  before(:all) do
155
194
  schema_define do
@@ -193,6 +232,46 @@ describe "OracleEnhancedAdapter" do
193
232
  end
194
233
  end
195
234
 
235
+ describe "lists" do
236
+ before(:all) do
237
+ schema_define do
238
+ create_table :test_posts do |t|
239
+ t.string :title
240
+ end
241
+ end
242
+ class ::TestPost < ActiveRecord::Base
243
+ has_many :test_comments
244
+ end
245
+ @ids = (1..1010).to_a
246
+ TestPost.transaction do
247
+ @ids.each do |id|
248
+ TestPost.create!(id: id, title: "Title #{id}")
249
+ end
250
+ end
251
+ end
252
+
253
+ after(:all) do
254
+ schema_define do
255
+ drop_table :test_posts
256
+ end
257
+ Object.send(:remove_const, "TestPost")
258
+ ActiveRecord::Base.clear_cache!
259
+ end
260
+
261
+ ##
262
+ # See this GitHub issue for an explanation of homogenous lists.
263
+ # https://github.com/rails/rails/commit/72fd0bae5948c1169411941aeea6fef4c58f34a9
264
+ it "should allow more than 1000 items in a list where the list is homogenous" do
265
+ posts = TestPost.where(id: @ids).to_a
266
+ expect(posts.size).to eq(@ids.size)
267
+ end
268
+
269
+ it "should allow more than 1000 items in a list where the list is non-homogenous" do
270
+ posts = TestPost.where(id: [*@ids, nil]).to_a
271
+ expect(posts.size).to eq(@ids.size)
272
+ end
273
+ end
274
+
196
275
  describe "with statement pool" do
197
276
  before(:all) do
198
277
  ActiveRecord::Base.establish_connection(CONNECTION_PARAMS.merge(statement_limit: 3))
@@ -244,6 +323,14 @@ describe "OracleEnhancedAdapter" do
244
323
  end
245
324
  end
246
325
 
326
+ describe "database_exists?" do
327
+ it "should raise `NotImplementedError`" do
328
+ expect {
329
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.database_exists?(CONNECTION_PARAMS)
330
+ }.to raise_error(NotImplementedError)
331
+ end
332
+ end
333
+
247
334
  describe "explain" do
248
335
  before(:all) do
249
336
  @conn = ActiveRecord::Base.connection
@@ -620,4 +707,39 @@ describe "OracleEnhancedAdapter" do
620
707
  expect(post.explain).to include("| TABLE ACCESS FULL| TEST_POSTS |")
621
708
  end
622
709
  end
710
+
711
+ describe "homogeneous in" do
712
+ before(:all) do
713
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
714
+ @conn = ActiveRecord::Base.connection
715
+ schema_define do
716
+ create_table :test_posts, force: true
717
+ create_table :test_comments, force: true do |t|
718
+ t.integer :test_post_id
719
+ end
720
+ end
721
+ class ::TestPost < ActiveRecord::Base
722
+ has_many :test_comments
723
+ end
724
+ class ::TestComment < ActiveRecord::Base
725
+ belongs_to :test_post
726
+ end
727
+ end
728
+
729
+ after(:all) do
730
+ schema_define do
731
+ drop_table :test_posts, if_exists: true
732
+ drop_table :test_comments, if_exists: true
733
+ end
734
+ Object.send(:remove_const, "TestPost")
735
+ Object.send(:remove_const, "TestComment")
736
+ ActiveRecord::Base.clear_cache!
737
+ end
738
+
739
+ it "should not raise undefined method length" do
740
+ post = TestPost.create!
741
+ post.test_comments << TestComment.create!
742
+ expect(TestComment.where(test_post_id: TestPost.select(:id)).size).to eq(1)
743
+ end
744
+ end
623
745
  end
@@ -35,9 +35,11 @@ describe "OracleEnhancedAdapter quoting of NCHAR and NVARCHAR2 columns" do
35
35
  columns = @conn.columns("test_items")
36
36
  %w(nchar_column nvarchar2_column char_column varchar2_column).each do |col|
37
37
  column = columns.detect { |c| c.name == col }
38
- value = @conn.type_cast_from_column(column, "abc")
38
+ type = @conn.lookup_cast_type_from_column(column)
39
+ value = type.serialize("abc")
39
40
  expect(@conn.quote(value)).to eq(column.sql_type[0, 1] == "N" ? "N'abc'" : "'abc'")
40
- nilvalue = @conn.type_cast_from_column(column, nil)
41
+ type = @conn.lookup_cast_type_from_column(column)
42
+ nilvalue = type.serialize(nil)
41
43
  expect(@conn.quote(nilvalue)).to eq("NULL")
42
44
  end
43
45
  end
@@ -1,4 +1,4 @@
1
- # copy this file to spec/config.yaml and set appropriate values
1
+ # copy this file to spec/spec_config.yaml and set appropriate values
2
2
  # you can also use environment variables, see spec_helper.rb
3
3
  database:
4
4
  name: 'orcl'
@@ -8,4 +8,4 @@ database:
8
8
  password: 'oracle_enhanced'
9
9
  sys_password: 'admin'
10
10
  non_default_tablespace: 'SYSTEM'
11
- timezone: 'Europe/Riga'
11
+ timezone: 'Europe/Riga'