activerecord-oracle_enhanced-adapter 6.0.4 → 6.1.0.rc1
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.
- checksums.yaml +4 -4
- data/History.md +83 -0
- data/README.md +1 -1
- data/VERSION +1 -1
- data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +2 -3
- data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +0 -1
- data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +0 -9
- data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +6 -6
- data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +4 -5
- data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +0 -1
- data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +3 -4
- data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +1 -2
- data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +2 -3
- data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +0 -1
- data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +2 -3
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +2 -3
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +15 -3
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +42 -39
- data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +16 -17
- data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +2 -1
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +57 -15
- data/lib/active_record/type/oracle_enhanced/boolean.rb +0 -1
- data/lib/active_record/type/oracle_enhanced/integer.rb +0 -1
- data/lib/arel/visitors/oracle.rb +253 -0
- data/lib/arel/visitors/oracle12.rb +160 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +9 -3
- data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +6 -1
- data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +0 -1
- data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +27 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +48 -0
- data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +4 -2
- data/spec/spec_config.yaml.template +2 -2
- data/spec/spec_helper.rb +13 -2
- data/spec/support/stats.sql +3 -0
- 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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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, "
|
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
|
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, "
|
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
|
@@ -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
|