activerecord-oracle_enhanced-adapter-with-schema 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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