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
@@ -8,8 +8,9 @@ module ActiveRecord #:nodoc:
8
8
  STATEMENT_TOKEN = "\n\n/\n\n"
9
9
 
10
10
  def structure_dump #:nodoc:
11
- sequences = select(<<~SQL.squish, "sequences to dump at structure dump")
12
- SELECT sequence_name, min_value, max_value, increment_by, order_flag, cycle_flag
11
+ sequences = select(<<~SQL.squish, "SCHEMA")
12
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */
13
+ sequence_name, min_value, max_value, increment_by, order_flag, cycle_flag
13
14
  FROM all_sequences
14
15
  where sequence_owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY 1
15
16
  SQL
@@ -17,8 +18,8 @@ module ActiveRecord #:nodoc:
17
18
  structure = sequences.map do |result|
18
19
  "CREATE SEQUENCE #{quote_table_name(result["sequence_name"])} MINVALUE #{result["min_value"]} MAXVALUE #{result["max_value"]} INCREMENT BY #{result["increment_by"]} #{result["order_flag"] == 'Y' ? "ORDER" : "NOORDER"} #{result["cycle_flag"] == 'Y' ? "CYCLE" : "NOCYCLE"}"
19
20
  end
20
- tables = select_values(<<~SQL.squish, "tables at structure dump")
21
- SELECT table_name FROM all_tables t
21
+ tables = select_values(<<~SQL.squish, "SCHEMA")
22
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ table_name FROM all_tables t
22
23
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N'
23
24
  AND NOT EXISTS (SELECT mv.mview_name FROM all_mviews mv
24
25
  WHERE mv.owner = t.owner AND mv.mview_name = t.table_name)
@@ -29,8 +30,8 @@ module ActiveRecord #:nodoc:
29
30
  tables.each do |table_name|
30
31
  virtual_columns = virtual_columns_for(table_name) if supports_virtual_columns?
31
32
  ddl = +"CREATE#{ ' GLOBAL TEMPORARY' if temporary_table?(table_name)} TABLE \"#{table_name}\" (\n"
32
- columns = select_all(<<~SQL.squish, "columns at structure dump")
33
- SELECT column_name, data_type, data_length, char_used, char_length,
33
+ columns = select_all(<<~SQL.squish, "SCHEMA")
34
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ column_name, data_type, data_length, char_used, char_length,
34
35
  data_precision, data_scale, data_default, nullable
35
36
  FROM all_tab_columns
36
37
  WHERE table_name = '#{table_name}'
@@ -89,8 +90,8 @@ module ActiveRecord #:nodoc:
89
90
 
90
91
  def structure_dump_primary_key(table) #:nodoc:
91
92
  opts = { name: "", cols: [] }
92
- pks = select_all(<<~SQL.squish, "Primary Keys")
93
- SELECT a.constraint_name, a.column_name, a.position
93
+ pks = select_all(<<~SQL.squish, "SCHEMA")
94
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ a.constraint_name, a.column_name, a.position
94
95
  FROM all_cons_columns a
95
96
  JOIN all_constraints c
96
97
  ON a.constraint_name = c.constraint_name
@@ -108,8 +109,8 @@ module ActiveRecord #:nodoc:
108
109
 
109
110
  def structure_dump_unique_keys(table) #:nodoc:
110
111
  keys = {}
111
- uks = select_all(<<~SQL.squish, "Primary Keys")
112
- SELECT a.constraint_name, a.column_name, a.position
112
+ uks = select_all(<<~SQL.squish, "SCHEMA")
113
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ a.constraint_name, a.column_name, a.position
113
114
  FROM all_cons_columns a
114
115
  JOIN all_constraints c
115
116
  ON a.constraint_name = c.constraint_name
@@ -144,8 +145,8 @@ module ActiveRecord #:nodoc:
144
145
  end
145
146
 
146
147
  def structure_dump_fk_constraints #:nodoc:
147
- foreign_keys = select_all(<<~SQL.squish, "foreign keys at structure dump")
148
- SELECT table_name FROM all_tables
148
+ foreign_keys = select_all(<<~SQL.squish, "SCHEMA")
149
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ table_name FROM all_tables
149
150
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY 1
150
151
  SQL
151
152
  fks = foreign_keys.map do |table|
@@ -172,8 +173,8 @@ module ActiveRecord #:nodoc:
172
173
 
173
174
  def structure_dump_column_comments(table_name)
174
175
  comments = []
175
- columns = select_values(<<~SQL.squish, "column comments at structure dump")
176
- SELECT column_name FROM user_tab_columns
176
+ columns = select_values(<<~SQL.squish, "SCHEMA")
177
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ column_name FROM user_tab_columns
177
178
  WHERE table_name = '#{table_name}' ORDER BY column_id
178
179
  SQL
179
180
 
@@ -206,8 +207,8 @@ module ActiveRecord #:nodoc:
206
207
  # Extract all stored procedures, packages, synonyms.
207
208
  def structure_dump_db_stored_code #:nodoc:
208
209
  structure = []
209
- all_source = select_all(<<~SQL.squish, "stored program at structure dump")
210
- SELECT DISTINCT name, type
210
+ all_source = select_all(<<~SQL.squish, "SCHEMA")
211
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ DISTINCT name, type
211
212
  FROM all_source
212
213
  WHERE type IN ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
213
214
  AND name NOT LIKE 'BIN$%'
@@ -216,7 +217,7 @@ module ActiveRecord #:nodoc:
216
217
  all_source.each do |source|
217
218
  ddl = +"CREATE OR REPLACE \n"
218
219
  texts = select_all(<<~SQL.squish, "all source at structure dump")
219
- SELECT text
220
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ text
220
221
  FROM all_source
221
222
  WHERE name = '#{source['name']}'
222
223
  AND type = '#{source['type']}'
@@ -238,8 +239,8 @@ module ActiveRecord #:nodoc:
238
239
 
239
240
  def structure_dump_views #:nodoc:
240
241
  structure = []
241
- views = select_all(<<~SQL.squish, "views at structure dump")
242
- SELECT view_name, text FROM all_views
242
+ views = select_all(<<~SQL.squish, "SCHEMA")
243
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ view_name, text FROM all_views
243
244
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY view_name ASC
244
245
  SQL
245
246
  views.each do |view|
@@ -250,8 +251,8 @@ module ActiveRecord #:nodoc:
250
251
 
251
252
  def structure_dump_synonyms #:nodoc:
252
253
  structure = []
253
- synonyms = select_all(<<~SQL.squish, "synonyms at structure dump")
254
- SELECT owner, synonym_name, table_name, table_owner
254
+ synonyms = select_all(<<~SQL.squish, "SCHEMA")
255
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ owner, synonym_name, table_name, table_owner
255
256
  FROM all_synonyms
256
257
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
257
258
  SQL
@@ -263,14 +264,15 @@ module ActiveRecord #:nodoc:
263
264
  end
264
265
 
265
266
  def structure_drop #:nodoc:
266
- sequences = select_values(<<~SQL.squish, "sequences to drop at structure dump")
267
- SELECT sequence_name FROM all_sequences where sequence_owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY 1
267
+ sequences = select_values(<<~SQL.squish, "SCHEMA")
268
+ SELECT/*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */
269
+ sequence_name FROM all_sequences where sequence_owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY 1
268
270
  SQL
269
271
  statements = sequences.map do |seq|
270
272
  "DROP SEQUENCE \"#{seq}\""
271
273
  end
272
- tables = select_values(<<~SQL.squish, "tables to drop at structure dump")
273
- SELECT table_name from all_tables t
274
+ tables = select_values(<<~SQL.squish, "SCHEMA")
275
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ table_name from all_tables t
274
276
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N'
275
277
  AND NOT EXISTS (SELECT mv.mview_name FROM all_mviews mv
276
278
  WHERE mv.owner = t.owner AND mv.mview_name = t.table_name)
@@ -285,8 +287,8 @@ module ActiveRecord #:nodoc:
285
287
  end
286
288
 
287
289
  def temp_table_drop #:nodoc:
288
- temporary_tables = select_values(<<~SQL.squish, "temporary tables to drop at structure dump")
289
- SELECT table_name FROM all_tables
290
+ temporary_tables = select_values(<<~SQL.squish, "SCHEMA")
291
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ table_name FROM all_tables
290
292
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
291
293
  AND secondary = 'N' AND temporary = 'Y' ORDER BY 1
292
294
  SQL
@@ -316,12 +318,11 @@ module ActiveRecord #:nodoc:
316
318
  end
317
319
 
318
320
  private
319
-
320
321
  # Called only if `supports_virtual_columns?` returns true
321
322
  # return [{'column_name' => 'FOOS', 'data_default' => '...'}, ...]
322
323
  def virtual_columns_for(table)
323
- select_all(<<~SQL.squish, "virtual columns for")
324
- SELECT column_name, data_default
324
+ select_all(<<~SQL.squish, "SCHEMA")
325
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ column_name, data_default
325
326
  FROM all_tab_cols
326
327
  WHERE virtual_column = 'YES'
327
328
  AND owner = SYS_CONTEXT('userenv', 'current_schema')
@@ -331,8 +332,8 @@ module ActiveRecord #:nodoc:
331
332
 
332
333
  def drop_sql_for_feature(type)
333
334
  short_type = type == "materialized view" ? "mview" : type
334
- features = select_values(<<~SQL.squish, "features to drop")
335
- SELECT #{short_type}_name FROM all_#{short_type.tableize}
335
+ features = select_values(<<~SQL.squish, "SCHEMA")
336
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ #{short_type}_name FROM all_#{short_type.tableize}
336
337
  where owner = SYS_CONTEXT('userenv', 'current_schema')
337
338
  SQL
338
339
  statements = features.map do |name|
@@ -342,8 +343,8 @@ module ActiveRecord #:nodoc:
342
343
  end
343
344
 
344
345
  def drop_sql_for_object(type)
345
- objects = select_values(<<~SQL.squish, "objects to drop")
346
- SELECT object_name FROM all_objects
346
+ objects = select_values(<<~SQL.squish, "SCHEMA")
347
+ SELECT /*+ OPTIMIZER_FEATURES_ENABLE('11.2.0.2') */ object_name FROM all_objects
347
348
  WHERE object_type = '#{type.upcase}' and owner = SYS_CONTEXT('userenv', 'current_schema')
348
349
  SQL
349
350
  statements = objects.map do |name|
@@ -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