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
@@ -4,6 +4,8 @@ module ActiveRecord
4
4
  module ConnectionAdapters #:nodoc:
5
5
  module OracleEnhanced
6
6
  class TypeMetadata < DelegateClass(ActiveRecord::ConnectionAdapters::SqlTypeMetadata) # :nodoc:
7
+ include Deduplicable
8
+
7
9
  attr_reader :virtual
8
10
 
9
11
  def initialize(type_metadata, virtual: nil)
@@ -23,7 +25,6 @@ module ActiveRecord
23
25
  end
24
26
 
25
27
  protected
26
-
27
28
  def attributes_for_hash
28
29
  [self.class, @type_metadata, virtual]
29
30
  end
@@ -30,6 +30,9 @@
30
30
  # contribution.
31
31
  # portions Copyright 2005 Graham Jenkins
32
32
 
33
+ require "arel/visitors/oracle"
34
+ require "arel/visitors/oracle12"
35
+ require "active_record/connection_adapters"
33
36
  require "active_record/connection_adapters/abstract_adapter"
34
37
  require "active_record/connection_adapters/statement_pool"
35
38
  require "active_record/connection_adapters/oracle_enhanced/connection"
@@ -213,13 +216,24 @@ module ActiveRecord
213
216
  cattr_accessor :use_shorter_identifier
214
217
  self.use_shorter_identifier = false
215
218
 
219
+ ##
220
+ # :singleton-method:
221
+ # By default, OracleEnhanced adapter will grant unlimited tablespace, create session, create table, create view,
222
+ # and create sequence when running the rake task db:create.
223
+ #
224
+ # If you wish to change these permissions you can add the following line to your initializer file:
225
+ #
226
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.permissions =
227
+ # ["create session", "create table", "create view", "create sequence", "create trigger", "ctxapp"]
228
+ cattr_accessor :permissions
229
+ self.permissions = ["create session", "create table", "create view", "create sequence"]
230
+
216
231
  ##
217
232
  # :singleton-method:
218
233
  # Specify default sequence start with value (by default 1 if not explicitly set), e.g.:
219
234
 
220
235
  class StatementPool < ConnectionAdapters::StatementPool
221
236
  private
222
-
223
237
  def dealloc(stmt)
224
238
  stmt.close
225
239
  end
@@ -238,6 +252,14 @@ module ActiveRecord
238
252
  ADAPTER_NAME
239
253
  end
240
254
 
255
+ # Oracle enhanced adapter has no implementation because
256
+ # Oracle Database cannot detect `NoDatabaseError`.
257
+ # Please refer to the following discussion for details.
258
+ # https://github.com/rsim/oracle-enhanced/pull/1900
259
+ def self.database_exists?(config)
260
+ raise NotImplementedError
261
+ end
262
+
241
263
  def arel_visitor # :nodoc:
242
264
  if supports_fetch_first_n_rows_and_offset?
243
265
  Arel::Visitors::Oracle12.new(self)
@@ -266,6 +288,10 @@ module ActiveRecord
266
288
  true
267
289
  end
268
290
 
291
+ def supports_common_table_expressions?
292
+ true
293
+ end
294
+
269
295
  def supports_views?
270
296
  true
271
297
  end
@@ -438,6 +464,7 @@ module ActiveRecord
438
464
  end
439
465
 
440
466
  def discard!
467
+ super
441
468
  @connection = nil
442
469
  end
443
470
 
@@ -449,9 +476,9 @@ module ActiveRecord
449
476
  # when inserting a new database record (see #prefetch_primary_key?).
450
477
  def next_sequence_value(sequence_name)
451
478
  # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
452
- raise ArgumentError "Trigger based primary key is not supported" if sequence_name == AUTOGENERATED_SEQUENCE_NAME
479
+ raise ArgumentError.new "Trigger based primary key is not supported" if sequence_name == AUTOGENERATED_SEQUENCE_NAME
453
480
  # call directly connection method to avoid prepared statement which causes fetching of next sequence value twice
454
- select_value(<<~SQL.squish, "next sequence value")
481
+ select_value(<<~SQL.squish, "SCHEMA")
455
482
  SELECT #{quote_table_name(sequence_name)}.NEXTVAL FROM dual
456
483
  SQL
457
484
  end
@@ -489,7 +516,7 @@ module ActiveRecord
489
516
  end
490
517
 
491
518
  if primary_key && sequence_name
492
- new_start_value = select_value(<<~SQL.squish, "new start value")
519
+ new_start_value = select_value(<<~SQL.squish, "SCHEMA")
493
520
  select NVL(max(#{quote_column_name(primary_key)}),0) + 1 from #{quote_table_name(table_name)}
494
521
  SQL
495
522
 
@@ -500,33 +527,33 @@ module ActiveRecord
500
527
 
501
528
  # Current database name
502
529
  def current_database
503
- select_value(<<~SQL.squish, "current database on CDB ")
530
+ select_value(<<~SQL.squish, "SCHEMA")
504
531
  SELECT SYS_CONTEXT('userenv', 'con_name') FROM dual
505
532
  SQL
506
533
  rescue ActiveRecord::StatementInvalid
507
- select_value(<<~SQL.squish, "current database")
534
+ select_value(<<~SQL.squish, "SCHEMA")
508
535
  SELECT SYS_CONTEXT('userenv', 'db_name') FROM dual
509
536
  SQL
510
537
  end
511
538
 
512
539
  # Current database session user
513
540
  def current_user
514
- select_value(<<~SQL.squish, "current user")
541
+ select_value(<<~SQL.squish, "SCHEMA")
515
542
  SELECT SYS_CONTEXT('userenv', 'session_user') FROM dual
516
543
  SQL
517
544
  end
518
545
 
519
546
  # Current database session schema
520
547
  def current_schema
521
- select_value(<<~SQL.squish, "current schema")
548
+ select_value(<<~SQL.squish, "SCHEMA")
522
549
  SELECT SYS_CONTEXT('userenv', 'current_schema') FROM dual
523
550
  SQL
524
551
  end
525
552
 
526
553
  # Default tablespace name of current user
527
554
  def default_tablespace
528
- select_value(<<~SQL.squish, "default tablespace")
529
- SELECT LOWER(default_tablespace) FROM user_users
555
+ select_value(<<~SQL.squish, "SCHEMA")
556
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ LOWER(default_tablespace) FROM user_users
530
557
  WHERE username = SYS_CONTEXT('userenv', 'current_schema')
531
558
  SQL
532
559
  end
@@ -534,8 +561,8 @@ module ActiveRecord
534
561
  def column_definitions(table_name)
535
562
  (owner, desc_table_name) = @connection.describe(table_name)
536
563
 
537
- select_all(<<~SQL.squish, "Column definitions")
538
- SELECT cols.column_name AS name, cols.data_type AS sql_type,
564
+ select_all(<<~SQL.squish, "SCHEMA")
565
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ cols.column_name AS name, cols.data_type AS sql_type,
539
566
  cols.data_default, cols.nullable, cols.virtual_column, cols.hidden_column,
540
567
  cols.data_type_owner AS sql_type_owner,
541
568
  DECODE(cols.data_type, 'NUMBER', data_precision,
@@ -566,16 +593,16 @@ module ActiveRecord
566
593
  def pk_and_sequence_for(table_name, owner = nil, desc_table_name = nil) #:nodoc:
567
594
  (owner, desc_table_name) = @connection.describe(table_name)
568
595
 
569
- seqs = select_values(<<~SQL.squish, "Sequence")
570
- select us.sequence_name
596
+ seqs = select_values(<<~SQL.squish, "SCHEMA")
597
+ select /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ us.sequence_name
571
598
  from all_sequences us
572
599
  where us.sequence_owner = '#{owner}'
573
600
  and us.sequence_name = upper(#{quote(default_sequence_name(desc_table_name))})
574
601
  SQL
575
602
 
576
603
  # changed back from user_constraints to all_constraints for consistency
577
- pks = select_values(<<~SQL.squish, "Primary Key")
578
- SELECT cc.column_name
604
+ pks = select_values(<<~SQL.squish, "SCHEMA")
605
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ cc.column_name
579
606
  FROM all_constraints c, all_cons_columns cc
580
607
  WHERE c.owner = '#{owner}'
581
608
  AND c.table_name = #{quote(desc_table_name)}
@@ -608,8 +635,8 @@ module ActiveRecord
608
635
  def primary_keys(table_name) # :nodoc:
609
636
  (_owner, desc_table_name) = @connection.describe(table_name)
610
637
 
611
- pks = select_values(<<~SQL.squish, "Primary Keys", [bind_string("table_name", desc_table_name)])
612
- SELECT cc.column_name
638
+ pks = select_values(<<~SQL.squish, "SCHEMA", [bind_string("table_name", desc_table_name)])
639
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ cc.column_name
613
640
  FROM all_constraints c, all_cons_columns cc
614
641
  WHERE c.owner = SYS_CONTEXT('userenv', 'current_schema')
615
642
  AND c.table_name = :table_name
@@ -628,7 +655,7 @@ module ActiveRecord
628
655
  #
629
656
  # It does not construct DISTINCT clause. Just return column names for distinct.
630
657
  order_columns = orders.reject(&:blank?).map { |s|
631
- s = s.to_sql unless s.is_a?(String)
658
+ s = visitor.compile(s) unless s.is_a?(String)
632
659
  # remove any ASC/DESC modifiers
633
660
  s.gsub(/\s+(ASC|DESC)\s*?/i, "")
634
661
  }.reject(&:blank?).map.with_index { |column, i|
@@ -638,8 +665,9 @@ module ActiveRecord
638
665
  end
639
666
 
640
667
  def temporary_table?(table_name) #:nodoc:
641
- select_value(<<~SQL.squish, "temporary table", [bind_string("table_name", table_name.upcase)]) == "Y"
642
- SELECT temporary FROM all_tables WHERE table_name = :table_name and owner = SYS_CONTEXT('userenv', 'current_schema')
668
+ select_value(<<~SQL.squish, "SCHEMA", [bind_string("table_name", table_name.upcase)]) == "Y"
669
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */
670
+ temporary FROM all_tables WHERE table_name = :table_name and owner = SYS_CONTEXT('userenv', 'current_schema')
643
671
  SQL
644
672
  end
645
673
 
@@ -746,3 +774,18 @@ require "active_record/connection_adapters/oracle_enhanced/version"
746
774
  module ActiveRecord
747
775
  autoload :OracleEnhancedProcedures, "active_record/connection_adapters/oracle_enhanced/procedures"
748
776
  end
777
+
778
+ # Workaround for https://github.com/jruby/jruby/issues/6267
779
+ if RUBY_ENGINE == "jruby"
780
+ require "jruby"
781
+
782
+ class org.jruby::RubyObjectSpace::WeakMap
783
+ field_reader :map
784
+ end
785
+
786
+ class ObjectSpace::WeakMap
787
+ def values
788
+ JRuby.ref(self).map.values.reject(&:nil?)
789
+ end
790
+ end
791
+ end
@@ -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