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