pmacs-activerecord-oracle_enhanced-adapter 1.5.6.1 → 1.6.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -7
  3. data/History.md +126 -0
  4. data/README.md +285 -178
  5. data/Rakefile +1 -1
  6. data/VERSION +1 -1
  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} +1 -3
  18. data/lib/active_record/connection_adapters/{oracle_enhanced_schema_creation.rb → oracle_enhanced/schema_creation.rb} +34 -35
  19. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +95 -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 +548 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +74 -0
  23. data/lib/active_record/connection_adapters/{oracle_enhanced_structure_dump.rb → oracle_enhanced/structure_dump.rb} +28 -6
  24. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +1 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +161 -68
  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/pmacs-activerecord-oracle_enhanced-adapter.rb +1 -1
  30. data/pmacs-activerecord-oracle_enhanced-adapter.gemspec +35 -31
  31. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +6 -31
  32. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +1 -1
  33. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +2 -2
  34. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +2 -2
  35. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +75 -63
  36. data/spec/active_record/connection_adapters/oracle_enhanced_database_tasks_spec.rb +1 -1
  37. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +7 -13
  38. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +2 -1
  39. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +25 -178
  40. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +60 -5
  41. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +1 -0
  42. data/spec/spec_helper.rb +21 -10
  43. metadata +32 -28
  44. data/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb +0 -77
  45. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +0 -350
  46. data/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb +0 -262
  47. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +0 -45
  48. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +0 -223
  49. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +0 -450
  50. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +0 -267
  51. 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,548 @@
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
+ unless quoted_column_names =~ /\(.*\)/
170
+ execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{quote_column_name(index_name)} #{index_type} (#{quoted_column_names})"
171
+ end
172
+ end
173
+ ensure
174
+ self.all_schema_indexes = nil
175
+ end
176
+
177
+ def add_index_options(table_name, column_name, options = {}) #:nodoc:
178
+ column_names = Array(column_name)
179
+ index_name = index_name(table_name, column: column_names)
180
+
181
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :tablespace, :options, :using)
182
+
183
+ index_type = options[:unique] ? "UNIQUE" : ""
184
+ index_name = options[:name].to_s if options.key?(:name)
185
+ tablespace = tablespace_for(:index, options[:tablespace])
186
+ max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
187
+ #TODO: This option is used for NOLOGGING, needs better argumetn name
188
+ index_options = options[:options]
189
+
190
+ if index_name.to_s.length > max_index_length
191
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
192
+ end
193
+ if index_name_exists?(table_name, index_name, false)
194
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
195
+ end
196
+
197
+ quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ")
198
+ [index_name, index_type, quoted_column_names, tablespace, index_options]
199
+ end
200
+
201
+ # Remove the given index from the table.
202
+ # Gives warning if index does not exist
203
+ def remove_index(table_name, options = {}) #:nodoc:
204
+ index_name = index_name(table_name, options)
205
+ unless index_name_exists?(table_name, index_name, true)
206
+ # sometimes options can be String or Array with column names
207
+ options = {} unless options.is_a?(Hash)
208
+ if options.has_key? :name
209
+ options_without_column = options.dup
210
+ options_without_column.delete :column
211
+ index_name_without_column = index_name(table_name, options_without_column)
212
+ return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
213
+ end
214
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
215
+ end
216
+ remove_index!(table_name, index_name)
217
+ end
218
+
219
+ # clear cached indexes when removing index
220
+ def remove_index!(table_name, index_name) #:nodoc:
221
+ #TODO: It should execute only when index_type == "UNIQUE"
222
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP CONSTRAINT #{quote_column_name(index_name)}" rescue nil
223
+ execute "DROP INDEX #{quote_column_name(index_name)}"
224
+ ensure
225
+ self.all_schema_indexes = nil
226
+ end
227
+
228
+ # returned shortened index name if default is too large
229
+ def index_name(table_name, options) #:nodoc:
230
+ default_name = super(table_name, options).to_s
231
+ # sometimes options can be String or Array with column names
232
+ options = {} unless options.is_a?(Hash)
233
+ identifier_max_length = options[:identifier_max_length] || index_name_length
234
+ return default_name if default_name.length <= identifier_max_length
235
+
236
+ # remove 'index', 'on' and 'and' keywords
237
+ shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
238
+
239
+ # leave just first three letters from each word
240
+ if shortened_name.length > identifier_max_length
241
+ shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
242
+ end
243
+ # generate unique name using hash function
244
+ if shortened_name.length > identifier_max_length
245
+ shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1]
246
+ end
247
+ @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
248
+ shortened_name
249
+ end
250
+
251
+ # Verify the existence of an index with a given name.
252
+ #
253
+ # The default argument is returned if the underlying implementation does not define the indexes method,
254
+ # as there's no way to determine the correct answer in that case.
255
+ #
256
+ # Will always query database and not index cache.
257
+ def index_name_exists?(table_name, index_name, default)
258
+ (owner, table_name, db_link) = @connection.describe(table_name)
259
+ result = select_value(<<-SQL)
260
+ SELECT 1 FROM all_indexes#{db_link} i
261
+ WHERE i.owner = '#{owner}'
262
+ AND i.table_owner = '#{owner}'
263
+ AND i.table_name = '#{table_name}'
264
+ AND i.index_name = '#{index_name.to_s.upcase}'
265
+ SQL
266
+ result == 1
267
+ end
268
+
269
+ def rename_index(table_name, old_name, new_name) #:nodoc:
270
+ unless index_name_exists?(table_name, old_name, true)
271
+ raise ArgumentError, "Index name '#{old_name}' on table '#{table_name}' does not exist"
272
+ end
273
+ if new_name.length > allowed_index_name_length
274
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
275
+ end
276
+ execute "ALTER INDEX #{quote_column_name(old_name)} rename to #{quote_column_name(new_name)}"
277
+ ensure
278
+ self.all_schema_indexes = nil
279
+ end
280
+
281
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
282
+ if type.to_sym == :virtual
283
+ type = options[:type]
284
+ end
285
+ type = aliased_types(type.to_s, type)
286
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} "
287
+ add_column_sql << type_to_sql(type, options[:limit], options[:precision], options[:scale]) if type
288
+
289
+ add_column_options!(add_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
290
+
291
+ add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name) if type
292
+
293
+ execute(add_column_sql)
294
+
295
+ create_sequence_and_trigger(table_name, options) if type && type.to_sym == :primary_key
296
+ ensure
297
+ clear_table_columns_cache(table_name)
298
+ end
299
+
300
+ def aliased_types(name, fallback)
301
+ fallback
302
+ end
303
+
304
+ def change_column_default(table_name, column_name, default) #:nodoc:
305
+ execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
306
+ ensure
307
+ clear_table_columns_cache(table_name)
308
+ end
309
+
310
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
311
+ column = column_for(table_name, column_name)
312
+
313
+ unless null || default.nil?
314
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
315
+ end
316
+
317
+ change_column table_name, column_name, column.sql_type, :null => null
318
+ end
319
+
320
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
321
+ column = column_for(table_name, column_name)
322
+
323
+ # remove :null option if its value is the same as current column definition
324
+ # otherwise Oracle will raise error
325
+ if options.has_key?(:null) && options[:null] == column.null
326
+ options[:null] = nil
327
+ end
328
+ if type.to_sym == :virtual
329
+ type = options[:type]
330
+ end
331
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} "
332
+ change_column_sql << "#{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" if type
333
+
334
+ add_column_options!(change_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
335
+
336
+ change_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, options[:table_name], options[:column_name]) if type
337
+
338
+ execute(change_column_sql)
339
+ ensure
340
+ clear_table_columns_cache(table_name)
341
+ end
342
+
343
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
344
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
345
+ self.all_schema_indexes = nil
346
+ rename_column_indexes(table_name, column_name, new_column_name)
347
+ ensure
348
+ clear_table_columns_cache(table_name)
349
+ end
350
+
351
+ def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
352
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)} CASCADE CONSTRAINTS"
353
+ ensure
354
+ clear_table_columns_cache(table_name)
355
+ self.all_schema_indexes = nil
356
+ end
357
+
358
+ def add_comment(table_name, column_name, comment) #:nodoc:
359
+ return if comment.blank?
360
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
361
+ end
362
+
363
+ def add_table_comment(table_name, comment) #:nodoc:
364
+ return if comment.blank?
365
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
366
+ end
367
+
368
+ def table_comment(table_name) #:nodoc:
369
+ (owner, table_name, db_link) = @connection.describe(table_name)
370
+ select_value <<-SQL
371
+ SELECT comments FROM all_tab_comments#{db_link}
372
+ WHERE owner = '#{owner}'
373
+ AND table_name = '#{table_name}'
374
+ SQL
375
+ end
376
+
377
+ def column_comment(table_name, column_name) #:nodoc:
378
+ (owner, table_name, db_link) = @connection.describe(table_name)
379
+ select_value <<-SQL
380
+ SELECT comments FROM all_col_comments#{db_link}
381
+ WHERE owner = '#{owner}'
382
+ AND table_name = '#{table_name}'
383
+ AND column_name = '#{column_name.upcase}'
384
+ SQL
385
+ end
386
+
387
+ # Maps logical Rails types to Oracle-specific data types.
388
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
389
+ # Ignore options for :text and :binary columns
390
+ return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s)
391
+
392
+ super
393
+ end
394
+
395
+ def tablespace(table_name)
396
+ select_value <<-SQL
397
+ SELECT tablespace_name
398
+ FROM user_tables
399
+ WHERE table_name='#{table_name.to_s.upcase}'
400
+ SQL
401
+ end
402
+
403
+ def add_foreign_key(from_table, to_table, options = {})
404
+ if options[:dependent]
405
+ ActiveSupport::Deprecation.warn "`:dependent` option will be deprecated. Please use `:on_delete` option"
406
+ end
407
+ case options[:dependent]
408
+ when :delete then options[:on_delete] = :cascade
409
+ when :nullify then options[:on_delete] = :nullify
410
+ else
411
+ end
412
+
413
+ super
414
+ end
415
+
416
+ def remove_foreign_key(from_table, options_or_to_table = {})
417
+ super
418
+ end
419
+
420
+ # get table foreign keys for schema dump
421
+ def foreign_keys(table_name) #:nodoc:
422
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
423
+
424
+ fk_info = select_all(<<-SQL, 'Foreign Keys')
425
+ SELECT r.table_name to_table
426
+ ,rc.column_name references_column
427
+ ,cc.column_name
428
+ ,c.constraint_name name
429
+ ,c.delete_rule
430
+ FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc,
431
+ user_constraints#{db_link} r, user_cons_columns#{db_link} rc
432
+ WHERE c.owner = '#{owner}'
433
+ AND c.table_name = '#{desc_table_name}'
434
+ AND c.constraint_type = 'R'
435
+ AND cc.owner = c.owner
436
+ AND cc.constraint_name = c.constraint_name
437
+ AND r.constraint_name = c.r_constraint_name
438
+ AND r.owner = c.owner
439
+ AND rc.owner = r.owner
440
+ AND rc.constraint_name = r.constraint_name
441
+ AND rc.position = cc.position
442
+ ORDER BY name, to_table, column_name, references_column
443
+ SQL
444
+
445
+ fk_info.map do |row|
446
+ options = {
447
+ column: oracle_downcase(row['column_name']),
448
+ name: oracle_downcase(row['name']),
449
+ primary_key: oracle_downcase(row['references_column'])
450
+ }
451
+ options[:on_delete] = extract_foreign_key_action(row['delete_rule'])
452
+ OracleEnhanced::ForeignKeyDefinition.new(oracle_downcase(table_name), oracle_downcase(row['to_table']), options)
453
+ end
454
+ end
455
+
456
+ def extract_foreign_key_action(specifier) # :nodoc:
457
+ case specifier
458
+ when 'CASCADE'; :cascade
459
+ when 'SET NULL'; :nullify
460
+ end
461
+ end
462
+
463
+ # REFERENTIAL INTEGRITY ====================================
464
+
465
+ def disable_referential_integrity(&block) #:nodoc:
466
+ sql_constraints = <<-SQL
467
+ SELECT constraint_name, owner, table_name
468
+ FROM user_constraints
469
+ WHERE constraint_type = 'R'
470
+ AND status = 'ENABLED'
471
+ SQL
472
+ old_constraints = select_all(sql_constraints)
473
+ begin
474
+ old_constraints.each do |constraint|
475
+ execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}"
476
+ end
477
+ yield
478
+ ensure
479
+ old_constraints.each do |constraint|
480
+ execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}"
481
+ end
482
+ end
483
+ end
484
+
485
+ private
486
+
487
+ def create_alter_table(name)
488
+ OracleEnhanced::AlterTable.new create_table_definition(name, false, {})
489
+ end
490
+
491
+ def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil)
492
+ tablespace_sql = ''
493
+ if tablespace = (tablespace_option || default_tablespace_for(obj_type))
494
+ tablespace_sql << if [:blob, :clob].include?(obj_type.to_sym)
495
+ " LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})"
496
+ else
497
+ " TABLESPACE #{tablespace}"
498
+ end
499
+ end
500
+ tablespace_sql
501
+ end
502
+
503
+ def default_tablespace_for(type)
504
+ (default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil
505
+ end
506
+
507
+
508
+ def column_for(table_name, column_name)
509
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
510
+ raise "No such column: #{table_name}.#{column_name}"
511
+ end
512
+ column
513
+ end
514
+
515
+ def create_sequence_and_trigger(table_name, options)
516
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
517
+ seq_start_value = options[:sequence_start_value] || default_sequence_start_value
518
+ execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
519
+
520
+ create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
521
+ end
522
+
523
+ def create_primary_key_trigger(table_name, options)
524
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
525
+ trigger_name = options[:trigger_name] || default_trigger_name(table_name)
526
+ primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
527
+ execute compress_lines(<<-SQL)
528
+ CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
529
+ BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
530
+ BEGIN
531
+ IF inserting THEN
532
+ IF :new.#{quote_column_name(primary_key)} IS NULL THEN
533
+ SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
534
+ END IF;
535
+ END IF;
536
+ END;
537
+ SQL
538
+ end
539
+
540
+ def default_trigger_name(table_name)
541
+ # truncate table name if necessary to fit in max length of identifier
542
+ "#{table_name.to_s[0,table_name_length-4]}_pkt"
543
+ end
544
+
545
+ end
546
+ end
547
+ end
548
+ end