activerecord-oracle_enhanced-adapter 1.5.6 → 1.6.0

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