activerecord-oracle_enhanced-adapter 1.2.3 → 1.2.4
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.
- data/History.txt +17 -0
- data/License.txt +1 -1
- data/README.rdoc +3 -1
- data/VERSION +1 -1
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +6 -3
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +333 -109
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +60 -36
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +13 -18
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +3 -4
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +3 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +86 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +31 -2
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +267 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +6 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +22 -75
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +61 -0
- data/spec/spec_helper.rb +3 -15
- metadata +4 -2
data/History.txt
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
== 1.2.4 2010-02-23
|
2
|
+
|
3
|
+
* Enhancements:
|
4
|
+
* rake db:test:purge will drop all schema objects from test schema (including views, synonyms, packages, functions, procedures) -
|
5
|
+
they should be always reloaded before tests run if necessary
|
6
|
+
* added views, synonyms, packages, functions, procedures, indexes, triggers, types, primary, unique and foreign key constraints to structure dump
|
7
|
+
* added :temporary option for create_table to create temporary tables
|
8
|
+
* added :tablespace option for add_index
|
9
|
+
* support function based indexes in schema dump
|
10
|
+
* support JNDI database connections in JRuby
|
11
|
+
* check ruby-oci8 minimum version 2.0.3
|
12
|
+
* added savepoints support (nested ActiveRecord transactions)
|
13
|
+
* Bug fixes:
|
14
|
+
* typecast returned BigDecimal integer values to Fixnum or Bignum
|
15
|
+
(to avoid issues with _before_type_cast values for id attributes because _before_type_cast is used in form helpers)
|
16
|
+
* clear table columns cache after columns definition change in migrations
|
17
|
+
|
1
18
|
== 1.2.3 2009-12-09
|
2
19
|
|
3
20
|
* Enhancements
|
data/License.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2008 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
|
1
|
+
Copyright (c) 2008-2010 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
data/README.rdoc
CHANGED
@@ -48,12 +48,14 @@ In addition install either ruby-oci8 (for MRI/YARV) or copy Oracle JDBC driver t
|
|
48
48
|
* Anton Jenkins
|
49
49
|
* Dave Smylie
|
50
50
|
* Alex Rothenberg
|
51
|
+
* Billy Reisinger
|
52
|
+
* David Blain
|
51
53
|
|
52
54
|
== LICENSE:
|
53
55
|
|
54
56
|
(The MIT License)
|
55
57
|
|
56
|
-
Copyright (c)
|
58
|
+
Copyright (c) 2008-2010 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
|
57
59
|
|
58
60
|
Permission is hereby granted, free of charge, to any person obtaining
|
59
61
|
a copy of this software and associated documentation files (the
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.2.
|
1
|
+
1.2.4
|
@@ -17,6 +17,7 @@ namespace :db do
|
|
17
17
|
abcs = ActiveRecord::Base.configurations
|
18
18
|
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
|
19
19
|
File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
|
20
|
+
File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_fk_constraints }
|
20
21
|
if ActiveRecord::Base.connection.supports_migrations?
|
21
22
|
File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
|
22
23
|
end
|
@@ -32,15 +33,17 @@ namespace :db do
|
|
32
33
|
abcs = ActiveRecord::Base.configurations
|
33
34
|
ActiveRecord::Base.establish_connection(:test)
|
34
35
|
IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |ddl|
|
35
|
-
|
36
|
+
ddl.chop! if ddl.last == ";"
|
37
|
+
ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
41
|
redefine_task :purge => :environment do
|
40
42
|
abcs = ActiveRecord::Base.configurations
|
41
43
|
ActiveRecord::Base.establish_connection(:test)
|
42
|
-
ActiveRecord::Base.connection.
|
43
|
-
|
44
|
+
ActiveRecord::Base.connection.full_drop.split("\n\n").each do |ddl|
|
45
|
+
ddl.chop! if ddl.last == ";"
|
46
|
+
ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
|
44
47
|
end
|
45
48
|
end
|
46
49
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
# oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g, 11g
|
2
3
|
#
|
3
4
|
# Authors or original oracle_adapter: Graham Jenkins, Michael Schoen
|
@@ -214,7 +215,7 @@ module ActiveRecord
|
|
214
215
|
Date.new(value.year, value.month, value.day) : value
|
215
216
|
end
|
216
217
|
|
217
|
-
class <<self
|
218
|
+
class << self
|
218
219
|
protected
|
219
220
|
|
220
221
|
def fallback_string_to_date(string) #:nodoc:
|
@@ -404,33 +405,45 @@ module ActiveRecord
|
|
404
405
|
@quoted_column_names, @quoted_table_names = {}, {}
|
405
406
|
end
|
406
407
|
|
408
|
+
ADAPTER_NAME = 'OracleEnhanced'.freeze
|
409
|
+
|
407
410
|
def adapter_name #:nodoc:
|
408
|
-
|
411
|
+
ADAPTER_NAME
|
409
412
|
end
|
410
413
|
|
411
414
|
def supports_migrations? #:nodoc:
|
412
415
|
true
|
413
416
|
end
|
414
417
|
|
418
|
+
def supports_savepoints? #:nodoc:
|
419
|
+
true
|
420
|
+
end
|
421
|
+
|
422
|
+
#:stopdoc:
|
423
|
+
NATIVE_DATABASE_TYPES = {
|
424
|
+
:primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
|
425
|
+
:string => { :name => "VARCHAR2", :limit => 255 },
|
426
|
+
:text => { :name => "CLOB" },
|
427
|
+
:integer => { :name => "NUMBER", :limit => 38 },
|
428
|
+
:float => { :name => "NUMBER" },
|
429
|
+
:decimal => { :name => "DECIMAL" },
|
430
|
+
:datetime => { :name => "DATE" },
|
431
|
+
# changed to native TIMESTAMP type
|
432
|
+
# :timestamp => { :name => "DATE" },
|
433
|
+
:timestamp => { :name => "TIMESTAMP" },
|
434
|
+
:time => { :name => "DATE" },
|
435
|
+
:date => { :name => "DATE" },
|
436
|
+
:binary => { :name => "BLOB" },
|
437
|
+
:boolean => { :name => "NUMBER", :limit => 1 }
|
438
|
+
}
|
439
|
+
# if emulate_booleans_from_strings then store booleans in VARCHAR2
|
440
|
+
NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
|
441
|
+
:boolean => { :name => "VARCHAR2", :limit => 1 }
|
442
|
+
)
|
443
|
+
#:startdoc:
|
444
|
+
|
415
445
|
def native_database_types #:nodoc:
|
416
|
-
|
417
|
-
:primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
|
418
|
-
:string => { :name => "VARCHAR2", :limit => 255 },
|
419
|
-
:text => { :name => "CLOB" },
|
420
|
-
:integer => { :name => "NUMBER", :limit => 38 },
|
421
|
-
:float => { :name => "NUMBER" },
|
422
|
-
:decimal => { :name => "DECIMAL" },
|
423
|
-
:datetime => { :name => "DATE" },
|
424
|
-
# changed to native TIMESTAMP type
|
425
|
-
# :timestamp => { :name => "DATE" },
|
426
|
-
:timestamp => { :name => "TIMESTAMP" },
|
427
|
-
:time => { :name => "DATE" },
|
428
|
-
:date => { :name => "DATE" },
|
429
|
-
:binary => { :name => "BLOB" },
|
430
|
-
# if emulate_booleans_from_strings then store booleans in VARCHAR2
|
431
|
-
:boolean => emulate_booleans_from_strings ?
|
432
|
-
{ :name => "VARCHAR2", :limit => 1 } : { :name => "NUMBER", :limit => 1 }
|
433
|
-
}
|
446
|
+
emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
|
434
447
|
end
|
435
448
|
|
436
449
|
# maximum length of Oracle identifiers
|
@@ -595,7 +608,8 @@ module ActiveRecord
|
|
595
608
|
end
|
596
609
|
protected :insert_sql
|
597
610
|
|
598
|
-
|
611
|
+
# use in set_sequence_name to avoid fetching primary key value from sequence
|
612
|
+
AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze
|
599
613
|
|
600
614
|
# Returns the next sequence value from a sequence generator. Not generally
|
601
615
|
# called directly; used by ActiveRecord to get the next primary key value
|
@@ -622,6 +636,18 @@ module ActiveRecord
|
|
622
636
|
@connection.autocommit = true
|
623
637
|
end
|
624
638
|
|
639
|
+
def create_savepoint #:nodoc:
|
640
|
+
execute("SAVEPOINT #{current_savepoint_name}")
|
641
|
+
end
|
642
|
+
|
643
|
+
def rollback_to_savepoint #:nodoc:
|
644
|
+
execute("ROLLBACK TO #{current_savepoint_name}")
|
645
|
+
end
|
646
|
+
|
647
|
+
def release_savepoint #:nodoc:
|
648
|
+
# there is no RELEASE SAVEPOINT statement in Oracle
|
649
|
+
end
|
650
|
+
|
625
651
|
def add_limit_offset!(sql, options) #:nodoc:
|
626
652
|
# added to_i for limit and offset to protect from SQL injection
|
627
653
|
offset = (options[:offset] || 0).to_i
|
@@ -713,14 +739,19 @@ module ActiveRecord
|
|
713
739
|
#
|
714
740
|
# see: abstract/schema_statements.rb
|
715
741
|
|
716
|
-
#
|
742
|
+
# Current database name
|
717
743
|
def current_database
|
718
|
-
|
744
|
+
select_value("select sys_context('userenv','db_name') from dual")
|
719
745
|
end
|
720
746
|
|
721
|
-
#
|
747
|
+
# Current database session user
|
722
748
|
def current_user
|
723
|
-
|
749
|
+
select_value("select sys_context('userenv','session_user') from dual")
|
750
|
+
end
|
751
|
+
|
752
|
+
# Default tablespace name of current user
|
753
|
+
def default_tablespace
|
754
|
+
select_value("select lower(default_tablespace) from user_users where username = sys_context('userenv','session_user')")
|
724
755
|
end
|
725
756
|
|
726
757
|
def tables(name = nil) #:nodoc:
|
@@ -735,37 +766,38 @@ module ActiveRecord
|
|
735
766
|
def indexes(table_name, name = nil) #:nodoc:
|
736
767
|
(owner, table_name, db_link) = @connection.describe(table_name)
|
737
768
|
unless all_schema_indexes
|
769
|
+
default_tablespace_name = default_tablespace
|
738
770
|
result = select_all(<<-SQL)
|
739
|
-
SELECT lower(i.table_name) as table_name, lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
|
740
|
-
FROM all_indexes#{db_link} i
|
771
|
+
SELECT lower(i.table_name) as table_name, lower(i.index_name) as index_name, i.uniqueness, lower(i.tablespace_name) as tablespace_name, lower(c.column_name) as column_name, e.column_expression as column_expression
|
772
|
+
FROM all_indexes#{db_link} i
|
773
|
+
JOIN all_ind_columns#{db_link} c on c.index_name = i.index_name and c.index_owner = i.owner
|
774
|
+
LEFT OUTER JOIN all_ind_expressions#{db_link} e on e.index_name = i.index_name and e.index_owner = i.owner and e.column_position = c.column_position
|
741
775
|
WHERE i.owner = '#{owner}'
|
742
776
|
AND i.table_owner = '#{owner}'
|
743
|
-
AND c.index_name = i.index_name
|
744
|
-
AND c.index_owner = i.owner
|
745
777
|
AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
|
746
778
|
ORDER BY i.index_name, c.column_position
|
747
779
|
SQL
|
748
|
-
|
780
|
+
|
749
781
|
current_index = nil
|
750
782
|
self.all_schema_indexes = []
|
751
|
-
|
783
|
+
|
752
784
|
result.each do |row|
|
753
785
|
# have to keep track of indexes because above query returns dups
|
754
786
|
# there is probably a better query we could figure out
|
755
787
|
if current_index != row['index_name']
|
756
|
-
|
788
|
+
all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE",
|
789
|
+
row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], [])
|
757
790
|
current_index = row['index_name']
|
758
791
|
end
|
759
|
-
|
760
|
-
self.all_schema_indexes.last.columns << row['column_name']
|
792
|
+
all_schema_indexes.last.columns << (row['column_expression'].nil? ? row['column_name'] : row['column_expression'].gsub('"','').downcase)
|
761
793
|
end
|
762
794
|
end
|
763
|
-
|
795
|
+
|
764
796
|
# Return the indexes just for the requested table, since AR is structured that way
|
765
797
|
table_name = table_name.downcase
|
766
798
|
all_schema_indexes.select{|i| i.table == table_name}
|
767
799
|
end
|
768
|
-
|
800
|
+
|
769
801
|
@@ignore_table_columns = nil #:nodoc:
|
770
802
|
|
771
803
|
# set ignored columns for table
|
@@ -899,6 +931,11 @@ module ActiveRecord
|
|
899
931
|
@@columns_cache = nil
|
900
932
|
end
|
901
933
|
|
934
|
+
# used in migrations to clear column cache for specified table
|
935
|
+
def clear_table_columns_cache(table_name)
|
936
|
+
@@columns_cache[table_name.to_s] = nil if @@cache_columns
|
937
|
+
end
|
938
|
+
|
902
939
|
##
|
903
940
|
# :singleton-method:
|
904
941
|
# Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
|
@@ -937,38 +974,52 @@ module ActiveRecord
|
|
937
974
|
# t.string :first_name, :comment => “Given name”
|
938
975
|
# t.string :last_name, :comment => “Surname”
|
939
976
|
# end
|
977
|
+
|
940
978
|
def create_table(name, options = {}, &block)
|
941
979
|
create_sequence = options[:id] != false
|
942
980
|
column_comments = {}
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
981
|
+
|
982
|
+
table_definition = TableDefinition.new(self)
|
983
|
+
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(name.to_s.singularize)) unless options[:id] == false
|
984
|
+
|
985
|
+
# store that primary key was defined in create_table block
|
986
|
+
unless create_sequence
|
987
|
+
class << table_definition
|
988
|
+
attr_accessor :create_sequence
|
989
|
+
def primary_key(*args)
|
990
|
+
self.create_sequence = true
|
991
|
+
super(*args)
|
952
992
|
end
|
953
993
|
end
|
994
|
+
end
|
954
995
|
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
end
|
963
|
-
super(name, type, options)
|
996
|
+
# store column comments
|
997
|
+
class << table_definition
|
998
|
+
attr_accessor :column_comments
|
999
|
+
def column(name, type, options = {})
|
1000
|
+
if options[:comment]
|
1001
|
+
self.column_comments ||= {}
|
1002
|
+
self.column_comments[name] = options[:comment]
|
964
1003
|
end
|
1004
|
+
super(name, type, options)
|
965
1005
|
end
|
1006
|
+
end
|
966
1007
|
|
967
|
-
|
968
|
-
|
969
|
-
|
1008
|
+
result = block.call(table_definition) if block
|
1009
|
+
create_sequence = create_sequence || table_definition.create_sequence
|
1010
|
+
column_comments = table_definition.column_comments if table_definition.column_comments
|
1011
|
+
|
1012
|
+
|
1013
|
+
if options[:force] && table_exists?(name)
|
1014
|
+
drop_table(name, options)
|
970
1015
|
end
|
971
1016
|
|
1017
|
+
create_sql = "CREATE#{' GLOBAL TEMPORARY' if options[:temporary]} TABLE "
|
1018
|
+
create_sql << "#{quote_table_name(name)} ("
|
1019
|
+
create_sql << table_definition.to_sql
|
1020
|
+
create_sql << ") #{options[:options]}"
|
1021
|
+
execute create_sql
|
1022
|
+
|
972
1023
|
create_sequence_and_trigger(name, options) if create_sequence
|
973
1024
|
|
974
1025
|
add_table_comment name, options[:comment]
|
@@ -987,12 +1038,29 @@ module ActiveRecord
|
|
987
1038
|
super(name)
|
988
1039
|
seq_name = options[:sequence_name] || default_sequence_name(name)
|
989
1040
|
execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
|
1041
|
+
ensure
|
1042
|
+
clear_table_columns_cache(name)
|
990
1043
|
end
|
991
1044
|
|
992
1045
|
# clear cached indexes when adding new index
|
993
1046
|
def add_index(table_name, column_name, options = {}) #:nodoc:
|
994
1047
|
self.all_schema_indexes = nil
|
995
|
-
|
1048
|
+
column_names = Array(column_name)
|
1049
|
+
index_name = index_name(table_name, :column => column_names)
|
1050
|
+
|
1051
|
+
if Hash === options # legacy support, since this param was a string
|
1052
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
1053
|
+
index_name = options[:name] || index_name
|
1054
|
+
tablespace = if options[:tablespace]
|
1055
|
+
" TABLESPACE #{options[:tablespace]}"
|
1056
|
+
else
|
1057
|
+
""
|
1058
|
+
end
|
1059
|
+
else
|
1060
|
+
index_type = options
|
1061
|
+
end
|
1062
|
+
quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
|
1063
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace}"
|
996
1064
|
end
|
997
1065
|
|
998
1066
|
# clear cached indexes when removing index
|
@@ -1026,10 +1094,14 @@ module ActiveRecord
|
|
1026
1094
|
options[:type] = type
|
1027
1095
|
add_column_options!(add_column_sql, options)
|
1028
1096
|
execute(add_column_sql)
|
1097
|
+
ensure
|
1098
|
+
clear_table_columns_cache(table_name)
|
1029
1099
|
end
|
1030
1100
|
|
1031
1101
|
def change_column_default(table_name, column_name, default) #:nodoc:
|
1032
1102
|
execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
|
1103
|
+
ensure
|
1104
|
+
clear_table_columns_cache(table_name)
|
1033
1105
|
end
|
1034
1106
|
|
1035
1107
|
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
@@ -1055,14 +1127,20 @@ module ActiveRecord
|
|
1055
1127
|
options[:type] = type
|
1056
1128
|
add_column_options!(change_column_sql, options)
|
1057
1129
|
execute(change_column_sql)
|
1130
|
+
ensure
|
1131
|
+
clear_table_columns_cache(table_name)
|
1058
1132
|
end
|
1059
1133
|
|
1060
1134
|
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
1061
1135
|
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
|
1136
|
+
ensure
|
1137
|
+
clear_table_columns_cache(table_name)
|
1062
1138
|
end
|
1063
1139
|
|
1064
1140
|
def remove_column(table_name, column_name) #:nodoc:
|
1065
1141
|
execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
1142
|
+
ensure
|
1143
|
+
clear_table_columns_cache(table_name)
|
1066
1144
|
end
|
1067
1145
|
|
1068
1146
|
def add_comment(table_name, column_name, comment) #:nodoc:
|
@@ -1124,78 +1202,179 @@ module ActiveRecord
|
|
1124
1202
|
|
1125
1203
|
def structure_dump #:nodoc:
|
1126
1204
|
s = select_all("select sequence_name from user_sequences order by 1").inject("") do |structure, seq|
|
1127
|
-
structure << "create sequence #{seq.to_a.first.last}
|
1205
|
+
structure << "create sequence #{seq.to_a.first.last}#{STATEMENT_TOKEN}"
|
1128
1206
|
end
|
1129
1207
|
|
1130
1208
|
# changed select from user_tables to all_tables - much faster in large data dictionaries
|
1131
1209
|
select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |structure, table|
|
1132
|
-
|
1210
|
+
table_name = table['table_name']
|
1211
|
+
virtual_columns = virtual_columns_for(table_name)
|
1212
|
+
ddl = "create#{ ' global temporary' if temporary_table?(table_name)} table #{table_name} (\n "
|
1133
1213
|
cols = select_all(%Q{
|
1134
1214
|
select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
|
1135
1215
|
from user_tab_columns
|
1136
|
-
where table_name = '#{
|
1216
|
+
where table_name = '#{table_name}'
|
1137
1217
|
order by column_id
|
1138
1218
|
}).map do |row|
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
col << ')'
|
1144
|
-
elsif row['data_type'].include?('CHAR')
|
1145
|
-
length = row['char_used'] == 'C' ? row['char_length'].to_i : row['data_length'].to_i
|
1146
|
-
col << "(#{length})"
|
1219
|
+
if(v = virtual_columns.find {|col| col['column_name'] == row['column_name']})
|
1220
|
+
structure_dump_virtual_column(row, v['data_default'])
|
1221
|
+
else
|
1222
|
+
structure_dump_column(row)
|
1147
1223
|
end
|
1148
|
-
col << " default #{row['data_default']}" if !row['data_default'].nil?
|
1149
|
-
col << ' not null' if row['nullable'] == 'N'
|
1150
|
-
col
|
1151
1224
|
end
|
1152
1225
|
ddl << cols.join(",\n ")
|
1153
|
-
ddl <<
|
1226
|
+
ddl << structure_dump_constraints(table_name)
|
1227
|
+
ddl << "\n)#{STATEMENT_TOKEN}"
|
1154
1228
|
structure << ddl
|
1229
|
+
structure << structure_dump_indexes(table_name)
|
1155
1230
|
end
|
1156
1231
|
end
|
1157
|
-
|
1232
|
+
|
1233
|
+
def structure_dump_virtual_column(column, data_default) #:nodoc:
|
1234
|
+
data_default = data_default.gsub(/"/, '')
|
1235
|
+
col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
|
1236
|
+
if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
|
1237
|
+
col << "(#{column['data_precision'].to_i}"
|
1238
|
+
col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
|
1239
|
+
col << ')'
|
1240
|
+
elsif column['data_type'].include?('CHAR')
|
1241
|
+
length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
|
1242
|
+
col << "(#{length})"
|
1243
|
+
end
|
1244
|
+
col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
|
1245
|
+
end
|
1246
|
+
|
1247
|
+
def structure_dump_column(column) #:nodoc:
|
1248
|
+
col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
|
1249
|
+
if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
|
1250
|
+
col << "(#{column['data_precision'].to_i}"
|
1251
|
+
col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
|
1252
|
+
col << ')'
|
1253
|
+
elsif column['data_type'].include?('CHAR')
|
1254
|
+
length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
|
1255
|
+
col << "(#{length})"
|
1256
|
+
end
|
1257
|
+
col << " default #{column['data_default']}" if !column['data_default'].nil?
|
1258
|
+
col << ' not null' if column['nullable'] == 'N'
|
1259
|
+
col
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
def structure_dump_constraints(table) #:nodoc:
|
1263
|
+
out = [structure_dump_primary_key(table), structure_dump_unique_keys(table)].flatten.compact
|
1264
|
+
out.length > 0 ? ",\n#{out.join(",\n")}" : ''
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
def structure_dump_primary_key(table) #:nodoc:
|
1268
|
+
opts = {:name => '', :cols => []}
|
1269
|
+
pks = select_all(<<-SQL, "Primary Keys")
|
1270
|
+
select a.constraint_name, a.column_name, a.position
|
1271
|
+
from user_cons_columns a
|
1272
|
+
join user_constraints c
|
1273
|
+
on a.constraint_name = c.constraint_name
|
1274
|
+
where c.table_name = '#{table.upcase}'
|
1275
|
+
and c.constraint_type = 'P'
|
1276
|
+
and c.owner = sys_context('userenv', 'session_user')
|
1277
|
+
SQL
|
1278
|
+
pks.each do |row|
|
1279
|
+
opts[:name] = row['constraint_name']
|
1280
|
+
opts[:cols][row['position']-1] = row['column_name']
|
1281
|
+
end
|
1282
|
+
opts[:cols].length > 0 ? " CONSTRAINT #{opts[:name]} PRIMARY KEY (#{opts[:cols].join(',')})" : nil
|
1283
|
+
end
|
1284
|
+
|
1285
|
+
def structure_dump_unique_keys(table) #:nodoc:
|
1286
|
+
keys = {}
|
1287
|
+
uks = select_all(<<-SQL, "Primary Keys")
|
1288
|
+
select a.constraint_name, a.column_name, a.position
|
1289
|
+
from user_cons_columns a
|
1290
|
+
join user_constraints c
|
1291
|
+
on a.constraint_name = c.constraint_name
|
1292
|
+
where c.table_name = '#{table.upcase}'
|
1293
|
+
and c.constraint_type = 'U'
|
1294
|
+
and c.owner = sys_context('userenv', 'session_user')
|
1295
|
+
SQL
|
1296
|
+
uks.each do |uk|
|
1297
|
+
keys[uk['constraint_name']] ||= []
|
1298
|
+
keys[uk['constraint_name']][uk['position']-1] = uk['column_name']
|
1299
|
+
end
|
1300
|
+
keys.map do |k,v|
|
1301
|
+
" CONSTRAINT #{k} UNIQUE (#{v.join(',')})"
|
1302
|
+
end
|
1303
|
+
end
|
1304
|
+
|
1305
|
+
def structure_dump_fk_constraints #:nodoc:
|
1306
|
+
fks = select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").map do |table|
|
1307
|
+
if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any?
|
1308
|
+
foreign_keys.map do |fk|
|
1309
|
+
column = fk.options[:column] || "#{fk.to_table.to_s.singularize}_id"
|
1310
|
+
constraint_name = foreign_key_constraint_name(fk.from_table, column, fk.options)
|
1311
|
+
sql = "ALTER TABLE #{quote_table_name(fk.from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} "
|
1312
|
+
sql << "#{foreign_key_definition(fk.to_table, fk.options)}"
|
1313
|
+
end
|
1314
|
+
end
|
1315
|
+
end.flatten.compact.join(STATEMENT_TOKEN)
|
1316
|
+
fks.length > 1 ? "#{fks}#{STATEMENT_TOKEN}" : ''
|
1317
|
+
end
|
1318
|
+
|
1158
1319
|
# Extract all stored procedures, packages, synonyms and views.
|
1159
1320
|
def structure_dump_db_stored_code #:nodoc:
|
1160
|
-
structure = "
|
1321
|
+
structure = ""
|
1161
1322
|
select_all("select distinct name, type
|
1162
1323
|
from all_source
|
1163
|
-
where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION')
|
1164
|
-
and owner = sys_context('userenv','session_user')").
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
1173
|
-
|
1174
|
-
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1324
|
+
where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
|
1325
|
+
and owner = sys_context('userenv','session_user') order by type").each do |source|
|
1326
|
+
ddl = "create or replace \n "
|
1327
|
+
lines = select_all(%Q{
|
1328
|
+
select text
|
1329
|
+
from all_source
|
1330
|
+
where name = '#{source['name']}'
|
1331
|
+
and type = '#{source['type']}'
|
1332
|
+
and owner = sys_context('userenv','session_user')
|
1333
|
+
order by line
|
1334
|
+
}).map do |row|
|
1335
|
+
ddl << row['text'] if row['text'].size > 1
|
1336
|
+
end
|
1337
|
+
ddl << ";" unless ddl.strip.last == ";"
|
1338
|
+
structure << ddl << STATEMENT_TOKEN
|
1178
1339
|
end
|
1179
1340
|
|
1180
1341
|
# export views
|
1181
|
-
select_all("select view_name, text from user_views").
|
1342
|
+
select_all("select view_name, text from user_views").each do |view|
|
1182
1343
|
ddl = "create or replace view #{view['view_name']} AS\n "
|
1183
1344
|
# any views with empty lines will cause OCI to barf when loading. remove blank lines =/
|
1184
1345
|
ddl << view['text'].gsub(/^\n/, '')
|
1185
|
-
ddl <<
|
1186
|
-
structure << ddl
|
1346
|
+
structure << ddl << STATEMENT_TOKEN
|
1187
1347
|
end
|
1188
1348
|
|
1189
1349
|
# export synonyms
|
1190
1350
|
select_all("select owner, synonym_name, table_name, table_owner
|
1191
1351
|
from all_synonyms
|
1192
|
-
where
|
1193
|
-
ddl = "create or replace #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']} for #{synonym['table_owner']}.#{synonym['table_name']}
|
1194
|
-
structure << ddl
|
1352
|
+
where owner = sys_context('userenv','session_user') ").each do |synonym|
|
1353
|
+
ddl = "create or replace #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']} for #{synonym['table_owner']}.#{synonym['table_name']}"
|
1354
|
+
structure << ddl << STATEMENT_TOKEN
|
1195
1355
|
end
|
1196
|
-
end
|
1197
1356
|
|
1357
|
+
structure
|
1358
|
+
end
|
1198
1359
|
|
1360
|
+
def structure_dump_indexes(table_name) #:nodoc:
|
1361
|
+
statements = indexes(table_name).map do |options|
|
1362
|
+
#def add_index(table_name, column_name, options = {})
|
1363
|
+
column_names = options[:columns]
|
1364
|
+
options = {:name => options[:name], :unique => options[:unique]}
|
1365
|
+
index_name = index_name(table_name, :column => column_names)
|
1366
|
+
if Hash === options # legacy support, since this param was a string
|
1367
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
1368
|
+
index_name = options[:name] || index_name
|
1369
|
+
else
|
1370
|
+
index_type = options
|
1371
|
+
end
|
1372
|
+
quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
|
1373
|
+
"CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
|
1374
|
+
end
|
1375
|
+
statements.length > 0 ? "#{statements.join(STATEMENT_TOKEN)}#{STATEMENT_TOKEN}" : ''
|
1376
|
+
end
|
1377
|
+
|
1199
1378
|
def structure_drop #:nodoc:
|
1200
1379
|
s = select_all("select sequence_name from user_sequences order by 1").inject("") do |drop, seq|
|
1201
1380
|
drop << "drop sequence #{seq.to_a.first.last};\n\n"
|
@@ -1206,8 +1385,26 @@ module ActiveRecord
|
|
1206
1385
|
drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
|
1207
1386
|
end
|
1208
1387
|
end
|
1209
|
-
|
1210
|
-
|
1388
|
+
|
1389
|
+
def temp_table_drop #:nodoc:
|
1390
|
+
# changed select from user_tables to all_tables - much faster in large data dictionaries
|
1391
|
+
select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') and temporary = 'Y' order by 1").inject('') do |drop, table|
|
1392
|
+
drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
|
1393
|
+
end
|
1394
|
+
end
|
1395
|
+
|
1396
|
+
def full_drop(preserve_tables=false) #:nodoc:
|
1397
|
+
s = preserve_tables ? [] : [structure_drop]
|
1398
|
+
s << temp_table_drop if preserve_tables
|
1399
|
+
s << drop_sql_for_feature("view")
|
1400
|
+
s << drop_sql_for_feature("synonym")
|
1401
|
+
s << drop_sql_for_feature("type")
|
1402
|
+
s << drop_sql_for_object("package")
|
1403
|
+
s << drop_sql_for_object("function")
|
1404
|
+
s << drop_sql_for_object("procedure")
|
1405
|
+
s.join("\n\n")
|
1406
|
+
end
|
1407
|
+
|
1211
1408
|
def add_column_options!(sql, options) #:nodoc:
|
1212
1409
|
type = options[:type] || ((column = options[:column]) && column.type)
|
1213
1410
|
type = type && type.to_sym
|
@@ -1251,6 +1448,13 @@ module ActiveRecord
|
|
1251
1448
|
sql << order_columns * ", "
|
1252
1449
|
end
|
1253
1450
|
|
1451
|
+
def temporary_table?(table_name) #:nodoc:
|
1452
|
+
select_value("select temporary from user_tables where table_name = '#{table_name.upcase}'") == 'Y'
|
1453
|
+
end
|
1454
|
+
|
1455
|
+
# statements separator used in structure dump
|
1456
|
+
STATEMENT_TOKEN = "\n\n--@@@--\n\n"
|
1457
|
+
|
1254
1458
|
# ORDER BY clause for the passed order option.
|
1255
1459
|
#
|
1256
1460
|
# Uses column aliases as defined by #distinct.
|
@@ -1330,13 +1534,41 @@ module ActiveRecord
|
|
1330
1534
|
string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
|
1331
1535
|
end
|
1332
1536
|
|
1537
|
+
# virtual columns are an 11g feature. This returns [] if feature is not
|
1538
|
+
# present or none are found.
|
1539
|
+
# return [{'column_name' => 'FOOS', 'data_default' => '...'}, ...]
|
1540
|
+
def virtual_columns_for(table)
|
1541
|
+
begin
|
1542
|
+
select_all <<-SQL
|
1543
|
+
select column_name, data_default
|
1544
|
+
from user_tab_cols
|
1545
|
+
where virtual_column='YES'
|
1546
|
+
and table_name='#{table.upcase}'
|
1547
|
+
SQL
|
1548
|
+
# feature not supported previous to 11g
|
1549
|
+
rescue ActiveRecord::StatementInvalid => e
|
1550
|
+
[]
|
1551
|
+
end
|
1552
|
+
end
|
1553
|
+
|
1554
|
+
def drop_sql_for_feature(type)
|
1555
|
+
select_values("select 'DROP #{type.upcase} \"' || #{type}_name || '\";' from user_#{type.tableize}").join("\n\n")
|
1556
|
+
end
|
1557
|
+
|
1558
|
+
def drop_sql_for_object(type)
|
1559
|
+
select_values("select 'DROP #{type.upcase} ' || object_name || ';' from user_objects where object_type = '#{type.upcase}'").join("\n\n")
|
1560
|
+
end
|
1561
|
+
|
1333
1562
|
public
|
1334
1563
|
# DBMS_OUTPUT =============================================
|
1335
1564
|
#
|
1336
1565
|
# PL/SQL in Oracle uses dbms_output for logging print statements
|
1337
1566
|
# These methods stick that output into the Rails log so Ruby and PL/SQL
|
1338
1567
|
# code can can be debugged together in a single application
|
1339
|
-
|
1568
|
+
|
1569
|
+
# Maximum DBMS_OUTPUT buffer size
|
1570
|
+
DBMS_OUTPUT_BUFFER_SIZE = 10000 # can be 1-1000000
|
1571
|
+
|
1340
1572
|
# Turn DBMS_Output logging on
|
1341
1573
|
def enable_dbms_output
|
1342
1574
|
set_dbms_output_plsql_connection
|
@@ -1397,15 +1629,7 @@ if defined?(CGI::Session::ActiveRecordStore::Session)
|
|
1397
1629
|
end
|
1398
1630
|
|
1399
1631
|
# Load custom create, update, delete methods functionality
|
1400
|
-
|
1401
|
-
begin
|
1402
|
-
require 'active_record/connection_adapters/oracle_enhanced_procedures'
|
1403
|
-
rescue LoadError
|
1404
|
-
if defined?(RAILS_DEFAULT_LOGGER)
|
1405
|
-
RAILS_DEFAULT_LOGGER.info "INFO: ActiveRecord oracle_enhanced adapter could not load ruby-plsql gem. "+
|
1406
|
-
"Custom create, update and delete methods will not be available."
|
1407
|
-
end
|
1408
|
-
end
|
1632
|
+
require 'active_record/connection_adapters/oracle_enhanced_procedures'
|
1409
1633
|
|
1410
1634
|
# Load additional methods for composite_primary_keys support
|
1411
1635
|
require 'active_record/connection_adapters/oracle_enhanced_cpk'
|