activerecord-oracle_enhanced-adapter 6.0.0.rc3 → 6.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +126 -0
  3. data/README.md +13 -2
  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 +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 +15 -3
  19. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +48 -45
  20. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +16 -17
  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 +57 -15
  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 +253 -0
  26. data/lib/arel/visitors/oracle12.rb +160 -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 +48 -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. data/spec/support/stats.sql +3 -0
  37. metadata +30 -26
@@ -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
 
@@ -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
@@ -193,6 +193,46 @@ describe "OracleEnhancedAdapter" do
193
193
  end
194
194
  end
195
195
 
196
+ describe "lists" do
197
+ before(:all) do
198
+ schema_define do
199
+ create_table :test_posts do |t|
200
+ t.string :title
201
+ end
202
+ end
203
+ class ::TestPost < ActiveRecord::Base
204
+ has_many :test_comments
205
+ end
206
+ @ids = (1..1010).to_a
207
+ TestPost.transaction do
208
+ @ids.each do |id|
209
+ TestPost.create!(id: id, title: "Title #{id}")
210
+ end
211
+ end
212
+ end
213
+
214
+ after(:all) do
215
+ schema_define do
216
+ drop_table :test_posts
217
+ end
218
+ Object.send(:remove_const, "TestPost")
219
+ ActiveRecord::Base.clear_cache!
220
+ end
221
+
222
+ ##
223
+ # See this GitHub issue for an explanation of homogenous lists.
224
+ # https://github.com/rails/rails/commit/72fd0bae5948c1169411941aeea6fef4c58f34a9
225
+ it "should allow more than 1000 items in a list where the list is homogenous" do
226
+ posts = TestPost.where(id: @ids).to_a
227
+ expect(posts.size).to eq(@ids.size)
228
+ end
229
+
230
+ it "should allow more than 1000 items in a list where the list is non-homogenous" do
231
+ posts = TestPost.where(id: [*@ids, nil]).to_a
232
+ expect(posts.size).to eq(@ids.size)
233
+ end
234
+ end
235
+
196
236
  describe "with statement pool" do
197
237
  before(:all) do
198
238
  ActiveRecord::Base.establish_connection(CONNECTION_PARAMS.merge(statement_limit: 3))
@@ -244,6 +284,14 @@ describe "OracleEnhancedAdapter" do
244
284
  end
245
285
  end
246
286
 
287
+ describe "database_exists?" do
288
+ it "should raise `NotImplementedError`" do
289
+ expect {
290
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.database_exists?(CONNECTION_PARAMS)
291
+ }.to raise_error(NotImplementedError)
292
+ end
293
+ end
294
+
247
295
  describe "explain" do
248
296
  before(:all) do
249
297
  @conn = ActiveRecord::Base.connection
@@ -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'
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "simplecov"
4
- SimpleCov.start
5
3
  require "rubygems"
6
4
  require "bundler"
7
5
  require "yaml"
@@ -19,8 +17,8 @@ end
19
17
 
20
18
  require "rspec"
21
19
 
22
- if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
23
- puts "==> Running specs with MRI version #{RUBY_VERSION}"
20
+ if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "truffleruby"
21
+ puts "==> Running specs with ruby version #{RUBY_VERSION}"
24
22
  require "oci8"
25
23
  elsif RUBY_ENGINE == "jruby"
26
24
  puts "==> Running specs with JRuby version #{JRUBY_VERSION}"
@@ -107,6 +105,8 @@ module LoggerSpecHelper
107
105
  end
108
106
  end
109
107
 
108
+ ActiveRecord::LogSubscriber::IGNORE_PAYLOAD_NAMES.replace(["EXPLAIN"])
109
+
110
110
  module SchemaSpecHelper
111
111
  def schema_define(&block)
112
112
  ActiveRecord::Schema.define do
@@ -185,6 +185,15 @@ SYSTEM_CONNECTION_PARAMS = {
185
185
  password: DATABASE_SYS_PASSWORD
186
186
  }
187
187
 
188
+ SERVICE_NAME_CONNECTION_PARAMS = {
189
+ adapter: "oracle_enhanced",
190
+ database: "/#{DATABASE_NAME}",
191
+ host: DATABASE_HOST,
192
+ port: DATABASE_PORT,
193
+ username: DATABASE_USER,
194
+ password: DATABASE_PASSWORD
195
+ }
196
+
188
197
  DATABASE_NON_DEFAULT_TABLESPACE = config["database"]["non_default_tablespace"] || ENV["DATABASE_NON_DEFAULT_TABLESPACE"] || "SYSTEM"
189
198
 
190
199
  # set default time zone in TZ environment variable