activerecord-oracle_enhanced-adapter 6.0.4 → 6.1.1

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 +108 -0
  3. data/README.md +12 -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 +9 -7
  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 +58 -52
  19. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +38 -36
  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 +64 -21
  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 +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 +161 -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 +25 -23
@@ -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
 
@@ -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
@@ -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
@@ -66,7 +66,7 @@ describe "OracleEnhancedAdapter structure dump" do
66
66
  ALTER TABLE TEST_POSTS
67
67
  ADD CONSTRAINT fk_test_post_foo FOREIGN KEY (foo_id) REFERENCES foos(id)
68
68
  SQL
69
- dump = ActiveRecord::Base.connection.structure_dump_fk_constraints
69
+ dump = ActiveRecord::Base.connection.structure_dump
70
70
  expect(dump.split('\n').length).to eq(1)
71
71
  expect(dump).to match(/ALTER TABLE \"?TEST_POSTS\"? ADD CONSTRAINT \"?FK_TEST_POST_FOO\"? FOREIGN KEY \(\"?FOO_ID\"?\) REFERENCES \"?FOOS\"?\(\"?ID\"?\)/i)
72
72
  end
@@ -86,7 +86,7 @@ describe "OracleEnhancedAdapter structure dump" do
86
86
  ADD CONSTRAINT fk_test_post_baz FOREIGN KEY (baz_id) REFERENCES foos(baz_id)
87
87
  SQL
88
88
 
89
- dump = ActiveRecord::Base.connection.structure_dump_fk_constraints
89
+ dump = ActiveRecord::Base.connection.structure_dump
90
90
  expect(dump.split('\n').length).to eq(1)
91
91
  expect(dump).to match(/ALTER TABLE \"?TEST_POSTS\"? ADD CONSTRAINT \"?FK_TEST_POST_BAZ\"? FOREIGN KEY \(\"?BAZ_ID\"?\) REFERENCES \"?FOOS\"?\(\"?BAZ_ID\"?\)/i)
92
92
  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
@@ -392,6 +479,45 @@ describe "OracleEnhancedAdapter" do
392
479
  end
393
480
  end
394
481
 
482
+ describe "Binary lob column" do
483
+ before(:all) do
484
+ schema_define do
485
+ create_table :test_binary_columns do |t|
486
+ t.binary :attachment
487
+ end
488
+ end
489
+ class ::TestBinaryColumn < ActiveRecord::Base
490
+ end
491
+ end
492
+
493
+ after(:all) do
494
+ schema_define do
495
+ drop_table :test_binary_columns
496
+ end
497
+ Object.send(:remove_const, "TestBinaryColumn")
498
+ ActiveRecord::Base.table_name_prefix = nil
499
+ ActiveRecord::Base.clear_cache!
500
+ end
501
+
502
+ before(:each) do
503
+ set_logger
504
+ end
505
+
506
+ after(:each) do
507
+ clear_logger
508
+ end
509
+
510
+ it "should serialize with non UTF-8 data" do
511
+ binary_value = +"Hello \x93\xfa\x96\x7b"
512
+ binary_value.force_encoding "UTF-8"
513
+
514
+ binary_column_object = TestBinaryColumn.new
515
+ binary_column_object.attachment = binary_value
516
+
517
+ expect(binary_column_object.save!).to eq(true)
518
+ end
519
+ end
520
+
395
521
  describe "quoting" do
396
522
  before(:all) do
397
523
  schema_define do
@@ -620,4 +746,39 @@ describe "OracleEnhancedAdapter" do
620
746
  expect(post.explain).to include("| TABLE ACCESS FULL| TEST_POSTS |")
621
747
  end
622
748
  end
749
+
750
+ describe "homogeneous in" do
751
+ before(:all) do
752
+ ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
753
+ @conn = ActiveRecord::Base.connection
754
+ schema_define do
755
+ create_table :test_posts, force: true
756
+ create_table :test_comments, force: true do |t|
757
+ t.integer :test_post_id
758
+ end
759
+ end
760
+ class ::TestPost < ActiveRecord::Base
761
+ has_many :test_comments
762
+ end
763
+ class ::TestComment < ActiveRecord::Base
764
+ belongs_to :test_post
765
+ end
766
+ end
767
+
768
+ after(:all) do
769
+ schema_define do
770
+ drop_table :test_posts, if_exists: true
771
+ drop_table :test_comments, if_exists: true
772
+ end
773
+ Object.send(:remove_const, "TestPost")
774
+ Object.send(:remove_const, "TestComment")
775
+ ActiveRecord::Base.clear_cache!
776
+ end
777
+
778
+ it "should not raise undefined method length" do
779
+ post = TestPost.create!
780
+ post.test_comments << TestComment.create!
781
+ expect(TestComment.where(test_post_id: TestPost.select(:id)).size).to eq(1)
782
+ end
783
+ end
623
784
  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'