activerecord-oracle_enhanced-adapter 6.0.4 → 6.1.0.rc1

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 +83 -0
  3. data/README.md +1 -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 +6 -6
  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 +15 -3
  18. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +42 -39
  19. data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +16 -17
  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 +57 -15
  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 +253 -0
  25. data/lib/arel/visitors/oracle12.rb +160 -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_adapter_spec.rb +48 -0
  31. data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +4 -2
  32. data/spec/spec_config.yaml.template +2 -2
  33. data/spec/spec_helper.rb +13 -2
  34. data/spec/support/stats.sql +3 -0
  35. metadata +35 -31
@@ -8,7 +8,7 @@ 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")
11
+ sequences = select(<<~SQL.squish, "SCHEMA")
12
12
  SELECT sequence_name, min_value, max_value, increment_by, order_flag, cycle_flag
13
13
  FROM all_sequences
14
14
  where sequence_owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY 1
@@ -17,7 +17,7 @@ module ActiveRecord #:nodoc:
17
17
  structure = sequences.map do |result|
18
18
  "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
19
  end
20
- tables = select_values(<<~SQL.squish, "tables at structure dump")
20
+ tables = select_values(<<~SQL.squish, "SCHEMA")
21
21
  SELECT table_name FROM all_tables t
22
22
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N'
23
23
  AND NOT EXISTS (SELECT mv.mview_name FROM all_mviews mv
@@ -29,7 +29,7 @@ module ActiveRecord #:nodoc:
29
29
  tables.each do |table_name|
30
30
  virtual_columns = virtual_columns_for(table_name) if supports_virtual_columns?
31
31
  ddl = +"CREATE#{ ' GLOBAL TEMPORARY' if temporary_table?(table_name)} TABLE \"#{table_name}\" (\n"
32
- columns = select_all(<<~SQL.squish, "columns at structure dump")
32
+ columns = select_all(<<~SQL.squish, "SCHEMA")
33
33
  SELECT column_name, data_type, data_length, char_used, char_length,
34
34
  data_precision, data_scale, data_default, nullable
35
35
  FROM all_tab_columns
@@ -89,7 +89,7 @@ module ActiveRecord #:nodoc:
89
89
 
90
90
  def structure_dump_primary_key(table) #:nodoc:
91
91
  opts = { name: "", cols: [] }
92
- pks = select_all(<<~SQL.squish, "Primary Keys")
92
+ pks = select_all(<<~SQL.squish, "SCHEMA")
93
93
  SELECT a.constraint_name, a.column_name, a.position
94
94
  FROM all_cons_columns a
95
95
  JOIN all_constraints c
@@ -108,7 +108,7 @@ module ActiveRecord #:nodoc:
108
108
 
109
109
  def structure_dump_unique_keys(table) #:nodoc:
110
110
  keys = {}
111
- uks = select_all(<<~SQL.squish, "Primary Keys")
111
+ uks = select_all(<<~SQL.squish, "SCHEMA")
112
112
  SELECT a.constraint_name, a.column_name, a.position
113
113
  FROM all_cons_columns a
114
114
  JOIN all_constraints c
@@ -144,7 +144,7 @@ module ActiveRecord #:nodoc:
144
144
  end
145
145
 
146
146
  def structure_dump_fk_constraints #:nodoc:
147
- foreign_keys = select_all(<<~SQL.squish, "foreign keys at structure dump")
147
+ foreign_keys = select_all(<<~SQL.squish, "SCHEMA")
148
148
  SELECT table_name FROM all_tables
149
149
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY 1
150
150
  SQL
@@ -172,7 +172,7 @@ module ActiveRecord #:nodoc:
172
172
 
173
173
  def structure_dump_column_comments(table_name)
174
174
  comments = []
175
- columns = select_values(<<~SQL.squish, "column comments at structure dump")
175
+ columns = select_values(<<~SQL.squish, "SCHEMA")
176
176
  SELECT column_name FROM user_tab_columns
177
177
  WHERE table_name = '#{table_name}' ORDER BY column_id
178
178
  SQL
@@ -206,7 +206,7 @@ module ActiveRecord #:nodoc:
206
206
  # Extract all stored procedures, packages, synonyms.
207
207
  def structure_dump_db_stored_code #:nodoc:
208
208
  structure = []
209
- all_source = select_all(<<~SQL.squish, "stored program at structure dump")
209
+ all_source = select_all(<<~SQL.squish, "SCHEMA")
210
210
  SELECT DISTINCT name, type
211
211
  FROM all_source
212
212
  WHERE type IN ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
@@ -238,7 +238,7 @@ module ActiveRecord #:nodoc:
238
238
 
239
239
  def structure_dump_views #:nodoc:
240
240
  structure = []
241
- views = select_all(<<~SQL.squish, "views at structure dump")
241
+ views = select_all(<<~SQL.squish, "SCHEMA")
242
242
  SELECT view_name, text FROM all_views
243
243
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY view_name ASC
244
244
  SQL
@@ -250,7 +250,7 @@ module ActiveRecord #:nodoc:
250
250
 
251
251
  def structure_dump_synonyms #:nodoc:
252
252
  structure = []
253
- synonyms = select_all(<<~SQL.squish, "synonyms at structure dump")
253
+ synonyms = select_all(<<~SQL.squish, "SCHEMA")
254
254
  SELECT owner, synonym_name, table_name, table_owner
255
255
  FROM all_synonyms
256
256
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
@@ -263,13 +263,13 @@ module ActiveRecord #:nodoc:
263
263
  end
264
264
 
265
265
  def structure_drop #:nodoc:
266
- sequences = select_values(<<~SQL.squish, "sequences to drop at structure dump")
266
+ sequences = select_values(<<~SQL.squish, "SCHEMA")
267
267
  SELECT sequence_name FROM all_sequences where sequence_owner = SYS_CONTEXT('userenv', 'current_schema') ORDER BY 1
268
268
  SQL
269
269
  statements = sequences.map do |seq|
270
270
  "DROP SEQUENCE \"#{seq}\""
271
271
  end
272
- tables = select_values(<<~SQL.squish, "tables to drop at structure dump")
272
+ tables = select_values(<<~SQL.squish, "SCHEMA")
273
273
  SELECT table_name from all_tables t
274
274
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N'
275
275
  AND NOT EXISTS (SELECT mv.mview_name FROM all_mviews mv
@@ -285,7 +285,7 @@ module ActiveRecord #:nodoc:
285
285
  end
286
286
 
287
287
  def temp_table_drop #:nodoc:
288
- temporary_tables = select_values(<<~SQL.squish, "temporary tables to drop at structure dump")
288
+ temporary_tables = select_values(<<~SQL.squish, "SCHEMA")
289
289
  SELECT table_name FROM all_tables
290
290
  WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
291
291
  AND secondary = 'N' AND temporary = 'Y' ORDER BY 1
@@ -316,11 +316,10 @@ module ActiveRecord #:nodoc:
316
316
  end
317
317
 
318
318
  private
319
-
320
319
  # Called only if `supports_virtual_columns?` returns true
321
320
  # return [{'column_name' => 'FOOS', 'data_default' => '...'}, ...]
322
321
  def virtual_columns_for(table)
323
- select_all(<<~SQL.squish, "virtual columns for")
322
+ select_all(<<~SQL.squish, "SCHEMA")
324
323
  SELECT column_name, data_default
325
324
  FROM all_tab_cols
326
325
  WHERE virtual_column = 'YES'
@@ -331,7 +330,7 @@ module ActiveRecord #:nodoc:
331
330
 
332
331
  def drop_sql_for_feature(type)
333
332
  short_type = type == "materialized view" ? "mview" : type
334
- features = select_values(<<~SQL.squish, "features to drop")
333
+ features = select_values(<<~SQL.squish, "SCHEMA")
335
334
  SELECT #{short_type}_name FROM all_#{short_type.tableize}
336
335
  where owner = SYS_CONTEXT('userenv', 'current_schema')
337
336
  SQL
@@ -342,7 +341,7 @@ module ActiveRecord #:nodoc:
342
341
  end
343
342
 
344
343
  def drop_sql_for_object(type)
345
- objects = select_values(<<~SQL.squish, "objects to drop")
344
+ objects = select_values(<<~SQL.squish, "SCHEMA")
346
345
  SELECT object_name FROM all_objects
347
346
  WHERE object_type = '#{type.upcase}' and owner = SYS_CONTEXT('userenv', 'current_schema')
348
347
  SQL
@@ -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,32 +527,32 @@ 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")
555
+ select_value(<<~SQL.squish, "SCHEMA")
529
556
  SELECT LOWER(default_tablespace) FROM user_users
530
557
  WHERE username = SYS_CONTEXT('userenv', 'current_schema')
531
558
  SQL
@@ -534,7 +561,7 @@ 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")
564
+ select_all(<<~SQL.squish, "SCHEMA")
538
565
  SELECT 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,
@@ -566,7 +593,7 @@ 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")
596
+ seqs = select_values(<<~SQL.squish, "SCHEMA")
570
597
  select us.sequence_name
571
598
  from all_sequences us
572
599
  where us.sequence_owner = '#{owner}'
@@ -574,7 +601,7 @@ module ActiveRecord
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")
604
+ pks = select_values(<<~SQL.squish, "SCHEMA")
578
605
  SELECT cc.column_name
579
606
  FROM all_constraints c, all_cons_columns cc
580
607
  WHERE c.owner = '#{owner}'
@@ -608,7 +635,7 @@ 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)])
638
+ pks = select_values(<<~SQL.squish, "SCHEMA", [bind_string("table_name", desc_table_name)])
612
639
  SELECT cc.column_name
613
640
  FROM all_constraints c, all_cons_columns cc
614
641
  WHERE c.owner = SYS_CONTEXT('userenv', 'current_schema')
@@ -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,7 +665,7 @@ 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"
668
+ select_value(<<~SQL.squish, "SCHEMA", [bind_string("table_name", table_name.upcase)]) == "Y"
642
669
  SELECT temporary FROM all_tables WHERE table_name = :table_name and owner = SYS_CONTEXT('userenv', 'current_schema')
643
670
  SQL
644
671
  end
@@ -746,3 +773,18 @@ require "active_record/connection_adapters/oracle_enhanced/version"
746
773
  module ActiveRecord
747
774
  autoload :OracleEnhancedProcedures, "active_record/connection_adapters/oracle_enhanced/procedures"
748
775
  end
776
+
777
+ # Workaround for https://github.com/jruby/jruby/issues/6267
778
+ if RUBY_ENGINE == "jruby"
779
+ require "jruby"
780
+
781
+ class org.jruby::RubyObjectSpace::WeakMap
782
+ field_reader :map
783
+ end
784
+
785
+ class ObjectSpace::WeakMap
786
+ def values
787
+ JRuby.ref(self).map.values.reject(&:nil?)
788
+ end
789
+ end
790
+ 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,253 @@
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
+ collector << "("
118
+ values.each_slice(in_clause_length).each_with_index do |valuez, i|
119
+ collector << " OR " unless i == 0
120
+ collector << column_name
121
+ collector << operator
122
+ collector << valuez.join(",")
123
+ collector << ")"
124
+ end
125
+ collector << ")"
126
+ end
127
+
128
+ collector
129
+ end
130
+
131
+ def visit_Arel_Nodes_In(o, collector)
132
+ attr, values = o.left, o.right
133
+
134
+ if Array === values
135
+ unless values.empty?
136
+ values.delete_if { |value| unboundable?(value) }
137
+ end
138
+
139
+ return collector << "1=0" if values.empty?
140
+ end
141
+
142
+ in_clause_length = @connection.in_clause_length
143
+
144
+ if !Array === values || values.length <= in_clause_length
145
+ visit(attr, collector) << " IN ("
146
+ visit(values, collector) << ")"
147
+ else
148
+ collector << "("
149
+ values.each_slice(in_clause_length).each_with_index do |valuez, i|
150
+ collector << " OR " unless i == 0
151
+ visit(attr, collector) << " IN ("
152
+ visit(valuez, collector) << ")"
153
+ end
154
+ collector << ")"
155
+ end
156
+ end
157
+
158
+ def visit_Arel_Nodes_NotIn(o, collector)
159
+ attr, values = o.left, o.right
160
+
161
+ if Array === values
162
+ unless values.empty?
163
+ values.delete_if { |value| unboundable?(value) }
164
+ end
165
+
166
+ return collector << "1=1" if values.empty?
167
+ end
168
+
169
+ in_clause_length = @connection.in_clause_length
170
+
171
+ if !Array === values || values.length <= in_clause_length
172
+ visit(attr, collector) << " NOT IN ("
173
+ visit(values, collector) << ")"
174
+ else
175
+ values.each_slice(in_clause_length).each_with_index do |valuez, i|
176
+ collector << " AND " unless i == 0
177
+ visit(attr, collector) << " NOT IN ("
178
+ visit(valuez, collector) << ")"
179
+ end
180
+ collector
181
+ end
182
+ end
183
+
184
+ def visit_Arel_Nodes_UpdateStatement(o, collector)
185
+ # Oracle does not allow ORDER BY/LIMIT in UPDATEs.
186
+ if o.orders.any? && o.limit.nil?
187
+ # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided,
188
+ # otherwise let the user deal with the error
189
+ o = o.dup
190
+ o.orders = []
191
+ end
192
+
193
+ super
194
+ end
195
+
196
+ ###
197
+ # Hacks for the order clauses specific to Oracle
198
+ def order_hacks(o)
199
+ return o if o.orders.empty?
200
+ return o unless o.cores.any? do |core|
201
+ core.projections.any? do |projection|
202
+ /FIRST_VALUE/ === projection
203
+ end
204
+ end
205
+ # Previous version with join and split broke ORDER BY clause
206
+ # if it contained functions with several arguments (separated by ',').
207
+ #
208
+ # orders = o.orders.map { |x| visit x }.join(', ').split(',')
209
+ orders = o.orders.map do |x|
210
+ string = visit(x, Arel::Collectors::SQLString.new).value
211
+ if string.include?(",")
212
+ split_order_string(string)
213
+ else
214
+ string
215
+ end
216
+ end.flatten
217
+ o.orders = []
218
+ orders.each_with_index do |order, i|
219
+ o.orders <<
220
+ Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i.match?(order)}")
221
+ end
222
+ o
223
+ end
224
+
225
+ # Split string by commas but count opening and closing brackets
226
+ # and ignore commas inside brackets.
227
+ def split_order_string(string)
228
+ array = []
229
+ i = 0
230
+ string.split(",").each do |part|
231
+ if array[i]
232
+ array[i] << "," << part
233
+ else
234
+ # to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral
235
+ array[i] = part.to_s
236
+ end
237
+ i += 1 if array[i].count("(") == array[i].count(")")
238
+ end
239
+ array
240
+ end
241
+
242
+ def visit_Arel_Nodes_BindParam(o, collector)
243
+ collector.add_bind(o.value) { |i| ":a#{i}" }
244
+ end
245
+
246
+ def is_distinct_from(o, collector)
247
+ collector << "DECODE("
248
+ collector = visit [o.left, o.right, 0, 1], collector
249
+ collector << ")"
250
+ end
251
+ end
252
+ end
253
+ end