activerecord-oracle_enhanced-adapter-with-schema 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +52 -0
  3. data/History.md +301 -0
  4. data/License.txt +20 -0
  5. data/README.md +123 -0
  6. data/RUNNING_TESTS.md +45 -0
  7. data/Rakefile +59 -0
  8. data/VERSION +1 -0
  9. data/activerecord-oracle_enhanced-adapter-with-schema.gemspec +130 -0
  10. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1399 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +121 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +146 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +46 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +494 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +227 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +260 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +428 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +258 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -0
  29. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
  30. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  31. data/lib/activerecord-oracle_enhanced-adapter-with-schema.rb +25 -0
  32. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +778 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +332 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +427 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +19 -0
  36. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +113 -0
  37. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +1388 -0
  38. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +69 -0
  39. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +141 -0
  40. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  41. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +378 -0
  42. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +440 -0
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1385 -0
  44. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
  45. data/spec/spec_helper.rb +189 -0
  46. 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