activerecord-oracle_enhanced-adapter 1.5.6 → 1.6.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/History.md +87 -0
  4. data/README.md +271 -174
  5. data/VERSION +1 -1
  6. data/activerecord-oracle_enhanced-adapter.gemspec +26 -22
  7. data/lib/active_record/connection_adapters/{oracle_enhanced_column.rb → oracle_enhanced/column.rb} +14 -63
  8. data/lib/active_record/connection_adapters/oracle_enhanced/column_dumper.rb +65 -0
  9. data/lib/active_record/connection_adapters/{oracle_enhanced_connection.rb → oracle_enhanced/connection.rb} +2 -2
  10. data/lib/active_record/connection_adapters/oracle_enhanced/context_index.rb +347 -0
  11. data/lib/active_record/connection_adapters/{oracle_enhanced_cpk.rb → oracle_enhanced/cpk.rb} +0 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/database_statements.rb +257 -0
  13. data/lib/active_record/connection_adapters/{oracle_enhanced_database_tasks.rb → oracle_enhanced/database_tasks.rb} +0 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced/dirty.rb +40 -0
  15. data/lib/active_record/connection_adapters/{oracle_enhanced_jdbc_connection.rb → oracle_enhanced/jdbc_connection.rb} +0 -0
  16. data/lib/active_record/connection_adapters/{oracle_enhanced_oci_connection.rb → oracle_enhanced/oci_connection.rb} +0 -0
  17. data/lib/active_record/connection_adapters/{oracle_enhanced_procedures.rb → oracle_enhanced/procedures.rb} +0 -0
  18. data/lib/active_record/connection_adapters/{oracle_enhanced_schema_creation.rb → oracle_enhanced/schema_creation.rb} +17 -16
  19. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +92 -0
  20. data/lib/active_record/connection_adapters/{oracle_enhanced_schema_dumper.rb → oracle_enhanced/schema_dumper.rb} +4 -32
  21. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +543 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +65 -0
  23. data/lib/active_record/connection_adapters/{oracle_enhanced_structure_dump.rb → oracle_enhanced/structure_dump.rb} +26 -4
  24. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +1 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +159 -66
  26. data/lib/active_record/oracle_enhanced/type/integer.rb +13 -0
  27. data/lib/active_record/oracle_enhanced/type/raw.rb +13 -0
  28. data/lib/active_record/oracle_enhanced/type/timestamp.rb +11 -0
  29. data/lib/activerecord-oracle_enhanced-adapter.rb +1 -1
  30. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +6 -31
  31. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +1 -1
  32. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +2 -2
  33. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +2 -2
  34. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +63 -63
  35. data/spec/active_record/connection_adapters/oracle_enhanced_database_tasks_spec.rb +1 -1
  36. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +7 -13
  37. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +21 -175
  38. data/spec/spec_config.yaml.template +10 -0
  39. data/spec/spec_helper.rb +21 -10
  40. metadata +29 -25
  41. data/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb +0 -77
  42. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +0 -350
  43. data/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb +0 -262
  44. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +0 -45
  45. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +0 -197
  46. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +0 -450
  47. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +0 -258
  48. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +0 -1
@@ -7,6 +7,7 @@ module ActiveRecord #:nodoc:
7
7
  private
8
8
  alias_method_chain :tables, :oracle_enhanced
9
9
  alias_method_chain :indexes, :oracle_enhanced
10
+ alias_method_chain :foreign_keys, :oracle_enhanced
10
11
  end
11
12
  end
12
13
 
@@ -56,37 +57,8 @@ module ActiveRecord #:nodoc:
56
57
  end
57
58
  end
58
59
 
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[:references].first.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
60
+ def foreign_keys_with_oracle_enhanced(table_name, stream)
61
+ return foreign_keys_without_oracle_enhanced(table_name, stream)
90
62
  end
91
63
 
92
64
  def synonyms(stream)
@@ -166,7 +138,7 @@ module ActiveRecord #:nodoc:
166
138
  else
167
139
  tbl.print ", id: false"
168
140
  end
169
- tbl.print ", force: true"
141
+ tbl.print ", force: :cascade"
170
142
  tbl.puts " do |t|"
171
143
 
172
144
  # then dump all non-primary key columns
@@ -0,0 +1,543 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'digest/sha1'
3
+
4
+ module ActiveRecord
5
+ module ConnectionAdapters
6
+ module OracleEnhanced
7
+ module SchemaStatements
8
+ # SCHEMA STATEMENTS ========================================
9
+ #
10
+ # see: abstract/schema_statements.rb
11
+
12
+ # Additional options for +create_table+ method in migration files.
13
+ #
14
+ # You can specify individual starting value in table creation migration file, e.g.:
15
+ #
16
+ # create_table :users, :sequence_start_value => 100 do |t|
17
+ # # ...
18
+ # end
19
+ #
20
+ # You can also specify other sequence definition additional parameters, e.g.:
21
+ #
22
+ # create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t|
23
+ # # ...
24
+ # end
25
+ #
26
+ # Create primary key trigger (so that you can skip primary key value in INSERT statement).
27
+ # By default trigger name will be "table_name_pkt", you can override the name with
28
+ # :trigger_name option (but it is not recommended to override it as then this trigger will
29
+ # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
30
+ # Example:
31
+ #
32
+ # create_table :users, :primary_key_trigger => true do |t|
33
+ # # ...
34
+ # end
35
+ #
36
+ # It is possible to add table and column comments in table creation migration files:
37
+ #
38
+ # create_table :employees, :comment => “Employees and contractors” do |t|
39
+ # t.string :first_name, :comment => “Given name”
40
+ # t.string :last_name, :comment => “Surname”
41
+ # end
42
+
43
+ def create_table(table_name, options = {})
44
+ create_sequence = options[:id] != false
45
+ column_comments = {}
46
+ temporary = options.delete(:temporary)
47
+ additional_options = options
48
+ td = create_table_definition table_name, temporary, additional_options
49
+ td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false
50
+
51
+ # store that primary key was defined in create_table block
52
+ unless create_sequence
53
+ class << td
54
+ attr_accessor :create_sequence
55
+ def primary_key(*args)
56
+ self.create_sequence = true
57
+ super(*args)
58
+ end
59
+ end
60
+ end
61
+
62
+ # store column comments
63
+ class << td
64
+ attr_accessor :column_comments
65
+ def column(name, type, options = {})
66
+ if options[:comment]
67
+ self.column_comments ||= {}
68
+ self.column_comments[name] = options[:comment]
69
+ end
70
+ super(name, type, options)
71
+ end
72
+ end
73
+
74
+ yield td if block_given?
75
+ create_sequence = create_sequence || td.create_sequence
76
+ column_comments = td.column_comments if td.column_comments
77
+ tablespace = tablespace_for(:table, options[:tablespace])
78
+
79
+ if options[:force] && table_exists?(table_name)
80
+ drop_table(table_name, options)
81
+ end
82
+
83
+ execute schema_creation.accept td
84
+
85
+ create_sequence_and_trigger(table_name, options) if create_sequence
86
+
87
+ add_table_comment table_name, options[:comment]
88
+ column_comments.each do |column_name, comment|
89
+ add_comment table_name, column_name, comment
90
+ end
91
+ td.indexes.each_pair { |c,o| add_index table_name, c, o }
92
+
93
+ td.foreign_keys.each_pair do |other_table_name, foreign_key_options|
94
+ add_foreign_key(table_name, other_table_name, foreign_key_options)
95
+ end
96
+ end
97
+
98
+ def create_table_definition(name, temporary, options)
99
+ ActiveRecord::ConnectionAdapters::OracleEnhanced::TableDefinition.new native_database_types, name, temporary, options
100
+ end
101
+
102
+ def rename_table(table_name, new_name) #:nodoc:
103
+ if new_name.to_s.length > table_name_length
104
+ raise ArgumentError, "New table name '#{new_name}' is too long; the limit is #{table_name_length} characters"
105
+ end
106
+ if "#{new_name}_seq".to_s.length > sequence_name_length
107
+ raise ArgumentError, "New sequence name '#{new_name}_seq' is too long; the limit is #{sequence_name_length} characters"
108
+ end
109
+ execute "RENAME #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
110
+ execute "RENAME #{quote_table_name("#{table_name}_seq")} TO #{quote_table_name("#{new_name}_seq")}" rescue nil
111
+
112
+ rename_table_indexes(table_name, new_name)
113
+ end
114
+
115
+ def drop_table(table_name, options = {}) #:nodoc:
116
+ execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE CONSTRAINTS' if options[:force] == :cascade}"
117
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
118
+ execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
119
+ rescue ActiveRecord::StatementInvalid => e
120
+ raise e unless options[:if_exists]
121
+ ensure
122
+ clear_table_columns_cache(table_name)
123
+ self.all_schema_indexes = nil
124
+ end
125
+
126
+ def dump_schema_information #:nodoc:
127
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
128
+ migrated = select_values("SELECT version FROM #{sm_table} ORDER BY version")
129
+ join_with_statement_token(migrated.map{|v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" })
130
+ end
131
+
132
+ def initialize_schema_migrations_table
133
+ sm_table = ActiveRecord::Migrator.schema_migrations_table_name
134
+
135
+ unless table_exists?(sm_table)
136
+ index_name = "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
137
+ if index_name.length > index_name_length
138
+ truncate_to = index_name_length - index_name.to_s.length - 1
139
+ truncated_name = "unique_schema_migrations"[0..truncate_to]
140
+ index_name = "#{Base.table_name_prefix}#{truncated_name}#{Base.table_name_suffix}"
141
+ end
142
+
143
+ create_table(sm_table, :id => false) do |schema_migrations_table|
144
+ schema_migrations_table.column :version, :string, :null => false
145
+ end
146
+ add_index sm_table, :version, :unique => true, :name => index_name
147
+
148
+ # Backwards-compatibility: if we find schema_info, assume we've
149
+ # migrated up to that point:
150
+ si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
151
+ if table_exists?(si_table)
152
+ ActiveSupport::Deprecation.warn "Usage of the schema table `#{si_table}` is deprecated. Please switch to using `schema_migrations` table"
153
+
154
+ old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
155
+ assume_migrated_upto_version(old_version)
156
+ drop_table(si_table)
157
+ end
158
+ end
159
+ end
160
+
161
+ def update_table_definition(table_name, base) #:nodoc:
162
+ OracleEnhanced::Table.new(table_name, base)
163
+ end
164
+
165
+ def add_index(table_name, column_name, options = {}) #:nodoc:
166
+ index_name, index_type, quoted_column_names, tablespace, index_options = add_index_options(table_name, column_name, options)
167
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{index_options}"
168
+ if index_type == 'UNIQUE'
169
+ execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(index_name)} #{index_type} (#{quoted_column_names})"
170
+ end
171
+ ensure
172
+ self.all_schema_indexes = nil
173
+ end
174
+
175
+ def add_index_options(table_name, column_name, options = {}) #:nodoc:
176
+ column_names = Array(column_name)
177
+ index_name = index_name(table_name, column: column_names)
178
+
179
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :tablespace, :options, :using)
180
+
181
+ index_type = options[:unique] ? "UNIQUE" : ""
182
+ index_name = options[:name].to_s if options.key?(:name)
183
+ tablespace = tablespace_for(:index, options[:tablespace])
184
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
185
+ #TODO: This option is used for NOLOGGING, needs better argumetn name
186
+ index_options = options[:options]
187
+
188
+ if index_name.to_s.length > max_index_length
189
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
190
+ end
191
+ if index_name_exists?(table_name, index_name, false)
192
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
193
+ end
194
+
195
+ quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ")
196
+ [index_name, index_type, quoted_column_names, tablespace, index_options]
197
+ end
198
+
199
+ # Remove the given index from the table.
200
+ # Gives warning if index does not exist
201
+ def remove_index(table_name, options = {}) #:nodoc:
202
+ index_name = index_name(table_name, options)
203
+ unless index_name_exists?(table_name, index_name, true)
204
+ # sometimes options can be String or Array with column names
205
+ options = {} unless options.is_a?(Hash)
206
+ if options.has_key? :name
207
+ options_without_column = options.dup
208
+ options_without_column.delete :column
209
+ index_name_without_column = index_name(table_name, options_without_column)
210
+ return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
211
+ end
212
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
213
+ end
214
+ remove_index!(table_name, index_name)
215
+ end
216
+
217
+ # clear cached indexes when removing index
218
+ def remove_index!(table_name, index_name) #:nodoc:
219
+ #TODO: It should execute only when index_type == "UNIQUE"
220
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(index_name)}" rescue nil
221
+ execute "DROP INDEX #{quote_column_name(index_name)}"
222
+ ensure
223
+ self.all_schema_indexes = nil
224
+ end
225
+
226
+ # returned shortened index name if default is too large
227
+ def index_name(table_name, options) #:nodoc:
228
+ default_name = super(table_name, options).to_s
229
+ # sometimes options can be String or Array with column names
230
+ options = {} unless options.is_a?(Hash)
231
+ identifier_max_length = options[:identifier_max_length] || index_name_length
232
+ return default_name if default_name.length <= identifier_max_length
233
+
234
+ # remove 'index', 'on' and 'and' keywords
235
+ shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
236
+
237
+ # leave just first three letters from each word
238
+ if shortened_name.length > identifier_max_length
239
+ shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
240
+ end
241
+ # generate unique name using hash function
242
+ if shortened_name.length > identifier_max_length
243
+ shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1]
244
+ end
245
+ @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
246
+ shortened_name
247
+ end
248
+
249
+ # Verify the existence of an index with a given name.
250
+ #
251
+ # The default argument is returned if the underlying implementation does not define the indexes method,
252
+ # as there's no way to determine the correct answer in that case.
253
+ #
254
+ # Will always query database and not index cache.
255
+ def index_name_exists?(table_name, index_name, default)
256
+ (owner, table_name, db_link) = @connection.describe(table_name)
257
+ result = select_value(<<-SQL)
258
+ SELECT 1 FROM all_indexes#{db_link} i
259
+ WHERE i.owner = '#{owner}'
260
+ AND i.table_owner = '#{owner}'
261
+ AND i.table_name = '#{table_name}'
262
+ AND i.index_name = '#{index_name.to_s.upcase}'
263
+ SQL
264
+ result == 1
265
+ end
266
+
267
+ def rename_index(table_name, old_name, new_name) #:nodoc:
268
+ unless index_name_exists?(table_name, old_name, true)
269
+ raise ArgumentError, "Index name '#{old_name}' on table '#{table_name}' does not exist"
270
+ end
271
+ if new_name.length > allowed_index_name_length
272
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
273
+ end
274
+ execute "ALTER INDEX #{quote_column_name(old_name)} rename to #{quote_column_name(new_name)}"
275
+ ensure
276
+ self.all_schema_indexes = nil
277
+ end
278
+
279
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
280
+ if type.to_sym == :virtual
281
+ type = options[:type]
282
+ end
283
+ type = aliased_types(type.to_s, type)
284
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} "
285
+ add_column_sql << type_to_sql(type, options[:limit], options[:precision], options[:scale]) if type
286
+
287
+ add_column_options!(add_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
288
+
289
+ add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name) if type
290
+
291
+ execute(add_column_sql)
292
+
293
+ create_sequence_and_trigger(table_name, options) if type && type.to_sym == :primary_key
294
+ ensure
295
+ clear_table_columns_cache(table_name)
296
+ end
297
+
298
+ def aliased_types(name, fallback)
299
+ fallback
300
+ end
301
+
302
+ def change_column_default(table_name, column_name, default) #:nodoc:
303
+ execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
304
+ ensure
305
+ clear_table_columns_cache(table_name)
306
+ end
307
+
308
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
309
+ column = column_for(table_name, column_name)
310
+
311
+ unless null || default.nil?
312
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
313
+ end
314
+
315
+ change_column table_name, column_name, column.sql_type, :null => null
316
+ end
317
+
318
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
319
+ column = column_for(table_name, column_name)
320
+
321
+ # remove :null option if its value is the same as current column definition
322
+ # otherwise Oracle will raise error
323
+ if options.has_key?(:null) && options[:null] == column.null
324
+ options[:null] = nil
325
+ end
326
+ if type.to_sym == :virtual
327
+ type = options[:type]
328
+ end
329
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} "
330
+ change_column_sql << "#{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" if type
331
+
332
+ add_column_options!(change_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
333
+
334
+ change_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, options[:table_name], options[:column_name]) if type
335
+
336
+ execute(change_column_sql)
337
+ ensure
338
+ clear_table_columns_cache(table_name)
339
+ end
340
+
341
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
342
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
343
+ self.all_schema_indexes = nil
344
+ rename_column_indexes(table_name, column_name, new_column_name)
345
+ ensure
346
+ clear_table_columns_cache(table_name)
347
+ end
348
+
349
+ def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
350
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)} CASCADE CONSTRAINTS"
351
+ ensure
352
+ clear_table_columns_cache(table_name)
353
+ self.all_schema_indexes = nil
354
+ end
355
+
356
+ def add_comment(table_name, column_name, comment) #:nodoc:
357
+ return if comment.blank?
358
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
359
+ end
360
+
361
+ def add_table_comment(table_name, comment) #:nodoc:
362
+ return if comment.blank?
363
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
364
+ end
365
+
366
+ def table_comment(table_name) #:nodoc:
367
+ (owner, table_name, db_link) = @connection.describe(table_name)
368
+ select_value <<-SQL
369
+ SELECT comments FROM all_tab_comments#{db_link}
370
+ WHERE owner = '#{owner}'
371
+ AND table_name = '#{table_name}'
372
+ SQL
373
+ end
374
+
375
+ def column_comment(table_name, column_name) #:nodoc:
376
+ (owner, table_name, db_link) = @connection.describe(table_name)
377
+ select_value <<-SQL
378
+ SELECT comments FROM all_col_comments#{db_link}
379
+ WHERE owner = '#{owner}'
380
+ AND table_name = '#{table_name}'
381
+ AND column_name = '#{column_name.upcase}'
382
+ SQL
383
+ end
384
+
385
+ # Maps logical Rails types to Oracle-specific data types.
386
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
387
+ # Ignore options for :text and :binary columns
388
+ return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s)
389
+
390
+ super
391
+ end
392
+
393
+ def tablespace(table_name)
394
+ select_value <<-SQL
395
+ SELECT tablespace_name
396
+ FROM user_tables
397
+ WHERE table_name='#{table_name.to_s.upcase}'
398
+ SQL
399
+ end
400
+
401
+ def add_foreign_key(from_table, to_table, options = {})
402
+ case options[:dependent]
403
+ when :delete then options[:on_delete] = :cascade
404
+ when :nullify then options[:on_delete] = :nullify
405
+ else
406
+ end
407
+
408
+ super
409
+ end
410
+
411
+ def remove_foreign_key(from_table, options_or_to_table = {})
412
+ super
413
+ end
414
+
415
+ # get table foreign keys for schema dump
416
+ def foreign_keys(table_name) #:nodoc:
417
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
418
+
419
+ fk_info = select_all(<<-SQL, 'Foreign Keys')
420
+ SELECT r.table_name to_table
421
+ ,rc.column_name references_column
422
+ ,cc.column_name
423
+ ,c.constraint_name name
424
+ ,c.delete_rule
425
+ FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc,
426
+ user_constraints#{db_link} r, user_cons_columns#{db_link} rc
427
+ WHERE c.owner = '#{owner}'
428
+ AND c.table_name = '#{desc_table_name}'
429
+ AND c.constraint_type = 'R'
430
+ AND cc.owner = c.owner
431
+ AND cc.constraint_name = c.constraint_name
432
+ AND r.constraint_name = c.r_constraint_name
433
+ AND r.owner = c.owner
434
+ AND rc.owner = r.owner
435
+ AND rc.constraint_name = r.constraint_name
436
+ AND rc.position = cc.position
437
+ ORDER BY name, to_table, column_name, references_column
438
+ SQL
439
+
440
+ fk_info.map do |row|
441
+ options = {
442
+ column: oracle_downcase(row['column_name']),
443
+ name: oracle_downcase(row['name']),
444
+ primary_key: oracle_downcase(row['references_column'])
445
+ }
446
+ options[:on_delete] = extract_foreign_key_action(row['delete_rule'])
447
+ OracleEnhanced::ForeignKeyDefinition.new(oracle_downcase(table_name), oracle_downcase(row['to_table']), options)
448
+ end
449
+ end
450
+
451
+ def extract_foreign_key_action(specifier) # :nodoc:
452
+ case specifier
453
+ when 'CASCADE'; :cascade
454
+ when 'SET NULL'; :nullify
455
+ end
456
+ end
457
+
458
+ # REFERENTIAL INTEGRITY ====================================
459
+
460
+ def disable_referential_integrity(&block) #:nodoc:
461
+ sql_constraints = <<-SQL
462
+ SELECT constraint_name, owner, table_name
463
+ FROM user_constraints
464
+ WHERE constraint_type = 'R'
465
+ AND status = 'ENABLED'
466
+ SQL
467
+ old_constraints = select_all(sql_constraints)
468
+ begin
469
+ old_constraints.each do |constraint|
470
+ execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}"
471
+ end
472
+ yield
473
+ ensure
474
+ old_constraints.each do |constraint|
475
+ execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}"
476
+ end
477
+ end
478
+ end
479
+
480
+ private
481
+
482
+ def create_alter_table(name)
483
+ OracleEnhanced::AlterTable.new create_table_definition(name, false, {})
484
+ end
485
+
486
+ def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil)
487
+ tablespace_sql = ''
488
+ if tablespace = (tablespace_option || default_tablespace_for(obj_type))
489
+ tablespace_sql << if [:blob, :clob].include?(obj_type.to_sym)
490
+ " LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})"
491
+ else
492
+ " TABLESPACE #{tablespace}"
493
+ end
494
+ end
495
+ tablespace_sql
496
+ end
497
+
498
+ def default_tablespace_for(type)
499
+ (default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil
500
+ end
501
+
502
+
503
+ def column_for(table_name, column_name)
504
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
505
+ raise "No such column: #{table_name}.#{column_name}"
506
+ end
507
+ column
508
+ end
509
+
510
+ def create_sequence_and_trigger(table_name, options)
511
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
512
+ seq_start_value = options[:sequence_start_value] || default_sequence_start_value
513
+ execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
514
+
515
+ create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
516
+ end
517
+
518
+ def create_primary_key_trigger(table_name, options)
519
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
520
+ trigger_name = options[:trigger_name] || default_trigger_name(table_name)
521
+ primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
522
+ execute compress_lines(<<-SQL)
523
+ CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
524
+ BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
525
+ BEGIN
526
+ IF inserting THEN
527
+ IF :new.#{quote_column_name(primary_key)} IS NULL THEN
528
+ SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
529
+ END IF;
530
+ END IF;
531
+ END;
532
+ SQL
533
+ end
534
+
535
+ def default_trigger_name(table_name)
536
+ # truncate table name if necessary to fit in max length of identifier
537
+ "#{table_name.to_s[0,table_name_length-4]}_pkt"
538
+ end
539
+
540
+ end
541
+ end
542
+ end
543
+ end