ctreatma-activerecord-oracle_enhanced-adapter 1.4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Gemfile +51 -0
- data/History.md +269 -0
- data/License.txt +20 -0
- data/README.md +378 -0
- data/RUNNING_TESTS.md +45 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/activerecord-oracle_enhanced-adapter.gemspec +130 -0
- data/ctreatma-activerecord-oracle_enhanced-adapter.gemspec +129 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1390 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +106 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +136 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +328 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +553 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +492 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +213 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +252 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +373 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +265 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +290 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
- data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +749 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +310 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +426 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +19 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +113 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +1330 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +69 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +121 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +374 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +380 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1112 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +323 -0
- data/spec/spec_helper.rb +185 -0
- metadata +287 -0
@@ -0,0 +1,252 @@
|
|
1
|
+
module ActiveRecord #:nodoc:
|
2
|
+
module ConnectionAdapters #:nodoc:
|
3
|
+
module OracleEnhancedSchemaDumper #:nodoc:
|
4
|
+
|
5
|
+
def self.included(base) #:nodoc:
|
6
|
+
base.class_eval do
|
7
|
+
private
|
8
|
+
alias_method_chain :tables, :oracle_enhanced
|
9
|
+
alias_method_chain :indexes, :oracle_enhanced
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def ignore_table?(table)
|
16
|
+
[ActiveRecord::Migrator.proper_table_name('schema_migrations'), ignore_tables].flatten.any? do |ignored|
|
17
|
+
case ignored
|
18
|
+
when String; table == ignored
|
19
|
+
when Regexp; table =~ ignored
|
20
|
+
else
|
21
|
+
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def tables_with_oracle_enhanced(stream)
|
27
|
+
return tables_without_oracle_enhanced(stream) unless @connection.respond_to?(:materialized_views)
|
28
|
+
# do not include materialized views in schema dump - they should be created separately after schema creation
|
29
|
+
sorted_tables = (@connection.tables - @connection.materialized_views).sort
|
30
|
+
sorted_tables.each do |tbl|
|
31
|
+
# add table prefix or suffix for schema_migrations
|
32
|
+
next if ignore_table? tbl
|
33
|
+
# change table name inspect method
|
34
|
+
tbl.extend TableInspect
|
35
|
+
oracle_enhanced_table(tbl, stream)
|
36
|
+
# add primary key trigger if table has it
|
37
|
+
primary_key_trigger(tbl, stream)
|
38
|
+
end
|
39
|
+
# following table definitions
|
40
|
+
# add foreign keys if table has them
|
41
|
+
sorted_tables.each do |tbl|
|
42
|
+
next if ignore_table? tbl
|
43
|
+
foreign_keys(tbl, stream)
|
44
|
+
end
|
45
|
+
|
46
|
+
# add synonyms in local schema
|
47
|
+
synonyms(stream)
|
48
|
+
end
|
49
|
+
|
50
|
+
def primary_key_trigger(table_name, stream)
|
51
|
+
if @connection.respond_to?(:has_primary_key_trigger?) && @connection.has_primary_key_trigger?(table_name)
|
52
|
+
pk, pk_seq = @connection.pk_and_sequence_for(table_name)
|
53
|
+
stream.print " add_primary_key_trigger #{table_name.inspect}"
|
54
|
+
stream.print ", :primary_key => \"#{pk}\"" if pk != 'id'
|
55
|
+
stream.print "\n\n"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def foreign_keys(table_name, stream)
|
60
|
+
if @connection.respond_to?(:foreign_keys) && (foreign_keys = @connection.foreign_keys(table_name)).any?
|
61
|
+
add_foreign_key_statements = foreign_keys.map do |foreign_key|
|
62
|
+
statement_parts = [ ('add_foreign_key ' + foreign_key.from_table.inspect) ]
|
63
|
+
statement_parts << foreign_key.to_table.inspect
|
64
|
+
|
65
|
+
if foreign_key.options[:columns].size == 1
|
66
|
+
column = foreign_key.options[:columns].first
|
67
|
+
if column != "#{foreign_key.to_table.singularize}_id"
|
68
|
+
statement_parts << (':column => ' + column.inspect)
|
69
|
+
end
|
70
|
+
|
71
|
+
if foreign_key.options[:references].first != 'id'
|
72
|
+
statement_parts << (':primary_key => ' + foreign_key.options[:primary_key].inspect)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
statement_parts << (':columns => ' + foreign_key.options[:columns].inspect)
|
76
|
+
end
|
77
|
+
|
78
|
+
statement_parts << (':name => ' + foreign_key.options[:name].inspect)
|
79
|
+
|
80
|
+
unless foreign_key.options[:dependent].blank?
|
81
|
+
statement_parts << (':dependent => ' + foreign_key.options[:dependent].inspect)
|
82
|
+
end
|
83
|
+
|
84
|
+
' ' + statement_parts.join(', ')
|
85
|
+
end
|
86
|
+
|
87
|
+
stream.puts add_foreign_key_statements.sort.join("\n")
|
88
|
+
stream.puts
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def synonyms(stream)
|
93
|
+
if @connection.respond_to?(:synonyms)
|
94
|
+
syns = @connection.synonyms
|
95
|
+
syns.each do |syn|
|
96
|
+
next if ignore_table? syn.name
|
97
|
+
table_name = syn.table_name
|
98
|
+
table_name = "#{syn.table_owner}.#{table_name}" if syn.table_owner
|
99
|
+
table_name = "#{table_name}@#{syn.db_link}" if syn.db_link
|
100
|
+
stream.print " add_synonym #{syn.name.inspect}, #{table_name.inspect}, :force => true"
|
101
|
+
stream.puts
|
102
|
+
end
|
103
|
+
stream.puts unless syns.empty?
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def indexes_with_oracle_enhanced(table, stream)
|
108
|
+
# return original method if not using oracle_enhanced
|
109
|
+
if (rails_env = defined?(Rails.env) ? Rails.env : (defined?(RAILS_ENV) ? RAILS_ENV : nil)) &&
|
110
|
+
ActiveRecord::Base.configurations[rails_env] &&
|
111
|
+
ActiveRecord::Base.configurations[rails_env]['adapter'] != 'oracle_enhanced'
|
112
|
+
return indexes_without_oracle_enhanced(table, stream)
|
113
|
+
end
|
114
|
+
if (indexes = @connection.indexes(table)).any?
|
115
|
+
add_index_statements = indexes.map do |index|
|
116
|
+
case index.type
|
117
|
+
when nil
|
118
|
+
# use table.inspect as it will remove prefix and suffix
|
119
|
+
statement_parts = [ ('add_index ' + table.inspect) ]
|
120
|
+
statement_parts << index.columns.inspect
|
121
|
+
statement_parts << (':name => ' + index.name.inspect)
|
122
|
+
statement_parts << ':unique => true' if index.unique
|
123
|
+
statement_parts << ':tablespace => ' + index.tablespace.inspect if index.tablespace
|
124
|
+
when 'CTXSYS.CONTEXT'
|
125
|
+
if index.statement_parameters
|
126
|
+
statement_parts = [ ('add_context_index ' + table.inspect) ]
|
127
|
+
statement_parts << index.statement_parameters
|
128
|
+
else
|
129
|
+
statement_parts = [ ('add_context_index ' + table.inspect) ]
|
130
|
+
statement_parts << index.columns.inspect
|
131
|
+
statement_parts << (':name => ' + index.name.inspect)
|
132
|
+
end
|
133
|
+
else
|
134
|
+
# unrecognized index type
|
135
|
+
statement_parts = ["# unrecognized index #{index.name.inspect} with type #{index.type.inspect}"]
|
136
|
+
end
|
137
|
+
' ' + statement_parts.join(', ')
|
138
|
+
end
|
139
|
+
|
140
|
+
stream.puts add_index_statements.sort.join("\n")
|
141
|
+
stream.puts
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def oracle_enhanced_table(table, stream)
|
146
|
+
columns = @connection.columns(table)
|
147
|
+
begin
|
148
|
+
tbl = StringIO.new
|
149
|
+
|
150
|
+
# first dump primary key column
|
151
|
+
if @connection.respond_to?(:pk_and_sequence_for)
|
152
|
+
pk, pk_seq = @connection.pk_and_sequence_for(table)
|
153
|
+
elsif @connection.respond_to?(:primary_key)
|
154
|
+
pk = @connection.primary_key(table)
|
155
|
+
end
|
156
|
+
|
157
|
+
tbl.print " create_table #{table.inspect}"
|
158
|
+
|
159
|
+
# addition to make temporary option work
|
160
|
+
tbl.print ", :temporary => true" if @connection.temporary_table?(table)
|
161
|
+
|
162
|
+
if columns.detect { |c| c.name == pk }
|
163
|
+
if pk != 'id'
|
164
|
+
tbl.print %Q(, :primary_key => "#{pk}")
|
165
|
+
end
|
166
|
+
else
|
167
|
+
tbl.print ", :id => false"
|
168
|
+
end
|
169
|
+
tbl.print ", :force => true"
|
170
|
+
tbl.puts " do |t|"
|
171
|
+
|
172
|
+
# then dump all non-primary key columns
|
173
|
+
column_specs = columns.map do |column|
|
174
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
|
175
|
+
next if column.name == pk
|
176
|
+
spec = {}
|
177
|
+
spec[:name] = column.name.inspect
|
178
|
+
spec[:type] = column.virtual? ? 'virtual' : column.type.to_s
|
179
|
+
spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
|
180
|
+
spec[:precision] = column.precision.inspect if !column.precision.nil?
|
181
|
+
spec[:scale] = column.scale.inspect if !column.scale.nil?
|
182
|
+
spec[:null] = 'false' if !column.null
|
183
|
+
spec[:default] = column.virtual_column_data_default if column.virtual?
|
184
|
+
spec[:default] ||= default_string(column.default) if column.has_default?
|
185
|
+
(spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
|
186
|
+
spec
|
187
|
+
end.compact
|
188
|
+
|
189
|
+
# find all migration keys used in this table
|
190
|
+
keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map(&:keys).flatten
|
191
|
+
|
192
|
+
# figure out the lengths for each column based on above keys
|
193
|
+
lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
|
194
|
+
|
195
|
+
# the string we're going to sprintf our values against, with standardized column widths
|
196
|
+
format_string = lengths.map{ |len| "%-#{len}s" }
|
197
|
+
|
198
|
+
# find the max length for the 'type' column, which is special
|
199
|
+
type_length = column_specs.map{ |column| column[:type].length }.max
|
200
|
+
|
201
|
+
# add column type definition to our format string
|
202
|
+
format_string.unshift " t.%-#{type_length}s "
|
203
|
+
|
204
|
+
format_string *= ''
|
205
|
+
|
206
|
+
column_specs.each do |colspec|
|
207
|
+
values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
|
208
|
+
values.unshift colspec[:type]
|
209
|
+
tbl.print((format_string % values).gsub(/,\s*$/, ''))
|
210
|
+
tbl.puts
|
211
|
+
end
|
212
|
+
|
213
|
+
tbl.puts " end"
|
214
|
+
tbl.puts
|
215
|
+
|
216
|
+
indexes(table, tbl)
|
217
|
+
|
218
|
+
tbl.rewind
|
219
|
+
stream.print tbl.read
|
220
|
+
rescue => e
|
221
|
+
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
222
|
+
stream.puts "# #{e.message}"
|
223
|
+
stream.puts
|
224
|
+
end
|
225
|
+
|
226
|
+
stream
|
227
|
+
end
|
228
|
+
|
229
|
+
|
230
|
+
# remove table name prefix and suffix when doing #inspect (which is used in tables method)
|
231
|
+
module TableInspect #:nodoc:
|
232
|
+
def inspect
|
233
|
+
remove_prefix_and_suffix(self)
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
def remove_prefix_and_suffix(table_name)
|
238
|
+
if table_name =~ /\A#{ActiveRecord::Base.table_name_prefix.to_s.gsub('$','\$')}(.*)#{ActiveRecord::Base.table_name_suffix.to_s.gsub('$','\$')}\Z/
|
239
|
+
"\"#{$1}\""
|
240
|
+
else
|
241
|
+
"\"#{table_name}\""
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
ActiveRecord::SchemaDumper.class_eval do
|
251
|
+
include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaDumper
|
252
|
+
end
|
@@ -0,0 +1,373 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
module OracleEnhancedSchemaStatements
|
6
|
+
# SCHEMA STATEMENTS ========================================
|
7
|
+
#
|
8
|
+
# see: abstract/schema_statements.rb
|
9
|
+
|
10
|
+
# Additional options for +create_table+ method in migration files.
|
11
|
+
#
|
12
|
+
# You can specify individual starting value in table creation migration file, e.g.:
|
13
|
+
#
|
14
|
+
# create_table :users, :sequence_start_value => 100 do |t|
|
15
|
+
# # ...
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# You can also specify other sequence definition additional parameters, e.g.:
|
19
|
+
#
|
20
|
+
# create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t|
|
21
|
+
# # ...
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Create primary key trigger (so that you can skip primary key value in INSERT statement).
|
25
|
+
# By default trigger name will be "table_name_pkt", you can override the name with
|
26
|
+
# :trigger_name option (but it is not recommended to override it as then this trigger will
|
27
|
+
# not be detected by ActiveRecord model and it will still do prefetching of sequence value).
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# create_table :users, :primary_key_trigger => true do |t|
|
31
|
+
# # ...
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# It is possible to add table and column comments in table creation migration files:
|
35
|
+
#
|
36
|
+
# create_table :employees, :comment => “Employees and contractors” do |t|
|
37
|
+
# t.string :first_name, :comment => “Given name”
|
38
|
+
# t.string :last_name, :comment => “Surname”
|
39
|
+
# end
|
40
|
+
|
41
|
+
def create_table(name, options = {}, &block)
|
42
|
+
create_sequence = options[:id] != false
|
43
|
+
column_comments = {}
|
44
|
+
|
45
|
+
table_definition = TableDefinition.new(self)
|
46
|
+
table_definition.primary_key(options[:primary_key] || Base.get_primary_key(name.to_s.singularize)) unless options[:id] == false
|
47
|
+
|
48
|
+
# store that primary key was defined in create_table block
|
49
|
+
unless create_sequence
|
50
|
+
class << table_definition
|
51
|
+
attr_accessor :create_sequence
|
52
|
+
def primary_key(*args)
|
53
|
+
self.create_sequence = true
|
54
|
+
super(*args)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# store column comments
|
60
|
+
class << table_definition
|
61
|
+
attr_accessor :column_comments
|
62
|
+
def column(name, type, options = {})
|
63
|
+
if options[:comment]
|
64
|
+
self.column_comments ||= {}
|
65
|
+
self.column_comments[name] = options[:comment]
|
66
|
+
end
|
67
|
+
super(name, type, options)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
result = block.call(table_definition) if block
|
72
|
+
create_sequence = create_sequence || table_definition.create_sequence
|
73
|
+
column_comments = table_definition.column_comments if table_definition.column_comments
|
74
|
+
tablespace = tablespace_for(:table, options[:tablespace])
|
75
|
+
|
76
|
+
if options[:force] && table_exists?(name)
|
77
|
+
drop_table(name, options)
|
78
|
+
end
|
79
|
+
|
80
|
+
create_sql = "CREATE#{' GLOBAL TEMPORARY' if options[:temporary]} TABLE "
|
81
|
+
create_sql << quote_table_name(name)
|
82
|
+
create_sql << " (#{table_definition.to_sql})"
|
83
|
+
unless options[:temporary]
|
84
|
+
create_sql << " ORGANIZATION #{options[:organization]}" if options[:organization]
|
85
|
+
create_sql << tablespace
|
86
|
+
table_definition.lob_columns.each{|cd| create_sql << tablespace_for(cd.sql_type.downcase.to_sym, nil, name, cd.name)}
|
87
|
+
end
|
88
|
+
create_sql << " #{options[:options]}"
|
89
|
+
execute create_sql
|
90
|
+
|
91
|
+
create_sequence_and_trigger(name, options) if create_sequence
|
92
|
+
|
93
|
+
add_table_comment name, options[:comment]
|
94
|
+
column_comments.each do |column_name, comment|
|
95
|
+
add_comment name, column_name, comment
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
def rename_table(name, new_name) #:nodoc:
|
101
|
+
if new_name.to_s.length > table_name_length
|
102
|
+
raise ArgumentError, "New table name '#{new_name}' is too long; the limit is #{table_name_length} characters"
|
103
|
+
end
|
104
|
+
if "#{new_name}_seq".to_s.length > sequence_name_length
|
105
|
+
raise ArgumentError, "New sequence name '#{new_name}_seq' is too long; the limit is #{sequence_name_length} characters"
|
106
|
+
end
|
107
|
+
execute "RENAME #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
|
108
|
+
execute "RENAME #{quote_table_name("#{name}_seq")} TO #{quote_table_name("#{new_name}_seq")}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def drop_table(name, options = {}) #:nodoc:
|
112
|
+
super(name)
|
113
|
+
seq_name = options[:sequence_name] || default_sequence_name(name)
|
114
|
+
execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
|
115
|
+
ensure
|
116
|
+
clear_table_columns_cache(name)
|
117
|
+
end
|
118
|
+
|
119
|
+
# clear cached indexes when adding new index
|
120
|
+
def add_index(table_name, column_name, options = {}) #:nodoc:
|
121
|
+
column_names = Array(column_name)
|
122
|
+
index_name = index_name(table_name, :column => column_names)
|
123
|
+
|
124
|
+
if Hash === options # legacy support, since this param was a string
|
125
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
126
|
+
index_name = options[:name].to_s if options.key?(:name)
|
127
|
+
tablespace = tablespace_for(:index, options[:tablespace])
|
128
|
+
else
|
129
|
+
index_type = options
|
130
|
+
end
|
131
|
+
|
132
|
+
if index_name.to_s.length > index_name_length
|
133
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
|
134
|
+
end
|
135
|
+
if index_name_exists?(table_name, index_name, false)
|
136
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
|
137
|
+
end
|
138
|
+
quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ")
|
139
|
+
|
140
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{options[:options]}"
|
141
|
+
ensure
|
142
|
+
self.all_schema_indexes = nil
|
143
|
+
end
|
144
|
+
|
145
|
+
# Remove the given index from the table.
|
146
|
+
# Gives warning if index does not exist
|
147
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
148
|
+
index_name = index_name(table_name, options)
|
149
|
+
unless index_name_exists?(table_name, index_name, true)
|
150
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
|
151
|
+
end
|
152
|
+
remove_index!(table_name, index_name)
|
153
|
+
end
|
154
|
+
|
155
|
+
# clear cached indexes when removing index
|
156
|
+
def remove_index!(table_name, index_name) #:nodoc:
|
157
|
+
execute "DROP INDEX #{quote_column_name(index_name)}"
|
158
|
+
ensure
|
159
|
+
self.all_schema_indexes = nil
|
160
|
+
end
|
161
|
+
|
162
|
+
# returned shortened index name if default is too large
|
163
|
+
def index_name(table_name, options) #:nodoc:
|
164
|
+
default_name = super(table_name, options).to_s
|
165
|
+
# sometimes options can be String or Array with column names
|
166
|
+
options = {} unless options.is_a?(Hash)
|
167
|
+
identifier_max_length = options[:identifier_max_length] || index_name_length
|
168
|
+
return default_name if default_name.length <= identifier_max_length
|
169
|
+
|
170
|
+
# remove 'index', 'on' and 'and' keywords
|
171
|
+
shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
|
172
|
+
|
173
|
+
# leave just first three letters from each word
|
174
|
+
if shortened_name.length > identifier_max_length
|
175
|
+
shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
|
176
|
+
end
|
177
|
+
# generate unique name using hash function
|
178
|
+
if shortened_name.length > identifier_max_length
|
179
|
+
shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1]
|
180
|
+
end
|
181
|
+
@logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
|
182
|
+
shortened_name
|
183
|
+
end
|
184
|
+
|
185
|
+
# Verify the existence of an index with a given name.
|
186
|
+
#
|
187
|
+
# The default argument is returned if the underlying implementation does not define the indexes method,
|
188
|
+
# as there's no way to determine the correct answer in that case.
|
189
|
+
#
|
190
|
+
# Will always query database and not index cache.
|
191
|
+
def index_name_exists?(table_name, index_name, default)
|
192
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
193
|
+
result = select_value(<<-SQL)
|
194
|
+
SELECT 1 FROM all_indexes#{db_link} i
|
195
|
+
WHERE i.owner = '#{owner}'
|
196
|
+
AND i.table_owner = '#{owner}'
|
197
|
+
AND i.table_name = '#{table_name}'
|
198
|
+
AND i.index_name = '#{index_name.to_s.upcase}'
|
199
|
+
SQL
|
200
|
+
result == 1
|
201
|
+
end
|
202
|
+
|
203
|
+
def rename_index(table_name, index_name, new_index_name) #:nodoc:
|
204
|
+
unless index_name_exists?(table_name, index_name, true)
|
205
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
|
206
|
+
end
|
207
|
+
execute "ALTER INDEX #{quote_column_name(index_name)} rename to #{quote_column_name(new_index_name)}"
|
208
|
+
ensure
|
209
|
+
self.all_schema_indexes = nil
|
210
|
+
end
|
211
|
+
|
212
|
+
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
213
|
+
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
214
|
+
add_column_options!(add_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
|
215
|
+
add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name)
|
216
|
+
execute(add_column_sql)
|
217
|
+
ensure
|
218
|
+
clear_table_columns_cache(table_name)
|
219
|
+
end
|
220
|
+
|
221
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
222
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
|
223
|
+
ensure
|
224
|
+
clear_table_columns_cache(table_name)
|
225
|
+
end
|
226
|
+
|
227
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
228
|
+
column = column_for(table_name, column_name)
|
229
|
+
|
230
|
+
unless null || default.nil?
|
231
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
232
|
+
end
|
233
|
+
|
234
|
+
change_column table_name, column_name, column.sql_type, :null => null
|
235
|
+
end
|
236
|
+
|
237
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
238
|
+
column = column_for(table_name, column_name)
|
239
|
+
|
240
|
+
# remove :null option if its value is the same as current column definition
|
241
|
+
# otherwise Oracle will raise error
|
242
|
+
if options.has_key?(:null) && options[:null] == column.null
|
243
|
+
options[:null] = nil
|
244
|
+
end
|
245
|
+
|
246
|
+
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
247
|
+
add_column_options!(change_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
|
248
|
+
change_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, options[:table_name], options[:column_name])
|
249
|
+
execute(change_column_sql)
|
250
|
+
ensure
|
251
|
+
clear_table_columns_cache(table_name)
|
252
|
+
end
|
253
|
+
|
254
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
255
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
|
256
|
+
ensure
|
257
|
+
clear_table_columns_cache(table_name)
|
258
|
+
end
|
259
|
+
|
260
|
+
def remove_column(table_name, column_name) #:nodoc:
|
261
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
262
|
+
ensure
|
263
|
+
clear_table_columns_cache(table_name)
|
264
|
+
end
|
265
|
+
|
266
|
+
def add_comment(table_name, column_name, comment) #:nodoc:
|
267
|
+
return if comment.blank?
|
268
|
+
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
|
269
|
+
end
|
270
|
+
|
271
|
+
def add_table_comment(table_name, comment) #:nodoc:
|
272
|
+
return if comment.blank?
|
273
|
+
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
|
274
|
+
end
|
275
|
+
|
276
|
+
def table_comment(table_name) #:nodoc:
|
277
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
278
|
+
select_value <<-SQL
|
279
|
+
SELECT comments FROM all_tab_comments#{db_link}
|
280
|
+
WHERE owner = '#{owner}'
|
281
|
+
AND table_name = '#{table_name}'
|
282
|
+
SQL
|
283
|
+
end
|
284
|
+
|
285
|
+
def column_comment(table_name, column_name) #:nodoc:
|
286
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
287
|
+
select_value <<-SQL
|
288
|
+
SELECT comments FROM all_col_comments#{db_link}
|
289
|
+
WHERE owner = '#{owner}'
|
290
|
+
AND table_name = '#{table_name}'
|
291
|
+
AND column_name = '#{column_name.upcase}'
|
292
|
+
SQL
|
293
|
+
end
|
294
|
+
|
295
|
+
# Maps logical Rails types to Oracle-specific data types.
|
296
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
297
|
+
# Ignore options for :text and :binary columns
|
298
|
+
return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s)
|
299
|
+
|
300
|
+
super
|
301
|
+
end
|
302
|
+
|
303
|
+
def tablespace(table_name)
|
304
|
+
select_value <<-SQL
|
305
|
+
SELECT tablespace_name
|
306
|
+
FROM user_tables
|
307
|
+
WHERE table_name='#{table_name.to_s.upcase}'
|
308
|
+
SQL
|
309
|
+
end
|
310
|
+
|
311
|
+
private
|
312
|
+
|
313
|
+
def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil)
|
314
|
+
tablespace_sql = ''
|
315
|
+
if tablespace = (tablespace_option || default_tablespace_for(obj_type))
|
316
|
+
tablespace_sql << if [:blob, :clob].include?(obj_type.to_sym)
|
317
|
+
" LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})"
|
318
|
+
else
|
319
|
+
" TABLESPACE #{tablespace}"
|
320
|
+
end
|
321
|
+
end
|
322
|
+
tablespace_sql
|
323
|
+
end
|
324
|
+
|
325
|
+
def default_tablespace_for(type)
|
326
|
+
(default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
def column_for(table_name, column_name)
|
331
|
+
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
332
|
+
raise "No such column: #{table_name}.#{column_name}"
|
333
|
+
end
|
334
|
+
column
|
335
|
+
end
|
336
|
+
|
337
|
+
def create_sequence_and_trigger(table_name, options)
|
338
|
+
seq_name = options[:sequence_name] || default_sequence_name(table_name)
|
339
|
+
seq_start_value = options[:sequence_start_value] || default_sequence_start_value
|
340
|
+
execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
|
341
|
+
|
342
|
+
create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
|
343
|
+
end
|
344
|
+
|
345
|
+
def create_primary_key_trigger(table_name, options)
|
346
|
+
seq_name = options[:sequence_name] || default_sequence_name(table_name)
|
347
|
+
trigger_name = options[:trigger_name] || default_trigger_name(table_name)
|
348
|
+
primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
|
349
|
+
execute compress_lines(<<-SQL)
|
350
|
+
CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
|
351
|
+
BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
|
352
|
+
BEGIN
|
353
|
+
IF inserting THEN
|
354
|
+
IF :new.#{quote_column_name(primary_key)} IS NULL THEN
|
355
|
+
SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
|
356
|
+
END IF;
|
357
|
+
END IF;
|
358
|
+
END;
|
359
|
+
SQL
|
360
|
+
end
|
361
|
+
|
362
|
+
def default_trigger_name(table_name)
|
363
|
+
# truncate table name if necessary to fit in max length of identifier
|
364
|
+
"#{table_name.to_s[0,table_name_length-4]}_pkt"
|
365
|
+
end
|
366
|
+
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
|
372
|
+
include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatements
|
373
|
+
end
|