activerecord-oracle_enhanced-adapter 1.4.3 → 5.2.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.
- checksums.yaml +5 -5
- data/History.md +1162 -2
- data/README.md +567 -155
- data/VERSION +1 -1
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +3 -1
- data/lib/active_record/connection_adapters/oracle_enhanced/column.rb +19 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/connection.rb +132 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +345 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_limits.rb +52 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +280 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/database_tasks.rb +64 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/dbms_output.rb +59 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_connection.rb +538 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/jdbc_quoting.rb +38 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/lob.rb +46 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/oci_connection.rb +435 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/oci_quoting.rb +44 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/procedures.rb +196 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/quoting.rb +164 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_creation.rb +95 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +79 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_dumper.rb +194 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +709 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +28 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/structure_dump.rb +353 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/type_metadata.rb +33 -0
- data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +3 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +385 -1083
- data/lib/active_record/type/oracle_enhanced/boolean.rb +20 -0
- data/lib/active_record/type/oracle_enhanced/integer.rb +15 -0
- data/lib/active_record/type/oracle_enhanced/json.rb +10 -0
- data/lib/active_record/type/oracle_enhanced/national_character_string.rb +26 -0
- data/lib/active_record/type/oracle_enhanced/national_character_text.rb +36 -0
- data/lib/active_record/type/oracle_enhanced/raw.rb +25 -0
- data/lib/active_record/type/oracle_enhanced/string.rb +29 -0
- data/lib/active_record/type/oracle_enhanced/text.rb +32 -0
- data/lib/active_record/type/oracle_enhanced/timestampltz.rb +25 -0
- data/lib/active_record/type/oracle_enhanced/timestamptz.rb +25 -0
- data/lib/activerecord-oracle_enhanced-adapter.rb +5 -13
- data/spec/active_record/connection_adapters/{oracle_enhanced_emulate_oracle_adapter_spec.rb → emulation/oracle_adapter_spec.rb} +5 -4
- data/spec/active_record/connection_adapters/oracle_enhanced/connection_spec.rb +469 -0
- data/spec/active_record/connection_adapters/{oracle_enhanced_context_index_spec.rb → oracle_enhanced/context_index_spec.rb} +140 -128
- data/spec/active_record/connection_adapters/oracle_enhanced/database_tasks_spec.rb +112 -0
- data/spec/active_record/connection_adapters/{oracle_enhanced_dbms_output_spec.rb → oracle_enhanced/dbms_output_spec.rb} +13 -13
- data/spec/active_record/connection_adapters/oracle_enhanced/procedures_spec.rb +365 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/quoting_spec.rb +196 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/schema_dumper_spec.rb +492 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/schema_statements_spec.rb +1433 -0
- data/spec/active_record/connection_adapters/oracle_enhanced/structure_dump_spec.rb +478 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +385 -550
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +92 -1249
- data/spec/active_record/oracle_enhanced/type/binary_spec.rb +119 -0
- data/spec/active_record/oracle_enhanced/type/boolean_spec.rb +208 -0
- data/spec/active_record/oracle_enhanced/type/dirty_spec.rb +139 -0
- data/spec/active_record/oracle_enhanced/type/float_spec.rb +48 -0
- data/spec/active_record/oracle_enhanced/type/integer_spec.rb +91 -0
- data/spec/active_record/oracle_enhanced/type/json_spec.rb +57 -0
- data/spec/active_record/oracle_enhanced/type/national_character_string_spec.rb +55 -0
- data/spec/active_record/oracle_enhanced/type/national_character_text_spec.rb +230 -0
- data/spec/active_record/oracle_enhanced/type/raw_spec.rb +122 -0
- data/spec/active_record/oracle_enhanced/type/text_spec.rb +229 -0
- data/spec/active_record/oracle_enhanced/type/timestamp_spec.rb +75 -0
- data/spec/spec_config.yaml.template +11 -0
- data/spec/spec_helper.rb +100 -93
- data/spec/support/alter_system_set_open_cursors.sql +1 -0
- data/spec/support/alter_system_user_password.sql +2 -0
- data/spec/support/create_oracle_enhanced_users.sql +31 -0
- metadata +105 -152
- data/.rspec +0 -2
- data/Gemfile +0 -52
- data/RUNNING_TESTS.md +0 -45
- data/Rakefile +0 -59
- data/activerecord-oracle_enhanced-adapter.gemspec +0 -130
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +0 -105
- data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +0 -41
- data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +0 -121
- data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +0 -151
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +0 -119
- data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +0 -359
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +0 -25
- data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +0 -21
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +0 -46
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +0 -572
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +0 -497
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +0 -260
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +0 -227
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +0 -260
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +0 -428
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +0 -258
- data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +0 -294
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +0 -17
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +0 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +0 -334
- data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +0 -19
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +0 -113
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +0 -141
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +0 -378
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +0 -440
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +0 -1400
- data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +0 -339
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module ConnectionAdapters
|
|
5
|
+
module OracleEnhanced
|
|
6
|
+
module ColumnMethods
|
|
7
|
+
def primary_key(name, type = :primary_key, **options)
|
|
8
|
+
# This is a placeholder for future :auto_increment support
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
[
|
|
13
|
+
:raw,
|
|
14
|
+
:timestamptz,
|
|
15
|
+
:timestampltz,
|
|
16
|
+
:ntext
|
|
17
|
+
].each do |column_type|
|
|
18
|
+
module_eval <<-CODE, __FILE__, __LINE__ + 1
|
|
19
|
+
def #{column_type}(*args, **options)
|
|
20
|
+
args.each { |name| column(name, :#{column_type}, options) }
|
|
21
|
+
end
|
|
22
|
+
CODE
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class ReferenceDefinition < ActiveRecord::ConnectionAdapters::ReferenceDefinition # :nodoc:
|
|
27
|
+
def initialize(
|
|
28
|
+
name,
|
|
29
|
+
polymorphic: false,
|
|
30
|
+
index: true,
|
|
31
|
+
foreign_key: false,
|
|
32
|
+
type: :integer,
|
|
33
|
+
**options)
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class SynonymDefinition < Struct.new(:name, :table_owner, :table_name, :db_link) #:nodoc:
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition
|
|
42
|
+
attr_accessor :parameters, :statement_parameters, :tablespace
|
|
43
|
+
|
|
44
|
+
def initialize(table, name, unique, columns, orders, type, parameters, statement_parameters, tablespace)
|
|
45
|
+
@parameters = parameters
|
|
46
|
+
@statement_parameters = statement_parameters
|
|
47
|
+
@tablespace = tablespace
|
|
48
|
+
super(table, name, unique, columns, orders: orders, type: type)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
|
53
|
+
include OracleEnhanced::ColumnMethods
|
|
54
|
+
|
|
55
|
+
attr_accessor :tablespace, :organization
|
|
56
|
+
def initialize(name, temporary = false, options = nil, as = nil, tablespace = nil, organization = nil, comment: nil)
|
|
57
|
+
@tablespace = tablespace
|
|
58
|
+
@organization = organization
|
|
59
|
+
super(name, temporary, options, as, comment: comment)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def new_column_definition(name, type, **options) # :nodoc:
|
|
63
|
+
if type == :virtual
|
|
64
|
+
raise "No virtual column definition found." unless options[:as]
|
|
65
|
+
type = options[:type]
|
|
66
|
+
end
|
|
67
|
+
super
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class Table < ActiveRecord::ConnectionAdapters::Table
|
|
75
|
+
include OracleEnhanced::ColumnMethods
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord #:nodoc:
|
|
4
|
+
module ConnectionAdapters #:nodoc:
|
|
5
|
+
module OracleEnhanced #:nodoc:
|
|
6
|
+
class SchemaDumper < ConnectionAdapters::SchemaDumper #:nodoc:
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def tables(stream)
|
|
10
|
+
# do not include materialized views in schema dump - they should be created separately after schema creation
|
|
11
|
+
sorted_tables = (@connection.tables - @connection.materialized_views).sort
|
|
12
|
+
sorted_tables.each do |tbl|
|
|
13
|
+
# add table prefix or suffix for schema_migrations
|
|
14
|
+
next if ignored? tbl
|
|
15
|
+
table(tbl, stream)
|
|
16
|
+
# add primary key trigger if table has it
|
|
17
|
+
primary_key_trigger(tbl, stream)
|
|
18
|
+
end
|
|
19
|
+
# following table definitions
|
|
20
|
+
# add foreign keys if table has them
|
|
21
|
+
sorted_tables.each do |tbl|
|
|
22
|
+
next if ignored? tbl
|
|
23
|
+
foreign_keys(tbl, stream)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# add synonyms in local schema
|
|
27
|
+
synonyms(stream)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def primary_key_trigger(table_name, stream)
|
|
31
|
+
if @connection.has_primary_key_trigger?(table_name)
|
|
32
|
+
pk, _pk_seq = @connection.pk_and_sequence_for(table_name)
|
|
33
|
+
stream.print " add_primary_key_trigger #{table_name.inspect}"
|
|
34
|
+
stream.print ", primary_key: \"#{pk}\"" if pk != "id"
|
|
35
|
+
stream.print "\n\n"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def synonyms(stream)
|
|
40
|
+
syns = @connection.synonyms
|
|
41
|
+
syns.each do |syn|
|
|
42
|
+
next if ignored? syn.name
|
|
43
|
+
table_name = syn.table_name
|
|
44
|
+
table_name = "#{syn.table_owner}.#{table_name}" if syn.table_owner
|
|
45
|
+
table_name = "#{table_name}@#{syn.db_link}" if syn.db_link
|
|
46
|
+
stream.print " add_synonym #{syn.name.inspect}, #{table_name.inspect}, force: true"
|
|
47
|
+
stream.puts
|
|
48
|
+
end
|
|
49
|
+
stream.puts unless syns.empty?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def _indexes(table, stream)
|
|
53
|
+
if (indexes = @connection.indexes(table)).any?
|
|
54
|
+
add_index_statements = indexes.map do |index|
|
|
55
|
+
case index.type
|
|
56
|
+
when nil
|
|
57
|
+
# do nothing here. see indexes_in_create
|
|
58
|
+
statement_parts = []
|
|
59
|
+
when "CTXSYS.CONTEXT"
|
|
60
|
+
if index.statement_parameters
|
|
61
|
+
statement_parts = [ ("add_context_index " + remove_prefix_and_suffix(table).inspect) ]
|
|
62
|
+
statement_parts << index.statement_parameters
|
|
63
|
+
else
|
|
64
|
+
statement_parts = [ ("add_context_index " + remove_prefix_and_suffix(table).inspect) ]
|
|
65
|
+
statement_parts << index.columns.inspect
|
|
66
|
+
statement_parts << ("name: " + index.name.inspect)
|
|
67
|
+
end
|
|
68
|
+
else
|
|
69
|
+
# unrecognized index type
|
|
70
|
+
statement_parts = ["# unrecognized index #{index.name.inspect} with type #{index.type.inspect}"]
|
|
71
|
+
end
|
|
72
|
+
" " + statement_parts.join(", ") unless statement_parts.empty?
|
|
73
|
+
end.compact
|
|
74
|
+
|
|
75
|
+
return if add_index_statements.empty?
|
|
76
|
+
|
|
77
|
+
stream.puts add_index_statements.sort.join("\n")
|
|
78
|
+
stream.puts
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def indexes_in_create(table, stream)
|
|
83
|
+
if (indexes = @connection.indexes(table)).any?
|
|
84
|
+
index_statements = indexes.map do |index|
|
|
85
|
+
" t.index #{index_parts(index).join(', ')}" unless index.type == "CTXSYS.CONTEXT"
|
|
86
|
+
end
|
|
87
|
+
stream.puts index_statements.sort.join("\n")
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def index_parts(index)
|
|
92
|
+
index_parts = super
|
|
93
|
+
index_parts << "tablespace: #{index.tablespace.inspect}" if index.tablespace
|
|
94
|
+
index_parts
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def table(table, stream)
|
|
98
|
+
columns = @connection.columns(table)
|
|
99
|
+
begin
|
|
100
|
+
tbl = StringIO.new
|
|
101
|
+
|
|
102
|
+
# first dump primary key column
|
|
103
|
+
if @connection.respond_to?(:primary_keys)
|
|
104
|
+
pk = @connection.primary_keys(table)
|
|
105
|
+
pk = pk.first unless pk.size > 1
|
|
106
|
+
else
|
|
107
|
+
pk = @connection.primary_key(table)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
|
111
|
+
|
|
112
|
+
# addition to make temporary option work
|
|
113
|
+
tbl.print ", temporary: true" if @connection.temporary_table?(table)
|
|
114
|
+
|
|
115
|
+
case pk
|
|
116
|
+
when String
|
|
117
|
+
tbl.print ", primary_key: #{pk.inspect}" unless pk == "id"
|
|
118
|
+
pkcol = columns.detect { |c| c.name == pk }
|
|
119
|
+
pkcolspec = column_spec_for_primary_key(pkcol)
|
|
120
|
+
if pkcolspec.present?
|
|
121
|
+
tbl.print ", #{format_colspec(pkcolspec)}"
|
|
122
|
+
end
|
|
123
|
+
when Array
|
|
124
|
+
tbl.print ", primary_key: #{pk.inspect}"
|
|
125
|
+
else
|
|
126
|
+
tbl.print ", id: false"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
table_options = @connection.table_options(table)
|
|
130
|
+
if table_options.present?
|
|
131
|
+
tbl.print ", #{format_options(table_options)}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
tbl.puts ", force: :cascade do |t|"
|
|
135
|
+
|
|
136
|
+
# then dump all non-primary key columns
|
|
137
|
+
columns.each do |column|
|
|
138
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
|
|
139
|
+
next if column.name == pk
|
|
140
|
+
type, colspec = column_spec(column)
|
|
141
|
+
tbl.print " t.#{type} #{column.name.inspect}"
|
|
142
|
+
tbl.print ", #{format_colspec(colspec)}" if colspec.present?
|
|
143
|
+
tbl.puts
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
indexes_in_create(table, tbl)
|
|
147
|
+
|
|
148
|
+
tbl.puts " end"
|
|
149
|
+
tbl.puts
|
|
150
|
+
|
|
151
|
+
_indexes(table, tbl)
|
|
152
|
+
|
|
153
|
+
tbl.rewind
|
|
154
|
+
stream.print tbl.read
|
|
155
|
+
rescue => e
|
|
156
|
+
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
|
157
|
+
stream.puts "# #{e.message}"
|
|
158
|
+
stream.puts
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def prepare_column_options(column)
|
|
163
|
+
spec = super
|
|
164
|
+
|
|
165
|
+
if @connection.supports_virtual_columns? && column.virtual?
|
|
166
|
+
spec[:as] = extract_expression_for_virtual_column(column)
|
|
167
|
+
spec = { type: schema_type(column).inspect }.merge!(spec) unless column.type == :decimal
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
spec
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def default_primary_key?(column)
|
|
174
|
+
schema_type(column) == :integer
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def extract_expression_for_virtual_column(column)
|
|
178
|
+
column_name = column.name
|
|
179
|
+
table_name = column.table_name
|
|
180
|
+
@connection.select_value(<<-SQL.strip.gsub(/\s+/, " "), "Table comment", [bind_string("table_name", table_name.upcase), bind_string("column_name", column_name.upcase)]).inspect
|
|
181
|
+
select data_default from all_tab_columns
|
|
182
|
+
where owner = SYS_CONTEXT('userenv', 'session_user')
|
|
183
|
+
and table_name = :table_name
|
|
184
|
+
and column_name = :column_name
|
|
185
|
+
SQL
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def bind_string(name, value)
|
|
189
|
+
ActiveRecord::Relation::QueryAttribute.new(name, value, Type::OracleEnhanced::String.new)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest/sha1"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
module ConnectionAdapters
|
|
7
|
+
module OracleEnhanced
|
|
8
|
+
module SchemaStatements
|
|
9
|
+
# SCHEMA STATEMENTS ========================================
|
|
10
|
+
#
|
|
11
|
+
# see: abstract/schema_statements.rb
|
|
12
|
+
|
|
13
|
+
def tables #:nodoc:
|
|
14
|
+
select_values(<<-SQL.strip.gsub(/\s+/, " "), "tables")
|
|
15
|
+
SELECT DECODE(table_name, UPPER(table_name), LOWER(table_name), table_name)
|
|
16
|
+
FROM all_tables
|
|
17
|
+
WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
|
|
18
|
+
AND secondary = 'N'
|
|
19
|
+
SQL
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def data_sources
|
|
23
|
+
super | synonyms.map(&:name)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def table_exists?(table_name)
|
|
27
|
+
table_name = table_name.to_s
|
|
28
|
+
if table_name.include?("@")
|
|
29
|
+
# db link is not table
|
|
30
|
+
false
|
|
31
|
+
else
|
|
32
|
+
default_owner = current_schema
|
|
33
|
+
end
|
|
34
|
+
real_name = OracleEnhanced::Quoting.valid_table_name?(table_name) ?
|
|
35
|
+
table_name.upcase : table_name
|
|
36
|
+
if real_name.include?(".")
|
|
37
|
+
table_owner, table_name = real_name.split(".")
|
|
38
|
+
else
|
|
39
|
+
table_owner, table_name = default_owner, real_name
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
select_values(<<-SQL.strip.gsub(/\s+/, " "), "table exists", [bind_string("owner", table_owner), bind_string("table_name", table_name)]).any?
|
|
43
|
+
SELECT owner, table_name
|
|
44
|
+
FROM all_tables
|
|
45
|
+
WHERE owner = :owner
|
|
46
|
+
AND table_name = :table_name
|
|
47
|
+
SQL
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def data_source_exists?(table_name)
|
|
51
|
+
(_owner, table_name, _db_link) = @connection.describe(table_name)
|
|
52
|
+
true
|
|
53
|
+
rescue
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def views # :nodoc:
|
|
58
|
+
select_values(<<-SQL.strip.gsub(/\s+/, " "), "views")
|
|
59
|
+
SELECT LOWER(view_name) FROM all_views WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
|
|
60
|
+
SQL
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def materialized_views #:nodoc:
|
|
64
|
+
select_values(<<-SQL.strip.gsub(/\s+/, " "), "materialized views")
|
|
65
|
+
SELECT LOWER(mview_name) FROM all_mviews WHERE owner = SYS_CONTEXT('userenv', 'current_schema')
|
|
66
|
+
SQL
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# get synonyms for schema dump
|
|
70
|
+
def synonyms
|
|
71
|
+
result = select_all(<<-SQL.strip.gsub(/\s+/, " "), "synonyms")
|
|
72
|
+
SELECT synonym_name, table_owner, table_name, db_link
|
|
73
|
+
FROM all_synonyms where owner = SYS_CONTEXT('userenv', 'session_user')
|
|
74
|
+
SQL
|
|
75
|
+
|
|
76
|
+
result.collect do |row|
|
|
77
|
+
OracleEnhanced::SynonymDefinition.new(oracle_downcase(row["synonym_name"]),
|
|
78
|
+
oracle_downcase(row["table_owner"]), oracle_downcase(row["table_name"]), oracle_downcase(row["db_link"]))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def indexes(table_name) #:nodoc:
|
|
83
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
|
84
|
+
default_tablespace_name = default_tablespace
|
|
85
|
+
|
|
86
|
+
result = select_all(<<-SQL.strip.gsub(/\s+/, " "), "indexes", [bind_string("owner", owner), bind_string("owner", owner)])
|
|
87
|
+
SELECT LOWER(i.table_name) AS table_name, LOWER(i.index_name) AS index_name, i.uniqueness,
|
|
88
|
+
i.index_type, i.ityp_owner, i.ityp_name, i.parameters,
|
|
89
|
+
LOWER(i.tablespace_name) AS tablespace_name,
|
|
90
|
+
LOWER(c.column_name) AS column_name, e.column_expression,
|
|
91
|
+
atc.virtual_column
|
|
92
|
+
FROM all_indexes#{db_link} i
|
|
93
|
+
JOIN all_ind_columns#{db_link} c ON c.index_name = i.index_name AND c.index_owner = i.owner
|
|
94
|
+
LEFT OUTER JOIN all_ind_expressions#{db_link} e ON e.index_name = i.index_name AND
|
|
95
|
+
e.index_owner = i.owner AND e.column_position = c.column_position
|
|
96
|
+
LEFT OUTER JOIN all_tab_cols#{db_link} atc ON i.table_name = atc.table_name AND
|
|
97
|
+
c.column_name = atc.column_name AND i.owner = atc.owner AND atc.hidden_column = 'NO'
|
|
98
|
+
WHERE i.owner = :owner
|
|
99
|
+
AND i.table_owner = :owner
|
|
100
|
+
AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc
|
|
101
|
+
WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
|
|
102
|
+
ORDER BY i.index_name, c.column_position
|
|
103
|
+
SQL
|
|
104
|
+
|
|
105
|
+
current_index = nil
|
|
106
|
+
all_schema_indexes = []
|
|
107
|
+
|
|
108
|
+
result.each do |row|
|
|
109
|
+
# have to keep track of indexes because above query returns dups
|
|
110
|
+
# there is probably a better query we could figure out
|
|
111
|
+
if current_index != row["index_name"]
|
|
112
|
+
statement_parameters = nil
|
|
113
|
+
if row["index_type"] == "DOMAIN" && row["ityp_owner"] == "CTXSYS" && row["ityp_name"] == "CONTEXT"
|
|
114
|
+
procedure_name = default_datastore_procedure(row["index_name"])
|
|
115
|
+
source = select_values(<<-SQL.strip.gsub(/\s+/, " "), "procedure", [bind_string("owner", owner), bind_string("procedure_name", procedure_name.upcase)]).join
|
|
116
|
+
SELECT text
|
|
117
|
+
FROM all_source#{db_link}
|
|
118
|
+
WHERE owner = :owner
|
|
119
|
+
AND name = :procedure_name
|
|
120
|
+
ORDER BY line
|
|
121
|
+
SQL
|
|
122
|
+
if source =~ /-- add_context_index_parameters (.+)\n/
|
|
123
|
+
statement_parameters = $1
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
all_schema_indexes << OracleEnhanced::IndexDefinition.new(
|
|
127
|
+
row["table_name"],
|
|
128
|
+
row["index_name"],
|
|
129
|
+
row["uniqueness"] == "UNIQUE",
|
|
130
|
+
[],
|
|
131
|
+
{},
|
|
132
|
+
row["index_type"] == "DOMAIN" ? "#{row['ityp_owner']}.#{row['ityp_name']}" : nil,
|
|
133
|
+
row["parameters"],
|
|
134
|
+
statement_parameters,
|
|
135
|
+
row["tablespace_name"] == default_tablespace_name ? nil : row["tablespace_name"])
|
|
136
|
+
current_index = row["index_name"]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Functional index columns and virtual columns both get stored as column expressions,
|
|
140
|
+
# but re-creating a virtual column index as an expression (instead of using the virtual column's name)
|
|
141
|
+
# results in a ORA-54018 error. Thus, we only want the column expression value returned
|
|
142
|
+
# when the column is not virtual.
|
|
143
|
+
if row["column_expression"] && row["virtual_column"] != "YES"
|
|
144
|
+
all_schema_indexes.last.columns << row["column_expression"]
|
|
145
|
+
else
|
|
146
|
+
all_schema_indexes.last.columns << row["column_name"].downcase
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Return the indexes just for the requested table, since AR is structured that way
|
|
151
|
+
table_name = table_name.downcase
|
|
152
|
+
all_schema_indexes.select { |i| i.table == table_name }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Additional options for +create_table+ method in migration files.
|
|
156
|
+
#
|
|
157
|
+
# You can specify individual starting value in table creation migration file, e.g.:
|
|
158
|
+
#
|
|
159
|
+
# create_table :users, :sequence_start_value => 100 do |t|
|
|
160
|
+
# # ...
|
|
161
|
+
# end
|
|
162
|
+
#
|
|
163
|
+
# You can also specify other sequence definition additional parameters, e.g.:
|
|
164
|
+
#
|
|
165
|
+
# create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t|
|
|
166
|
+
# # ...
|
|
167
|
+
# end
|
|
168
|
+
#
|
|
169
|
+
# Create primary key trigger (so that you can skip primary key value in INSERT statement).
|
|
170
|
+
# By default trigger name will be "table_name_pkt", you can override the name with
|
|
171
|
+
# :trigger_name option (but it is not recommended to override it as then this trigger will
|
|
172
|
+
# not be detected by ActiveRecord model and it will still do prefetching of sequence value).
|
|
173
|
+
# Example:
|
|
174
|
+
#
|
|
175
|
+
# create_table :users, :primary_key_trigger => true do |t|
|
|
176
|
+
# # ...
|
|
177
|
+
# end
|
|
178
|
+
#
|
|
179
|
+
# It is possible to add table and column comments in table creation migration files:
|
|
180
|
+
#
|
|
181
|
+
# create_table :employees, :comment => “Employees and contractors” do |t|
|
|
182
|
+
# t.string :first_name, :comment => “Given name”
|
|
183
|
+
# t.string :last_name, :comment => “Surname”
|
|
184
|
+
# end
|
|
185
|
+
|
|
186
|
+
def create_table(table_name, comment: nil, **options)
|
|
187
|
+
create_sequence = options[:id] != false
|
|
188
|
+
td = create_table_definition table_name, options[:temporary], options[:options], options[:as], options[:tablespace], options[:organization], comment: comment
|
|
189
|
+
|
|
190
|
+
if options[:id] != false && !options[:as]
|
|
191
|
+
pk = options.fetch(:primary_key) do
|
|
192
|
+
Base.get_primary_key table_name.to_s.singularize
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
if pk.is_a?(Array)
|
|
196
|
+
td.primary_keys pk
|
|
197
|
+
else
|
|
198
|
+
td.primary_key pk, options.fetch(:id, :primary_key), options
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# store that primary key was defined in create_table block
|
|
203
|
+
unless create_sequence
|
|
204
|
+
class << td
|
|
205
|
+
attr_accessor :create_sequence
|
|
206
|
+
def primary_key(*args)
|
|
207
|
+
self.create_sequence = true
|
|
208
|
+
super(*args)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
yield td if block_given?
|
|
214
|
+
create_sequence = create_sequence || td.create_sequence
|
|
215
|
+
|
|
216
|
+
if options[:force] && data_source_exists?(table_name)
|
|
217
|
+
drop_table(table_name, options)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
execute schema_creation.accept td
|
|
221
|
+
|
|
222
|
+
create_sequence_and_trigger(table_name, options) if create_sequence
|
|
223
|
+
|
|
224
|
+
if supports_comments? && !supports_comments_in_create?
|
|
225
|
+
change_table_comment(table_name, comment) if comment
|
|
226
|
+
td.columns.each do |column|
|
|
227
|
+
change_column_comment(table_name, column.name, column.comment) if column.comment.present?
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
td.indexes.each { |c, o| add_index table_name, c, o }
|
|
231
|
+
|
|
232
|
+
rebuild_primary_key_index_to_default_tablespace(table_name, options)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def rename_table(table_name, new_name) #:nodoc:
|
|
236
|
+
if new_name.to_s.length > table_name_length
|
|
237
|
+
raise ArgumentError, "New table name '#{new_name}' is too long; the limit is #{table_name_length} characters"
|
|
238
|
+
end
|
|
239
|
+
execute "RENAME #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
|
|
240
|
+
execute "RENAME #{quote_table_name("#{table_name}_seq")} TO #{default_sequence_name(new_name)}" rescue nil
|
|
241
|
+
|
|
242
|
+
rename_table_indexes(table_name, new_name)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def drop_table(table_name, options = {}) #:nodoc:
|
|
246
|
+
execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE CONSTRAINTS' if options[:force] == :cascade}"
|
|
247
|
+
seq_name = options[:sequence_name] || default_sequence_name(table_name)
|
|
248
|
+
execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
|
|
249
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
250
|
+
raise e unless options[:if_exists]
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def insert_versions_sql(versions) # :nodoc:
|
|
254
|
+
sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
|
|
255
|
+
|
|
256
|
+
if supports_multi_insert?
|
|
257
|
+
versions.inject("INSERT ALL\n".dup) { |sql, version|
|
|
258
|
+
sql << "INTO #{sm_table} (version) VALUES (#{quote(version)})\n"
|
|
259
|
+
} << "SELECT * FROM DUAL\n"
|
|
260
|
+
else
|
|
261
|
+
if versions.is_a?(Array)
|
|
262
|
+
# called from ActiveRecord::Base.connection#dump_schema_information
|
|
263
|
+
versions.map { |version|
|
|
264
|
+
"INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
|
|
265
|
+
}.join("\n\n/\n\n")
|
|
266
|
+
else
|
|
267
|
+
# called from ActiveRecord::Base.connection#assume_migrated_upto_version
|
|
268
|
+
"INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)})"
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def add_index(table_name, column_name, options = {}) #:nodoc:
|
|
274
|
+
index_name, index_type, quoted_column_names, tablespace, index_options = add_index_options(table_name, column_name, options)
|
|
275
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{index_options}"
|
|
276
|
+
if index_type == "UNIQUE"
|
|
277
|
+
unless quoted_column_names =~ /\(.*\)/
|
|
278
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(index_name)} #{index_type} (#{quoted_column_names})"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def add_index_options(table_name, column_name, comment: nil, **options) #:nodoc:
|
|
284
|
+
column_names = Array(column_name)
|
|
285
|
+
index_name = index_name(table_name, column: column_names)
|
|
286
|
+
|
|
287
|
+
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :tablespace, :options, :using)
|
|
288
|
+
|
|
289
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
|
290
|
+
index_name = options[:name].to_s if options.key?(:name)
|
|
291
|
+
tablespace = tablespace_for(:index, options[:tablespace])
|
|
292
|
+
max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
|
|
293
|
+
# TODO: This option is used for NOLOGGING, needs better argumetn name
|
|
294
|
+
index_options = options[:options]
|
|
295
|
+
|
|
296
|
+
if index_name.to_s.length > max_index_length
|
|
297
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
|
|
298
|
+
end
|
|
299
|
+
if table_exists?(table_name) && index_name_exists?(table_name, index_name)
|
|
300
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ")
|
|
304
|
+
[index_name, index_type, quoted_column_names, tablespace, index_options]
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Remove the given index from the table.
|
|
308
|
+
# Gives warning if index does not exist
|
|
309
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
|
310
|
+
index_name = index_name_for_remove(table_name, options)
|
|
311
|
+
# TODO: It should execute only when index_type == "UNIQUE"
|
|
312
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(index_name)}" rescue nil
|
|
313
|
+
execute "DROP INDEX #{quote_column_name(index_name)}"
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# returned shortened index name if default is too large
|
|
317
|
+
def index_name(table_name, options) #:nodoc:
|
|
318
|
+
default_name = super(table_name, options).to_s
|
|
319
|
+
# sometimes options can be String or Array with column names
|
|
320
|
+
options = {} unless options.is_a?(Hash)
|
|
321
|
+
identifier_max_length = options[:identifier_max_length] || index_name_length
|
|
322
|
+
return default_name if default_name.length <= identifier_max_length
|
|
323
|
+
|
|
324
|
+
# remove 'index', 'on' and 'and' keywords
|
|
325
|
+
shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
|
|
326
|
+
|
|
327
|
+
# leave just first three letters from each word
|
|
328
|
+
if shortened_name.length > identifier_max_length
|
|
329
|
+
shortened_name = shortened_name.split("_").map { |w| w[0, 3] }.join("_")
|
|
330
|
+
end
|
|
331
|
+
# generate unique name using hash function
|
|
332
|
+
if shortened_name.length > identifier_max_length
|
|
333
|
+
shortened_name = "i" + Digest::SHA1.hexdigest(default_name)[0, identifier_max_length - 1]
|
|
334
|
+
end
|
|
335
|
+
@logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
|
|
336
|
+
shortened_name
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Verify the existence of an index with a given name.
|
|
340
|
+
#
|
|
341
|
+
# The default argument is returned if the underlying implementation does not define the indexes method,
|
|
342
|
+
# as there's no way to determine the correct answer in that case.
|
|
343
|
+
#
|
|
344
|
+
# Will always query database and not index cache.
|
|
345
|
+
def index_name_exists?(table_name, index_name)
|
|
346
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
|
347
|
+
result = select_value(<<-SQL.strip.gsub(/\s+/, " "), "index name exists")
|
|
348
|
+
SELECT 1 FROM all_indexes#{db_link} i
|
|
349
|
+
WHERE i.owner = '#{owner}'
|
|
350
|
+
AND i.table_owner = '#{owner}'
|
|
351
|
+
AND i.table_name = '#{table_name}'
|
|
352
|
+
AND i.index_name = '#{index_name.to_s.upcase}'
|
|
353
|
+
SQL
|
|
354
|
+
result == 1
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def rename_index(table_name, old_name, new_name) #:nodoc:
|
|
358
|
+
validate_index_length!(table_name, new_name)
|
|
359
|
+
execute "ALTER INDEX #{quote_column_name(old_name)} rename to #{quote_column_name(new_name)}"
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Add synonym to existing table or view or sequence. Can be used to create local synonym to
|
|
363
|
+
# remote table in other schema or in other database
|
|
364
|
+
# Examples:
|
|
365
|
+
#
|
|
366
|
+
# add_synonym :posts, "blog.posts"
|
|
367
|
+
# add_synonym :posts_seq, "blog.posts_seq"
|
|
368
|
+
# add_synonym :employees, "hr.employees@dblink", :force => true
|
|
369
|
+
#
|
|
370
|
+
def add_synonym(name, table_name, options = {})
|
|
371
|
+
sql = "CREATE".dup
|
|
372
|
+
if options[:force] == true
|
|
373
|
+
sql << " OR REPLACE"
|
|
374
|
+
end
|
|
375
|
+
sql << " SYNONYM #{quote_table_name(name)} FOR #{quote_table_name(table_name)}"
|
|
376
|
+
execute sql
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Remove existing synonym to table or view or sequence
|
|
380
|
+
# Example:
|
|
381
|
+
#
|
|
382
|
+
# remove_synonym :posts, "blog.posts"
|
|
383
|
+
#
|
|
384
|
+
def remove_synonym(name)
|
|
385
|
+
execute "DROP SYNONYM #{quote_table_name(name)}"
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def add_reference(table_name, *args)
|
|
389
|
+
OracleEnhanced::ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self))
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
|
393
|
+
type = aliased_types(type.to_s, type)
|
|
394
|
+
at = create_alter_table table_name
|
|
395
|
+
at.add_column(column_name, type, options)
|
|
396
|
+
add_column_sql = schema_creation.accept at
|
|
397
|
+
add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name)
|
|
398
|
+
execute add_column_sql
|
|
399
|
+
create_sequence_and_trigger(table_name, options) if type && type.to_sym == :primary_key
|
|
400
|
+
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def aliased_types(name, fallback)
|
|
404
|
+
fallback
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def change_column_default(table_name, column_name, default_or_changes) #:nodoc:
|
|
408
|
+
default = extract_new_default_value(default_or_changes)
|
|
409
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
|
413
|
+
column = column_for(table_name, column_name)
|
|
414
|
+
|
|
415
|
+
unless null || default.nil?
|
|
416
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
change_column table_name, column_name, column.sql_type, null: null
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
|
423
|
+
column = column_for(table_name, column_name)
|
|
424
|
+
|
|
425
|
+
# remove :null option if its value is the same as current column definition
|
|
426
|
+
# otherwise Oracle will raise error
|
|
427
|
+
if options.has_key?(:null) && options[:null] == column.null
|
|
428
|
+
options[:null] = nil
|
|
429
|
+
end
|
|
430
|
+
if type.to_sym == :virtual
|
|
431
|
+
type = options[:type]
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
td = create_table_definition(table_name)
|
|
435
|
+
cd = td.new_column_definition(column.name, type, options)
|
|
436
|
+
change_column_stmt = schema_creation.accept cd
|
|
437
|
+
change_column_stmt << tablespace_for((type_to_sql(type).downcase.to_sym), nil, options[:table_name], options[:column_name]) if type
|
|
438
|
+
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{change_column_stmt}"
|
|
439
|
+
execute(change_column_sql)
|
|
440
|
+
|
|
441
|
+
change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
|
445
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
|
|
446
|
+
rename_column_indexes(table_name, column_name, new_column_name)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
|
|
450
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)} CASCADE CONSTRAINTS"
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
def change_table_comment(table_name, comment)
|
|
454
|
+
clear_cache!
|
|
455
|
+
if comment.nil?
|
|
456
|
+
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS ''"
|
|
457
|
+
else
|
|
458
|
+
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def change_column_comment(table_name, column_name, comment) #:nodoc:
|
|
463
|
+
clear_cache!
|
|
464
|
+
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS '#{comment}'"
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def table_comment(table_name) #:nodoc:
|
|
468
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
|
469
|
+
select_value(<<-SQL.strip.gsub(/\s+/, " "), "Table comment", [bind_string("owner", owner), bind_string("table_name", table_name)])
|
|
470
|
+
SELECT comments FROM all_tab_comments#{db_link}
|
|
471
|
+
WHERE owner = :owner
|
|
472
|
+
AND table_name = :table_name
|
|
473
|
+
SQL
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def table_options(table_name) # :nodoc:
|
|
477
|
+
if comment = table_comment(table_name)
|
|
478
|
+
{ comment: comment }
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def column_comment(table_name, column_name) #:nodoc:
|
|
483
|
+
# TODO: it does not exist in Abstract adapter
|
|
484
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
|
485
|
+
select_value(<<-SQL.strip.gsub(/\s+/, " "), "Column comment", [bind_string("owner", owner), bind_string("table_name", table_name), bind_string("column_name", column_name.upcase)])
|
|
486
|
+
SELECT comments FROM all_col_comments#{db_link}
|
|
487
|
+
WHERE owner = :owner
|
|
488
|
+
AND table_name = :table_name
|
|
489
|
+
AND column_name = :column_name
|
|
490
|
+
SQL
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# Maps logical Rails types to Oracle-specific data types.
|
|
494
|
+
def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) #:nodoc:
|
|
495
|
+
# Ignore options for :text, :ntext and :binary columns
|
|
496
|
+
return super(type) if ["text", "ntext", "binary"].include?(type.to_s)
|
|
497
|
+
|
|
498
|
+
super
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def tablespace(table_name)
|
|
502
|
+
select_value(<<-SQL.strip.gsub(/\s+/, " "), "tablespace")
|
|
503
|
+
SELECT tablespace_name
|
|
504
|
+
FROM all_tables
|
|
505
|
+
WHERE table_name='#{table_name.to_s.upcase}'
|
|
506
|
+
AND owner = SYS_CONTEXT('userenv', 'session_user')
|
|
507
|
+
SQL
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# get table foreign keys for schema dump
|
|
511
|
+
def foreign_keys(table_name) #:nodoc:
|
|
512
|
+
(owner, desc_table_name, db_link) = @connection.describe(table_name)
|
|
513
|
+
|
|
514
|
+
fk_info = select_all(<<-SQL.strip.gsub(/\s+/, " "), "Foreign Keys", [bind_string("owner", owner), bind_string("desc_table_name", desc_table_name)])
|
|
515
|
+
SELECT r.table_name to_table
|
|
516
|
+
,rc.column_name references_column
|
|
517
|
+
,cc.column_name
|
|
518
|
+
,c.constraint_name name
|
|
519
|
+
,c.delete_rule
|
|
520
|
+
FROM all_constraints#{db_link} c, all_cons_columns#{db_link} cc,
|
|
521
|
+
all_constraints#{db_link} r, all_cons_columns#{db_link} rc
|
|
522
|
+
WHERE c.owner = :owner
|
|
523
|
+
AND c.table_name = :desc_table_name
|
|
524
|
+
AND c.constraint_type = 'R'
|
|
525
|
+
AND cc.owner = c.owner
|
|
526
|
+
AND cc.constraint_name = c.constraint_name
|
|
527
|
+
AND r.constraint_name = c.r_constraint_name
|
|
528
|
+
AND r.owner = c.owner
|
|
529
|
+
AND rc.owner = r.owner
|
|
530
|
+
AND rc.constraint_name = r.constraint_name
|
|
531
|
+
AND rc.position = cc.position
|
|
532
|
+
ORDER BY name, to_table, column_name, references_column
|
|
533
|
+
SQL
|
|
534
|
+
|
|
535
|
+
fk_info.map do |row|
|
|
536
|
+
options = {
|
|
537
|
+
column: oracle_downcase(row["column_name"]),
|
|
538
|
+
name: oracle_downcase(row["name"]),
|
|
539
|
+
primary_key: oracle_downcase(row["references_column"])
|
|
540
|
+
}
|
|
541
|
+
options[:on_delete] = extract_foreign_key_action(row["delete_rule"])
|
|
542
|
+
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.new(oracle_downcase(table_name), oracle_downcase(row["to_table"]), options)
|
|
543
|
+
end
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def extract_foreign_key_action(specifier) # :nodoc:
|
|
547
|
+
case specifier
|
|
548
|
+
when "CASCADE"; :cascade
|
|
549
|
+
when "SET NULL"; :nullify
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# REFERENTIAL INTEGRITY ====================================
|
|
554
|
+
|
|
555
|
+
def disable_referential_integrity(&block) #:nodoc:
|
|
556
|
+
old_constraints = select_all(<<-SQL.strip.gsub(/\s+/, " "), "Foreign Keys to disable and enable")
|
|
557
|
+
SELECT constraint_name, owner, table_name
|
|
558
|
+
FROM all_constraints
|
|
559
|
+
WHERE constraint_type = 'R'
|
|
560
|
+
AND status = 'ENABLED'
|
|
561
|
+
AND owner = SYS_CONTEXT('userenv', 'session_user')
|
|
562
|
+
SQL
|
|
563
|
+
begin
|
|
564
|
+
old_constraints.each do |constraint|
|
|
565
|
+
execute "ALTER TABLE #{quote_table_name(constraint["table_name"])} DISABLE CONSTRAINT #{quote_table_name(constraint["constraint_name"])}"
|
|
566
|
+
end
|
|
567
|
+
yield
|
|
568
|
+
ensure
|
|
569
|
+
old_constraints.each do |constraint|
|
|
570
|
+
execute "ALTER TABLE #{quote_table_name(constraint["table_name"])} ENABLE CONSTRAINT #{quote_table_name(constraint["constraint_name"])}"
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def create_alter_table(name)
|
|
576
|
+
OracleEnhanced::AlterTable.new create_table_definition(name, false, {})
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
def update_table_definition(table_name, base)
|
|
580
|
+
OracleEnhanced::Table.new(table_name, base)
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def create_schema_dumper(options)
|
|
584
|
+
OracleEnhanced::SchemaDumper.create(self, options)
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
private
|
|
588
|
+
|
|
589
|
+
def schema_creation
|
|
590
|
+
OracleEnhanced::SchemaCreation.new self
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
def create_table_definition(*args)
|
|
594
|
+
OracleEnhanced::TableDefinition.new(*args)
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def new_column_from_field(table_name, field)
|
|
598
|
+
limit, scale = field["limit"], field["scale"]
|
|
599
|
+
if limit || scale
|
|
600
|
+
field["sql_type"] += "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
if field["sql_type_owner"]
|
|
604
|
+
field["sql_type"] = field["sql_type_owner"] + "." + field["sql_type"]
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
is_virtual = field["virtual_column"] == "YES"
|
|
608
|
+
|
|
609
|
+
# clean up odd default spacing from Oracle
|
|
610
|
+
if field["data_default"] && !is_virtual
|
|
611
|
+
field["data_default"].sub!(/^(.*?)\s*$/, '\1')
|
|
612
|
+
|
|
613
|
+
# If a default contains a newline these cleanup regexes need to
|
|
614
|
+
# match newlines.
|
|
615
|
+
field["data_default"].sub!(/^'(.*)'$/m, '\1')
|
|
616
|
+
field["data_default"] = nil if field["data_default"] =~ /^(null|empty_[bc]lob\(\))$/i
|
|
617
|
+
# TODO: Needs better fix to fallback "N" to false
|
|
618
|
+
field["data_default"] = false if (field["data_default"] == "N" && OracleEnhancedAdapter.emulate_booleans_from_strings)
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
type_metadata = fetch_type_metadata(field["sql_type"], is_virtual)
|
|
622
|
+
default_value = extract_value_from_default(field["data_default"])
|
|
623
|
+
default_value = nil if is_virtual
|
|
624
|
+
OracleEnhanced::Column.new(oracle_downcase(field["name"]),
|
|
625
|
+
default_value,
|
|
626
|
+
type_metadata,
|
|
627
|
+
field["nullable"] == "Y",
|
|
628
|
+
table_name,
|
|
629
|
+
field["column_comment"]
|
|
630
|
+
)
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def fetch_type_metadata(sql_type, virtual = nil)
|
|
634
|
+
OracleEnhanced::TypeMetadata.new(super(sql_type), virtual: virtual)
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def tablespace_for(obj_type, tablespace_option, table_name = nil, column_name = nil)
|
|
638
|
+
tablespace_sql = "".dup
|
|
639
|
+
if tablespace = (tablespace_option || default_tablespace_for(obj_type))
|
|
640
|
+
if [:blob, :clob, :nclob].include?(obj_type.to_sym)
|
|
641
|
+
tablespace_sql << " LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})"
|
|
642
|
+
else
|
|
643
|
+
tablespace_sql << " TABLESPACE #{tablespace}"
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
tablespace_sql
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
def default_tablespace_for(type)
|
|
650
|
+
(default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
def column_for(table_name, column_name)
|
|
654
|
+
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
|
655
|
+
raise "No such column: #{table_name}.#{column_name}"
|
|
656
|
+
end
|
|
657
|
+
column
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def create_sequence_and_trigger(table_name, options)
|
|
661
|
+
seq_name = options[:sequence_name] || default_sequence_name(table_name)
|
|
662
|
+
seq_start_value = options[:sequence_start_value] || default_sequence_start_value
|
|
663
|
+
execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
|
|
664
|
+
|
|
665
|
+
create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
def create_primary_key_trigger(table_name, options)
|
|
669
|
+
seq_name = options[:sequence_name] || default_sequence_name(table_name)
|
|
670
|
+
trigger_name = options[:trigger_name] || default_trigger_name(table_name)
|
|
671
|
+
primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
|
|
672
|
+
execute <<-SQL
|
|
673
|
+
CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
|
|
674
|
+
BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
|
|
675
|
+
BEGIN
|
|
676
|
+
IF inserting THEN
|
|
677
|
+
IF :new.#{quote_column_name(primary_key)} IS NULL THEN
|
|
678
|
+
SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
|
|
679
|
+
END IF;
|
|
680
|
+
END IF;
|
|
681
|
+
END;
|
|
682
|
+
SQL
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
def default_trigger_name(table_name)
|
|
686
|
+
# truncate table name if necessary to fit in max length of identifier
|
|
687
|
+
"#{table_name.to_s[0, table_name_length - 4]}_pkt"
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
def rebuild_primary_key_index_to_default_tablespace(table_name, options)
|
|
691
|
+
tablespace = default_tablespace_for(:index)
|
|
692
|
+
|
|
693
|
+
return unless tablespace
|
|
694
|
+
|
|
695
|
+
index_name = select_value(<<-SQL.strip.gsub(/\s+/, " "), "Index name for primary key", [bind_string("table_name", table_name.upcase)])
|
|
696
|
+
SELECT index_name FROM all_constraints
|
|
697
|
+
WHERE table_name = :table_name
|
|
698
|
+
AND constraint_type = 'P'
|
|
699
|
+
AND owner = SYS_CONTEXT('userenv', 'current_schema')
|
|
700
|
+
SQL
|
|
701
|
+
|
|
702
|
+
return unless index_name
|
|
703
|
+
|
|
704
|
+
execute("ALTER INDEX #{quote_column_name(index_name)} REBUILD TABLESPACE #{tablespace}")
|
|
705
|
+
end
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
end
|
|
709
|
+
end
|