activerecord-oracle_enhanced-adapter 6.0.0 → 6.1.0

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 +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'