pmacs-activerecord-oracle_enhanced-adapter 1.4.2.rc1

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.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +52 -0
  3. data/History.md +284 -0
  4. data/License.txt +20 -0
  5. data/README.md +403 -0
  6. data/RUNNING_TESTS.md +45 -0
  7. data/Rakefile +59 -0
  8. data/VERSION +1 -0
  9. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1408 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +118 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +141 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +135 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +44 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +491 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +231 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +257 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +397 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +265 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
  29. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  30. data/lib/pmacs-activerecord-oracle_enhanced-adapter.rb +25 -0
  31. data/pmacs-activerecord-oracle_enhanced-adapter.gemspec +131 -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 +1376 -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 +438 -0
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1280 -0
  44. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
  45. data/spec/spec_helper.rb +187 -0
  46. metadata +302 -0
@@ -0,0 +1,257 @@
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[:type] = column.virtual? ? 'virtual' : column.type.to_s
180
+ spec[:virtual_type] = column.type.inspect if column.virtual?
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
+
235
+ # remove table name prefix and suffix when doing #inspect (which is used in tables method)
236
+ module TableInspect #:nodoc:
237
+ def inspect
238
+ remove_prefix_and_suffix(self)
239
+ end
240
+
241
+ private
242
+ def remove_prefix_and_suffix(table_name)
243
+ if table_name =~ /\A#{ActiveRecord::Base.table_name_prefix.to_s.gsub('$','\$')}(.*)#{ActiveRecord::Base.table_name_suffix.to_s.gsub('$','\$')}\Z/
244
+ "\"#{$1}\""
245
+ else
246
+ "\"#{table_name}\""
247
+ end
248
+ end
249
+ end
250
+
251
+ end
252
+ end
253
+ end
254
+
255
+ ActiveRecord::SchemaDumper.class_eval do
256
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaDumper
257
+ end
@@ -0,0 +1,397 @@
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
+ if type.to_sym == :virtual
214
+ type = options[:type]
215
+ end
216
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} "
217
+ add_column_sql << type_to_sql(type, options[:limit], options[:precision], options[:scale]) if type
218
+
219
+ add_column_options!(add_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
220
+
221
+ add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name) if type
222
+
223
+ execute(add_column_sql)
224
+ ensure
225
+ clear_table_columns_cache(table_name)
226
+ end
227
+
228
+ def change_column_default(table_name, column_name, default) #:nodoc:
229
+ execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
230
+ ensure
231
+ clear_table_columns_cache(table_name)
232
+ end
233
+
234
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
235
+ column = column_for(table_name, column_name)
236
+
237
+ unless null || default.nil?
238
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
239
+ end
240
+
241
+ change_column table_name, column_name, column.sql_type, :null => null
242
+ end
243
+
244
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
245
+ column = column_for(table_name, column_name)
246
+
247
+ # remove :null option if its value is the same as current column definition
248
+ # otherwise Oracle will raise error
249
+ if options.has_key?(:null) && options[:null] == column.null
250
+ options[:null] = nil
251
+ end
252
+ if type.to_sym == :virtual
253
+ type = options[:type]
254
+ end
255
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} "
256
+ change_column_sql << "#{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" if type
257
+
258
+ add_column_options!(change_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
259
+
260
+ change_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, options[:table_name], options[:column_name]) if type
261
+
262
+ execute(change_column_sql)
263
+ ensure
264
+ clear_table_columns_cache(table_name)
265
+ end
266
+
267
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
268
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
269
+ ensure
270
+ clear_table_columns_cache(table_name)
271
+ end
272
+
273
+ def remove_column(table_name, *column_names) #:nodoc:
274
+ major, minor = ActiveRecord::VERSION::MAJOR, ActiveRecord::VERSION::MINOR
275
+ is_deprecated = (major == 3 and minor >= 2) or major > 3
276
+
277
+ if column_names.flatten! and is_deprecated
278
+ message = 'Passing array to remove_columns is deprecated, please use ' +
279
+ 'multiple arguments, like: `remove_columns(:posts, :foo, :bar)`'
280
+ ActiveSupport::Deprecation.warn message, caller
281
+ end
282
+
283
+ column_names.each do |column_name|
284
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
285
+ end
286
+ ensure
287
+ clear_table_columns_cache(table_name)
288
+ end
289
+
290
+ def add_comment(table_name, column_name, comment) #:nodoc:
291
+ return if comment.blank?
292
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
293
+ end
294
+
295
+ def add_table_comment(table_name, comment) #:nodoc:
296
+ return if comment.blank?
297
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
298
+ end
299
+
300
+ def table_comment(table_name) #:nodoc:
301
+ (owner, table_name, db_link) = @connection.describe(table_name)
302
+ select_value <<-SQL
303
+ SELECT comments FROM all_tab_comments#{db_link}
304
+ WHERE owner = '#{owner}'
305
+ AND table_name = '#{table_name}'
306
+ SQL
307
+ end
308
+
309
+ def column_comment(table_name, column_name) #:nodoc:
310
+ (owner, table_name, db_link) = @connection.describe(table_name)
311
+ select_value <<-SQL
312
+ SELECT comments FROM all_col_comments#{db_link}
313
+ WHERE owner = '#{owner}'
314
+ AND table_name = '#{table_name}'
315
+ AND column_name = '#{column_name.upcase}'
316
+ SQL
317
+ end
318
+
319
+ # Maps logical Rails types to Oracle-specific data types.
320
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
321
+ # Ignore options for :text and :binary columns
322
+ return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s)
323
+
324
+ super
325
+ end
326
+
327
+ def tablespace(table_name)
328
+ select_value <<-SQL
329
+ SELECT tablespace_name
330
+ FROM user_tables
331
+ WHERE table_name='#{table_name.to_s.upcase}'
332
+ SQL
333
+ end
334
+
335
+ private
336
+
337
+ def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil)
338
+ tablespace_sql = ''
339
+ if tablespace = (tablespace_option || default_tablespace_for(obj_type))
340
+ tablespace_sql << if [:blob, :clob].include?(obj_type.to_sym)
341
+ " LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})"
342
+ else
343
+ " TABLESPACE #{tablespace}"
344
+ end
345
+ end
346
+ tablespace_sql
347
+ end
348
+
349
+ def default_tablespace_for(type)
350
+ (default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil
351
+ end
352
+
353
+
354
+ def column_for(table_name, column_name)
355
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
356
+ raise "No such column: #{table_name}.#{column_name}"
357
+ end
358
+ column
359
+ end
360
+
361
+ def create_sequence_and_trigger(table_name, options)
362
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
363
+ seq_start_value = options[:sequence_start_value] || default_sequence_start_value
364
+ execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
365
+
366
+ create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
367
+ end
368
+
369
+ def create_primary_key_trigger(table_name, options)
370
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
371
+ trigger_name = options[:trigger_name] || default_trigger_name(table_name)
372
+ primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
373
+ execute compress_lines(<<-SQL)
374
+ CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
375
+ BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
376
+ BEGIN
377
+ IF inserting THEN
378
+ IF :new.#{quote_column_name(primary_key)} IS NULL THEN
379
+ SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
380
+ END IF;
381
+ END IF;
382
+ END;
383
+ SQL
384
+ end
385
+
386
+ def default_trigger_name(table_name)
387
+ # truncate table name if necessary to fit in max length of identifier
388
+ "#{table_name.to_s[0,table_name_length-4]}_pkt"
389
+ end
390
+
391
+ end
392
+ end
393
+ end
394
+
395
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
396
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatements
397
+ end