pmacs-activerecord-oracle_enhanced-adapter 1.4.2.rc1

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 +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