activerecord-oracle_enhanced-adapter 1.5.6 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/History.md +107 -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/database_statements.rb +257 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced/dirty.rb +40 -0
  13. data/lib/active_record/connection_adapters/{oracle_enhanced_schema_creation.rb → oracle_enhanced/schema_creation.rb} +17 -16
  14. data/lib/active_record/connection_adapters/oracle_enhanced/schema_definitions.rb +95 -0
  15. data/lib/active_record/connection_adapters/{oracle_enhanced_schema_dumper.rb → oracle_enhanced/schema_dumper.rb} +4 -32
  16. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements.rb +546 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced/schema_statements_ext.rb +65 -0
  18. data/lib/active_record/connection_adapters/{oracle_enhanced_structure_dump.rb → oracle_enhanced/structure_dump.rb} +26 -4
  19. data/lib/active_record/connection_adapters/oracle_enhanced/version.rb +1 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +159 -66
  21. data/lib/active_record/oracle_enhanced/type/integer.rb +13 -0
  22. data/lib/active_record/oracle_enhanced/type/raw.rb +13 -0
  23. data/lib/active_record/oracle_enhanced/type/timestamp.rb +11 -0
  24. data/lib/activerecord-oracle_enhanced-adapter.rb +1 -1
  25. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +6 -31
  26. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +1 -1
  27. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +2 -2
  28. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +2 -2
  29. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +63 -63
  30. data/spec/active_record/connection_adapters/oracle_enhanced_database_tasks_spec.rb +1 -1
  31. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +7 -13
  32. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +25 -178
  33. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +14 -5
  34. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +1 -0
  35. data/spec/spec_config.yaml.template +10 -0
  36. data/spec/spec_helper.rb +21 -10
  37. metadata +27 -23
  38. data/lib/active_record/connection_adapters/oracle_enhanced_column_dumper.rb +0 -77
  39. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +0 -350
  40. data/lib/active_record/connection_adapters/oracle_enhanced_database_statements.rb +0 -262
  41. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +0 -45
  42. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +0 -197
  43. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +0 -450
  44. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +0 -258
  45. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +0 -1
  46. /data/lib/active_record/connection_adapters/{oracle_enhanced_cpk.rb → oracle_enhanced/cpk.rb} +0 -0
  47. /data/lib/active_record/connection_adapters/{oracle_enhanced_database_tasks.rb → oracle_enhanced/database_tasks.rb} +0 -0
  48. /data/lib/active_record/connection_adapters/{oracle_enhanced_jdbc_connection.rb → oracle_enhanced/jdbc_connection.rb} +0 -0
  49. /data/lib/active_record/connection_adapters/{oracle_enhanced_oci_connection.rb → oracle_enhanced/oci_connection.rb} +0 -0
  50. /data/lib/active_record/connection_adapters/{oracle_enhanced_procedures.rb → oracle_enhanced/procedures.rb} +0 -0
@@ -0,0 +1,546 @@
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
+ if options[:dependent]
403
+ ActiveSupport::Deprecation.warn "`:dependent` option will be deprecated. Please use `:on_delete` option"
404
+ end
405
+ case options[:dependent]
406
+ when :delete then options[:on_delete] = :cascade
407
+ when :nullify then options[:on_delete] = :nullify
408
+ else
409
+ end
410
+
411
+ super
412
+ end
413
+
414
+ def remove_foreign_key(from_table, options_or_to_table = {})
415
+ super
416
+ end
417
+
418
+ # get table foreign keys for schema dump
419
+ def foreign_keys(table_name) #:nodoc:
420
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
421
+
422
+ fk_info = select_all(<<-SQL, 'Foreign Keys')
423
+ SELECT r.table_name to_table
424
+ ,rc.column_name references_column
425
+ ,cc.column_name
426
+ ,c.constraint_name name
427
+ ,c.delete_rule
428
+ FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc,
429
+ user_constraints#{db_link} r, user_cons_columns#{db_link} rc
430
+ WHERE c.owner = '#{owner}'
431
+ AND c.table_name = '#{desc_table_name}'
432
+ AND c.constraint_type = 'R'
433
+ AND cc.owner = c.owner
434
+ AND cc.constraint_name = c.constraint_name
435
+ AND r.constraint_name = c.r_constraint_name
436
+ AND r.owner = c.owner
437
+ AND rc.owner = r.owner
438
+ AND rc.constraint_name = r.constraint_name
439
+ AND rc.position = cc.position
440
+ ORDER BY name, to_table, column_name, references_column
441
+ SQL
442
+
443
+ fk_info.map do |row|
444
+ options = {
445
+ column: oracle_downcase(row['column_name']),
446
+ name: oracle_downcase(row['name']),
447
+ primary_key: oracle_downcase(row['references_column'])
448
+ }
449
+ options[:on_delete] = extract_foreign_key_action(row['delete_rule'])
450
+ OracleEnhanced::ForeignKeyDefinition.new(oracle_downcase(table_name), oracle_downcase(row['to_table']), options)
451
+ end
452
+ end
453
+
454
+ def extract_foreign_key_action(specifier) # :nodoc:
455
+ case specifier
456
+ when 'CASCADE'; :cascade
457
+ when 'SET NULL'; :nullify
458
+ end
459
+ end
460
+
461
+ # REFERENTIAL INTEGRITY ====================================
462
+
463
+ def disable_referential_integrity(&block) #:nodoc:
464
+ sql_constraints = <<-SQL
465
+ SELECT constraint_name, owner, table_name
466
+ FROM user_constraints
467
+ WHERE constraint_type = 'R'
468
+ AND status = 'ENABLED'
469
+ SQL
470
+ old_constraints = select_all(sql_constraints)
471
+ begin
472
+ old_constraints.each do |constraint|
473
+ execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}"
474
+ end
475
+ yield
476
+ ensure
477
+ old_constraints.each do |constraint|
478
+ execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}"
479
+ end
480
+ end
481
+ end
482
+
483
+ private
484
+
485
+ def create_alter_table(name)
486
+ OracleEnhanced::AlterTable.new create_table_definition(name, false, {})
487
+ end
488
+
489
+ def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil)
490
+ tablespace_sql = ''
491
+ if tablespace = (tablespace_option || default_tablespace_for(obj_type))
492
+ tablespace_sql << if [:blob, :clob].include?(obj_type.to_sym)
493
+ " LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})"
494
+ else
495
+ " TABLESPACE #{tablespace}"
496
+ end
497
+ end
498
+ tablespace_sql
499
+ end
500
+
501
+ def default_tablespace_for(type)
502
+ (default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil
503
+ end
504
+
505
+
506
+ def column_for(table_name, column_name)
507
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
508
+ raise "No such column: #{table_name}.#{column_name}"
509
+ end
510
+ column
511
+ end
512
+
513
+ def create_sequence_and_trigger(table_name, options)
514
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
515
+ seq_start_value = options[:sequence_start_value] || default_sequence_start_value
516
+ execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
517
+
518
+ create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
519
+ end
520
+
521
+ def create_primary_key_trigger(table_name, options)
522
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
523
+ trigger_name = options[:trigger_name] || default_trigger_name(table_name)
524
+ primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
525
+ execute compress_lines(<<-SQL)
526
+ CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
527
+ BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
528
+ BEGIN
529
+ IF inserting THEN
530
+ IF :new.#{quote_column_name(primary_key)} IS NULL THEN
531
+ SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
532
+ END IF;
533
+ END IF;
534
+ END;
535
+ SQL
536
+ end
537
+
538
+ def default_trigger_name(table_name)
539
+ # truncate table name if necessary to fit in max length of identifier
540
+ "#{table_name.to_s[0,table_name_length-4]}_pkt"
541
+ end
542
+
543
+ end
544
+ end
545
+ end
546
+ end
@@ -0,0 +1,65 @@
1
+ require 'digest/sha1'
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module OracleEnhancedSchemaStatementsExt
6
+ # Create primary key trigger (so that you can skip primary key value in INSERT statement).
7
+ # By default trigger name will be "table_name_pkt", you can override the name with
8
+ # :trigger_name option (but it is not recommended to override it as then this trigger will
9
+ # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
10
+ #
11
+ # add_primary_key_trigger :users
12
+ #
13
+ # You can also create primary key trigger using +create_table+ with :primary_key_trigger
14
+ # option:
15
+ #
16
+ # create_table :users, :primary_key_trigger => true do |t|
17
+ # # ...
18
+ # end
19
+ #
20
+ def add_primary_key_trigger(table_name, options={})
21
+ # call the same private method that is used for create_table :primary_key_trigger => true
22
+ create_primary_key_trigger(table_name, options)
23
+ end
24
+
25
+ # Add synonym to existing table or view or sequence. Can be used to create local synonym to
26
+ # remote table in other schema or in other database
27
+ # Examples:
28
+ #
29
+ # add_synonym :posts, "blog.posts"
30
+ # add_synonym :posts_seq, "blog.posts_seq"
31
+ # add_synonym :employees, "hr.employees@dblink", :force => true
32
+ #
33
+ def add_synonym(name, table_name, options = {})
34
+ sql = "CREATE"
35
+ if options[:force] == true
36
+ sql << " OR REPLACE"
37
+ end
38
+ sql << " SYNONYM #{quote_table_name(name)} FOR #{quote_table_name(table_name)}"
39
+ execute sql
40
+ end
41
+
42
+ # Remove existing synonym to table or view or sequence
43
+ # Example:
44
+ #
45
+ # remove_synonym :posts, "blog.posts"
46
+ #
47
+ def remove_synonym(name)
48
+ execute "DROP SYNONYM #{quote_table_name(name)}"
49
+ end
50
+
51
+ # get synonyms for schema dump
52
+ def synonyms #:nodoc:
53
+ select_all("SELECT synonym_name, table_owner, table_name, db_link FROM user_synonyms").collect do |row|
54
+ OracleEnhanced::SynonymDefinition.new(oracle_downcase(row['synonym_name']),
55
+ oracle_downcase(row['table_owner']), oracle_downcase(row['table_name']), oracle_downcase(row['db_link']))
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
62
+
63
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
64
+ include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatementsExt
65
+ end
@@ -134,12 +134,34 @@ module ActiveRecord #:nodoc:
134
134
  join_with_statement_token(fks)
135
135
  end
136
136
 
137
- def dump_schema_information #:nodoc:
138
- sm_table = ActiveRecord::Migrator.schema_migrations_table_name
139
- migrated = select_values("SELECT version FROM #{sm_table} ORDER BY version")
140
- join_with_statement_token(migrated.map{|v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" })
137
+ def foreign_key_definition(to_table, options = {}) #:nodoc:
138
+ columns = Array(options[:column] || options[:columns])
139
+
140
+ if columns.size > 1
141
+ # composite foreign key
142
+ columns_sql = columns.map {|c| quote_column_name(c)}.join(',')
143
+ references = options[:references] || columns
144
+ references_sql = references.map {|c| quote_column_name(c)}.join(',')
145
+ else
146
+ columns_sql = quote_column_name(columns.first || "#{to_table.to_s.singularize}_id")
147
+ references = options[:references] ? options[:references].first : nil
148
+ references_sql = quote_column_name(options[:primary_key] || references || "id")
149
+ end
150
+
151
+ table_name = to_table
152
+
153
+ sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(table_name)}(#{references_sql})"
154
+
155
+ case options[:dependent]
156
+ when :nullify
157
+ sql << " ON DELETE SET NULL"
158
+ when :delete
159
+ sql << " ON DELETE CASCADE"
160
+ end
161
+ sql
141
162
  end
142
163
 
164
+
143
165
  # Extract all stored procedures, packages, synonyms and views.
144
166
  def structure_dump_db_stored_code #:nodoc:
145
167
  structure = []
@@ -0,0 +1 @@
1
+ ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter::VERSION = File.read(File.expand_path('../../../../../VERSION', __FILE__)).chomp