activerecord-oracle_enhanced-adapter-with-schema 0.0.1
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/.rspec +2 -0
- data/Gemfile +52 -0
- data/History.md +301 -0
- data/License.txt +20 -0
- data/README.md +123 -0
- data/RUNNING_TESTS.md +45 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/activerecord-oracle_enhanced-adapter-with-schema.gemspec +130 -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 +1399 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +121 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +146 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -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 +46 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +494 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +227 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +260 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +428 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +258 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -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-with-schema.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +778 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +332 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +427 -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 +1388 -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 +141 -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 +378 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +440 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1385 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
- data/spec/spec_helper.rb +189 -0
- metadata +260 -0
@@ -0,0 +1,260 @@
|
|
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
|
+
['schema_migrations', ignore_tables].flatten.any? do |ignored|
|
17
|
+
case ignored
|
18
|
+
when String; remove_prefix_and_suffix(table) == ignored
|
19
|
+
when Regexp; remove_prefix_and_suffix(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[:type] = column.virtual? ? 'virtual' : column.type.to_s
|
180
|
+
spec[:virtual_type] = column.type.inspect if column.virtual? && column.sql_type != 'NUMBER'
|
181
|
+
spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
|
182
|
+
spec[:precision] = column.precision.inspect if !column.precision.nil?
|
183
|
+
spec[:scale] = column.scale.inspect if !column.scale.nil?
|
184
|
+
spec[:null] = 'false' if !column.null
|
185
|
+
spec[:as] = column.virtual_column_data_default if column.virtual?
|
186
|
+
spec[:default] = default_string(column.default) if column.has_default? && !column.virtual?
|
187
|
+
(spec.keys - [:name, :type]).each do |k|
|
188
|
+
key_s = (k == :virtual_type ? ":type => " : "#{k.inspect} => ")
|
189
|
+
spec[k] = key_s + spec[k]
|
190
|
+
end
|
191
|
+
spec
|
192
|
+
end.compact
|
193
|
+
|
194
|
+
# find all migration keys used in this table
|
195
|
+
keys = [:name, :limit, :precision, :scale, :default, :null, :as, :virtual_type] & column_specs.map(&:keys).flatten
|
196
|
+
|
197
|
+
# figure out the lengths for each column based on above keys
|
198
|
+
lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
|
199
|
+
|
200
|
+
# the string we're going to sprintf our values against, with standardized column widths
|
201
|
+
format_string = lengths.map{ |len| "%-#{len}s" }
|
202
|
+
|
203
|
+
# find the max length for the 'type' column, which is special
|
204
|
+
type_length = column_specs.map{ |column| column[:type].length }.max
|
205
|
+
|
206
|
+
# add column type definition to our format string
|
207
|
+
format_string.unshift " t.%-#{type_length}s "
|
208
|
+
|
209
|
+
format_string *= ''
|
210
|
+
|
211
|
+
column_specs.each do |colspec|
|
212
|
+
values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
|
213
|
+
values.unshift colspec[:type]
|
214
|
+
tbl.print((format_string % values).gsub(/,\s*$/, ''))
|
215
|
+
tbl.puts
|
216
|
+
end
|
217
|
+
|
218
|
+
tbl.puts " end"
|
219
|
+
tbl.puts
|
220
|
+
|
221
|
+
indexes(table, tbl)
|
222
|
+
|
223
|
+
tbl.rewind
|
224
|
+
stream.print tbl.read
|
225
|
+
rescue => e
|
226
|
+
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
227
|
+
stream.puts "# #{e.message}"
|
228
|
+
stream.puts
|
229
|
+
end
|
230
|
+
|
231
|
+
stream
|
232
|
+
end
|
233
|
+
|
234
|
+
def remove_prefix_and_suffix(table)
|
235
|
+
table.gsub(/^(#{ActiveRecord::Base.table_name_prefix})(.+)(#{ActiveRecord::Base.table_name_suffix})$/, "\\2")
|
236
|
+
end
|
237
|
+
|
238
|
+
# remove table name prefix and suffix when doing #inspect (which is used in tables method)
|
239
|
+
module TableInspect #:nodoc:
|
240
|
+
def inspect
|
241
|
+
remove_prefix_and_suffix(self)
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
def remove_prefix_and_suffix(table_name)
|
246
|
+
if table_name =~ /\A#{ActiveRecord::Base.table_name_prefix.to_s.gsub('$','\$')}(.*)#{ActiveRecord::Base.table_name_suffix.to_s.gsub('$','\$')}\Z/
|
247
|
+
"\"#{$1}\""
|
248
|
+
else
|
249
|
+
"\"#{table_name}\""
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
ActiveRecord::SchemaDumper.class_eval do
|
259
|
+
include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaDumper
|
260
|
+
end
|
@@ -0,0 +1,428 @@
|
|
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
|
+
def initialize_schema_migrations_table
|
120
|
+
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
|
121
|
+
|
122
|
+
unless table_exists?(sm_table)
|
123
|
+
index_name = "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
|
124
|
+
if index_name.length > index_name_length
|
125
|
+
truncate_to = index_name_length - index_name.to_s.length - 1
|
126
|
+
truncated_name = "unique_schema_migrations"[0..truncate_to]
|
127
|
+
index_name = "#{Base.table_name_prefix}#{truncated_name}#{Base.table_name_suffix}"
|
128
|
+
end
|
129
|
+
|
130
|
+
create_table(sm_table, :id => false) do |schema_migrations_table|
|
131
|
+
schema_migrations_table.column :version, :string, :null => false
|
132
|
+
end
|
133
|
+
add_index sm_table, :version, :unique => true, :name => index_name
|
134
|
+
|
135
|
+
# Backwards-compatibility: if we find schema_info, assume we've
|
136
|
+
# migrated up to that point:
|
137
|
+
si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
|
138
|
+
if table_exists?(si_table)
|
139
|
+
ActiveSupport::Deprecation.warn "Usage of the schema table `#{si_table}` is deprecated. Please switch to using `schema_migrations` table"
|
140
|
+
|
141
|
+
old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
|
142
|
+
assume_migrated_upto_version(old_version)
|
143
|
+
drop_table(si_table)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# clear cached indexes when adding new index
|
149
|
+
def add_index(table_name, column_name, options = {}) #:nodoc:
|
150
|
+
column_names = Array(column_name)
|
151
|
+
index_name = index_name(table_name, :column => column_names)
|
152
|
+
|
153
|
+
if Hash === options # legacy support, since this param was a string
|
154
|
+
index_type = options[:unique] ? "UNIQUE" : ""
|
155
|
+
index_name = options[:name].to_s if options.key?(:name)
|
156
|
+
tablespace = tablespace_for(:index, options[:tablespace])
|
157
|
+
else
|
158
|
+
index_type = options
|
159
|
+
end
|
160
|
+
|
161
|
+
if index_name.to_s.length > index_name_length
|
162
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
|
163
|
+
end
|
164
|
+
if index_name_exists?(table_name, index_name, false)
|
165
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
|
166
|
+
end
|
167
|
+
quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ")
|
168
|
+
|
169
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{options[:options]}"
|
170
|
+
ensure
|
171
|
+
self.all_schema_indexes = nil
|
172
|
+
end
|
173
|
+
|
174
|
+
# Remove the given index from the table.
|
175
|
+
# Gives warning if index does not exist
|
176
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
177
|
+
index_name = index_name(table_name, options)
|
178
|
+
unless index_name_exists?(table_name, index_name, true)
|
179
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
|
180
|
+
end
|
181
|
+
remove_index!(table_name, index_name)
|
182
|
+
end
|
183
|
+
|
184
|
+
# clear cached indexes when removing index
|
185
|
+
def remove_index!(table_name, index_name) #:nodoc:
|
186
|
+
execute "DROP INDEX #{quote_column_name(index_name)}"
|
187
|
+
ensure
|
188
|
+
self.all_schema_indexes = nil
|
189
|
+
end
|
190
|
+
|
191
|
+
# returned shortened index name if default is too large
|
192
|
+
def index_name(table_name, options) #:nodoc:
|
193
|
+
default_name = super(table_name, options).to_s
|
194
|
+
# sometimes options can be String or Array with column names
|
195
|
+
options = {} unless options.is_a?(Hash)
|
196
|
+
identifier_max_length = options[:identifier_max_length] || index_name_length
|
197
|
+
return default_name if default_name.length <= identifier_max_length
|
198
|
+
|
199
|
+
# remove 'index', 'on' and 'and' keywords
|
200
|
+
shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
|
201
|
+
|
202
|
+
# leave just first three letters from each word
|
203
|
+
if shortened_name.length > identifier_max_length
|
204
|
+
shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
|
205
|
+
end
|
206
|
+
# generate unique name using hash function
|
207
|
+
if shortened_name.length > identifier_max_length
|
208
|
+
shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1]
|
209
|
+
end
|
210
|
+
@logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
|
211
|
+
shortened_name
|
212
|
+
end
|
213
|
+
|
214
|
+
# Verify the existence of an index with a given name.
|
215
|
+
#
|
216
|
+
# The default argument is returned if the underlying implementation does not define the indexes method,
|
217
|
+
# as there's no way to determine the correct answer in that case.
|
218
|
+
#
|
219
|
+
# Will always query database and not index cache.
|
220
|
+
def index_name_exists?(table_name, index_name, default)
|
221
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
222
|
+
result = select_value(<<-SQL)
|
223
|
+
SELECT 1 FROM all_indexes#{db_link} i
|
224
|
+
WHERE i.owner = '#{owner}'
|
225
|
+
AND i.table_owner = '#{owner}'
|
226
|
+
AND i.table_name = '#{table_name}'
|
227
|
+
AND i.index_name = '#{index_name.to_s.upcase}'
|
228
|
+
SQL
|
229
|
+
result == 1
|
230
|
+
end
|
231
|
+
|
232
|
+
def rename_index(table_name, index_name, new_index_name) #:nodoc:
|
233
|
+
unless index_name_exists?(table_name, index_name, true)
|
234
|
+
raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
|
235
|
+
end
|
236
|
+
execute "ALTER INDEX #{quote_column_name(index_name)} rename to #{quote_column_name(new_index_name)}"
|
237
|
+
ensure
|
238
|
+
self.all_schema_indexes = nil
|
239
|
+
end
|
240
|
+
|
241
|
+
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
242
|
+
if type.to_sym == :virtual
|
243
|
+
type = options[:type]
|
244
|
+
end
|
245
|
+
add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} "
|
246
|
+
add_column_sql << type_to_sql(type, options[:limit], options[:precision], options[:scale]) if type
|
247
|
+
|
248
|
+
add_column_options!(add_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
|
249
|
+
|
250
|
+
add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name) if type
|
251
|
+
|
252
|
+
execute(add_column_sql)
|
253
|
+
ensure
|
254
|
+
clear_table_columns_cache(table_name)
|
255
|
+
end
|
256
|
+
|
257
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
258
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
|
259
|
+
ensure
|
260
|
+
clear_table_columns_cache(table_name)
|
261
|
+
end
|
262
|
+
|
263
|
+
def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
|
264
|
+
column = column_for(table_name, column_name)
|
265
|
+
|
266
|
+
unless null || default.nil?
|
267
|
+
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
268
|
+
end
|
269
|
+
|
270
|
+
change_column table_name, column_name, column.sql_type, :null => null
|
271
|
+
end
|
272
|
+
|
273
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
274
|
+
column = column_for(table_name, column_name)
|
275
|
+
|
276
|
+
# remove :null option if its value is the same as current column definition
|
277
|
+
# otherwise Oracle will raise error
|
278
|
+
if options.has_key?(:null) && options[:null] == column.null
|
279
|
+
options[:null] = nil
|
280
|
+
end
|
281
|
+
if type.to_sym == :virtual
|
282
|
+
type = options[:type]
|
283
|
+
end
|
284
|
+
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} "
|
285
|
+
change_column_sql << "#{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" if type
|
286
|
+
|
287
|
+
add_column_options!(change_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
|
288
|
+
|
289
|
+
change_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, options[:table_name], options[:column_name]) if type
|
290
|
+
|
291
|
+
execute(change_column_sql)
|
292
|
+
ensure
|
293
|
+
clear_table_columns_cache(table_name)
|
294
|
+
end
|
295
|
+
|
296
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
297
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
|
298
|
+
ensure
|
299
|
+
clear_table_columns_cache(table_name)
|
300
|
+
end
|
301
|
+
|
302
|
+
def remove_column(table_name, *column_names) #:nodoc:
|
303
|
+
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
|
304
|
+
|
305
|
+
major, minor = ActiveRecord::VERSION::MAJOR, ActiveRecord::VERSION::MINOR
|
306
|
+
is_deprecated = (major == 3 and minor >= 2) or major > 3
|
307
|
+
|
308
|
+
if column_names.flatten! and is_deprecated
|
309
|
+
message = 'Passing array to remove_columns is deprecated, please use ' +
|
310
|
+
'multiple arguments, like: `remove_columns(:posts, :foo, :bar)`'
|
311
|
+
ActiveSupport::Deprecation.warn message, caller
|
312
|
+
end
|
313
|
+
|
314
|
+
column_names.each do |column_name|
|
315
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
|
316
|
+
end
|
317
|
+
ensure
|
318
|
+
clear_table_columns_cache(table_name)
|
319
|
+
end
|
320
|
+
|
321
|
+
def add_comment(table_name, column_name, comment) #:nodoc:
|
322
|
+
return if comment.blank?
|
323
|
+
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
|
324
|
+
end
|
325
|
+
|
326
|
+
def add_table_comment(table_name, comment) #:nodoc:
|
327
|
+
return if comment.blank?
|
328
|
+
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
|
329
|
+
end
|
330
|
+
|
331
|
+
def table_comment(table_name) #:nodoc:
|
332
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
333
|
+
select_value <<-SQL
|
334
|
+
SELECT comments FROM all_tab_comments#{db_link}
|
335
|
+
WHERE owner = '#{owner}'
|
336
|
+
AND table_name = '#{table_name}'
|
337
|
+
SQL
|
338
|
+
end
|
339
|
+
|
340
|
+
def column_comment(table_name, column_name) #:nodoc:
|
341
|
+
(owner, table_name, db_link) = @connection.describe(table_name)
|
342
|
+
select_value <<-SQL
|
343
|
+
SELECT comments FROM all_col_comments#{db_link}
|
344
|
+
WHERE owner = '#{owner}'
|
345
|
+
AND table_name = '#{table_name}'
|
346
|
+
AND column_name = '#{column_name.upcase}'
|
347
|
+
SQL
|
348
|
+
end
|
349
|
+
|
350
|
+
# Maps logical Rails types to Oracle-specific data types.
|
351
|
+
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
352
|
+
# Ignore options for :text and :binary columns
|
353
|
+
return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s)
|
354
|
+
|
355
|
+
super
|
356
|
+
end
|
357
|
+
|
358
|
+
def tablespace(table_name)
|
359
|
+
select_value <<-SQL
|
360
|
+
SELECT tablespace_name
|
361
|
+
FROM user_tables
|
362
|
+
WHERE table_name='#{table_name.to_s.upcase}'
|
363
|
+
SQL
|
364
|
+
end
|
365
|
+
|
366
|
+
private
|
367
|
+
|
368
|
+
def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil)
|
369
|
+
tablespace_sql = ''
|
370
|
+
if tablespace = (tablespace_option || default_tablespace_for(obj_type))
|
371
|
+
tablespace_sql << if [:blob, :clob].include?(obj_type.to_sym)
|
372
|
+
" LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})"
|
373
|
+
else
|
374
|
+
" TABLESPACE #{tablespace}"
|
375
|
+
end
|
376
|
+
end
|
377
|
+
tablespace_sql
|
378
|
+
end
|
379
|
+
|
380
|
+
def default_tablespace_for(type)
|
381
|
+
(default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil
|
382
|
+
end
|
383
|
+
|
384
|
+
|
385
|
+
def column_for(table_name, column_name)
|
386
|
+
unless column = columns(table_name).find { |c| c.name == column_name.to_s }
|
387
|
+
raise "No such column: #{table_name}.#{column_name}"
|
388
|
+
end
|
389
|
+
column
|
390
|
+
end
|
391
|
+
|
392
|
+
def create_sequence_and_trigger(table_name, options)
|
393
|
+
seq_name = options[:sequence_name] || default_sequence_name(table_name)
|
394
|
+
seq_start_value = options[:sequence_start_value] || default_sequence_start_value
|
395
|
+
execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
|
396
|
+
|
397
|
+
create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
|
398
|
+
end
|
399
|
+
|
400
|
+
def create_primary_key_trigger(table_name, options)
|
401
|
+
seq_name = options[:sequence_name] || default_sequence_name(table_name)
|
402
|
+
trigger_name = options[:trigger_name] || default_trigger_name(table_name)
|
403
|
+
primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
|
404
|
+
execute compress_lines(<<-SQL)
|
405
|
+
CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
|
406
|
+
BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
|
407
|
+
BEGIN
|
408
|
+
IF inserting THEN
|
409
|
+
IF :new.#{quote_column_name(primary_key)} IS NULL THEN
|
410
|
+
SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
|
411
|
+
END IF;
|
412
|
+
END IF;
|
413
|
+
END;
|
414
|
+
SQL
|
415
|
+
end
|
416
|
+
|
417
|
+
def default_trigger_name(table_name)
|
418
|
+
# truncate table name if necessary to fit in max length of identifier
|
419
|
+
"#{table_name.to_s[0,table_name_length-4]}_pkt"
|
420
|
+
end
|
421
|
+
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
|
427
|
+
include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatements
|
428
|
+
end
|