pmacs-activerecord-oracle_enhanced-adapter 1.5.6.1 → 1.6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -1,450 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- require 'digest/sha1'
3
-
4
- module ActiveRecord
5
- module ConnectionAdapters
6
- module OracleEnhancedSchemaStatements
7
- # SCHEMA STATEMENTS ========================================
8
- #
9
- # see: abstract/schema_statements.rb
10
-
11
- # Additional options for +create_table+ method in migration files.
12
- #
13
- # You can specify individual starting value in table creation migration file, e.g.:
14
- #
15
- # create_table :users, :sequence_start_value => 100 do |t|
16
- # # ...
17
- # end
18
- #
19
- # You can also specify other sequence definition additional parameters, e.g.:
20
- #
21
- # create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t|
22
- # # ...
23
- # end
24
- #
25
- # Create primary key trigger (so that you can skip primary key value in INSERT statement).
26
- # By default trigger name will be "table_name_pkt", you can override the name with
27
- # :trigger_name option (but it is not recommended to override it as then this trigger will
28
- # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
29
- # Example:
30
- #
31
- # create_table :users, :primary_key_trigger => true do |t|
32
- # # ...
33
- # end
34
- #
35
- # It is possible to add table and column comments in table creation migration files:
36
- #
37
- # create_table :employees, :comment => “Employees and contractors” do |t|
38
- # t.string :first_name, :comment => “Given name”
39
- # t.string :last_name, :comment => “Surname”
40
- # end
41
-
42
- def create_table(name, options = {})
43
- create_sequence = options[:id] != false
44
- column_comments = {}
45
- temporary = options.delete(:temporary)
46
- additional_options = options
47
- table_definition = create_table_definition name, temporary, additional_options
48
- table_definition.primary_key(options[:primary_key] || Base.get_primary_key(name.to_s.singularize)) unless options[:id] == false
49
-
50
- # store that primary key was defined in create_table block
51
- unless create_sequence
52
- class << table_definition
53
- attr_accessor :create_sequence
54
- def primary_key(*args)
55
- self.create_sequence = true
56
- super(*args)
57
- end
58
- end
59
- end
60
-
61
- # store column comments
62
- class << table_definition
63
- attr_accessor :column_comments
64
- def column(name, type, options = {})
65
- if options[:comment]
66
- self.column_comments ||= {}
67
- self.column_comments[name] = options[:comment]
68
- end
69
- super(name, type, options)
70
- end
71
- end
72
-
73
- yield table_definition if block_given?
74
- create_sequence = create_sequence || table_definition.create_sequence
75
- column_comments = table_definition.column_comments if table_definition.column_comments
76
- tablespace = tablespace_for(:table, options[:tablespace])
77
-
78
- if options[:force] && table_exists?(name)
79
- drop_table(name, options)
80
- end
81
-
82
- execute schema_creation.accept table_definition
83
-
84
- create_sequence_and_trigger(name, options) if create_sequence
85
-
86
- add_table_comment name, options[:comment]
87
- column_comments.each do |column_name, comment|
88
- add_comment name, column_name, comment
89
- end
90
- table_definition.indexes.each_pair { |c,o| add_index name, c, o }
91
-
92
- unless table_definition.foreign_keys.nil?
93
- table_definition.foreign_keys.each do |foreign_key|
94
- add_foreign_key(table_definition.name, foreign_key.to_table, foreign_key.options)
95
- end
96
- end
97
- end
98
-
99
- def create_table_definition(name, temporary, options)
100
- TableDefinition.new native_database_types, name, temporary, options
101
- end
102
-
103
- def rename_table(table_name, new_name) #:nodoc:
104
- if new_name.to_s.length > table_name_length
105
- raise ArgumentError, "New table name '#{new_name}' is too long; the limit is #{table_name_length} characters"
106
- end
107
- if "#{new_name}_seq".to_s.length > sequence_name_length
108
- raise ArgumentError, "New sequence name '#{new_name}_seq' is too long; the limit is #{sequence_name_length} characters"
109
- end
110
- execute "RENAME #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
111
- execute "RENAME #{quote_table_name("#{table_name}_seq")} TO #{quote_table_name("#{new_name}_seq")}" rescue nil
112
-
113
- rename_table_indexes(table_name, new_name)
114
- end
115
-
116
- def drop_table(name, options = {}) #:nodoc:
117
- super(name)
118
- seq_name = options[:sequence_name] || default_sequence_name(name)
119
- execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
120
- rescue ActiveRecord::StatementInvalid => e
121
- raise e unless options[:if_exists]
122
- ensure
123
- clear_table_columns_cache(name)
124
- self.all_schema_indexes = nil
125
- end
126
-
127
- def initialize_schema_migrations_table
128
- sm_table = ActiveRecord::Migrator.schema_migrations_table_name
129
-
130
- unless table_exists?(sm_table)
131
- index_name = "#{Base.table_name_prefix}unique_schema_migrations#{Base.table_name_suffix}"
132
- if index_name.length > index_name_length
133
- truncate_to = index_name_length - index_name.to_s.length - 1
134
- truncated_name = "unique_schema_migrations"[0..truncate_to]
135
- index_name = "#{Base.table_name_prefix}#{truncated_name}#{Base.table_name_suffix}"
136
- end
137
-
138
- create_table(sm_table, :id => false) do |schema_migrations_table|
139
- schema_migrations_table.column :version, :string, :null => false
140
- end
141
- add_index sm_table, :version, :unique => true, :name => index_name
142
-
143
- # Backwards-compatibility: if we find schema_info, assume we've
144
- # migrated up to that point:
145
- si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix
146
- if table_exists?(si_table)
147
- ActiveSupport::Deprecation.warn "Usage of the schema table `#{si_table}` is deprecated. Please switch to using `schema_migrations` table"
148
-
149
- old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i
150
- assume_migrated_upto_version(old_version)
151
- drop_table(si_table)
152
- end
153
- end
154
- end
155
-
156
- # clear cached indexes when adding new index
157
- def add_index(table_name, column_name, options = {}) #:nodoc:
158
- column_names = Array(column_name)
159
- index_name = index_name(table_name, column: column_names)
160
-
161
- if Hash === options # legacy support, since this param was a string
162
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :tablespace, :options, :using)
163
-
164
- index_type = options[:unique] ? "UNIQUE" : ""
165
- index_name = options[:name].to_s if options.key?(:name)
166
- tablespace = tablespace_for(:index, options[:tablespace])
167
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
168
- additional_options = options[:options]
169
- else
170
- if options
171
- message = "Passing a string as third argument of `add_index` is deprecated and will" +
172
- " be removed in Rails 4.1." +
173
- " Use add_index(#{table_name.inspect}, #{column_name.inspect}, unique: true) instead"
174
-
175
- ActiveSupport::Deprecation.warn message
176
- end
177
-
178
- index_type = options
179
- additional_options = nil
180
- max_index_length = allowed_index_name_length
181
- end
182
-
183
- if index_name.to_s.length > max_index_length
184
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
185
- end
186
- if index_name_exists?(table_name, index_name, false)
187
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
188
- end
189
- quoted_column_names = column_names.map { |e| quote_column_name_or_expression(e) }.join(", ")
190
-
191
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace} #{additional_options}"
192
- ensure
193
- self.all_schema_indexes = nil
194
- end
195
-
196
- # Remove the given index from the table.
197
- # Gives warning if index does not exist
198
- def remove_index(table_name, options = {}) #:nodoc:
199
- index_name = index_name(table_name, options)
200
- unless index_name_exists?(table_name, index_name, true)
201
- # sometimes options can be String or Array with column names
202
- options = {} unless options.is_a?(Hash)
203
- if options.has_key? :name
204
- options_without_column = options.dup
205
- options_without_column.delete :column
206
- index_name_without_column = index_name(table_name, options_without_column)
207
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
208
- end
209
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
210
- end
211
- remove_index!(table_name, index_name)
212
- end
213
-
214
- # clear cached indexes when removing index
215
- def remove_index!(table_name, index_name) #:nodoc:
216
- execute "DROP INDEX #{quote_column_name(index_name)}"
217
- ensure
218
- self.all_schema_indexes = nil
219
- end
220
-
221
- # returned shortened index name if default is too large
222
- def index_name(table_name, options) #:nodoc:
223
- default_name = super(table_name, options).to_s
224
- # sometimes options can be String or Array with column names
225
- options = {} unless options.is_a?(Hash)
226
- identifier_max_length = options[:identifier_max_length] || index_name_length
227
- return default_name if default_name.length <= identifier_max_length
228
-
229
- # remove 'index', 'on' and 'and' keywords
230
- shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
231
-
232
- # leave just first three letters from each word
233
- if shortened_name.length > identifier_max_length
234
- shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
235
- end
236
- # generate unique name using hash function
237
- if shortened_name.length > identifier_max_length
238
- shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,identifier_max_length-1]
239
- end
240
- @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
241
- shortened_name
242
- end
243
-
244
- # Verify the existence of an index with a given name.
245
- #
246
- # The default argument is returned if the underlying implementation does not define the indexes method,
247
- # as there's no way to determine the correct answer in that case.
248
- #
249
- # Will always query database and not index cache.
250
- def index_name_exists?(table_name, index_name, default)
251
- (owner, table_name, db_link) = @connection.describe(table_name)
252
- result = select_value(<<-SQL)
253
- SELECT 1 FROM all_indexes#{db_link} i
254
- WHERE i.owner = '#{owner}'
255
- AND i.table_owner = '#{owner}'
256
- AND i.table_name = '#{table_name}'
257
- AND i.index_name = '#{index_name.to_s.upcase}'
258
- SQL
259
- result == 1
260
- end
261
-
262
- def rename_index(table_name, index_name, new_index_name) #:nodoc:
263
- unless index_name_exists?(table_name, index_name, true)
264
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
265
- end
266
- execute "ALTER INDEX #{quote_column_name(index_name)} rename to #{quote_column_name(new_index_name)}"
267
- ensure
268
- self.all_schema_indexes = nil
269
- end
270
-
271
- def add_column(table_name, column_name, type, options = {}) #:nodoc:
272
- if type.to_sym == :virtual
273
- type = options[:type]
274
- end
275
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} "
276
- add_column_sql << type_to_sql(type, options[:limit], options[:precision], options[:scale]) if type
277
-
278
- add_column_options!(add_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
279
-
280
- add_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, table_name, column_name) if type
281
-
282
- execute(add_column_sql)
283
-
284
- create_sequence_and_trigger(table_name, options) if type && type.to_sym == :primary_key
285
- ensure
286
- clear_table_columns_cache(table_name)
287
- end
288
-
289
- def change_column_default(table_name, column_name, default) #:nodoc:
290
- execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
291
- ensure
292
- clear_table_columns_cache(table_name)
293
- end
294
-
295
- def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
296
- column = column_for(table_name, column_name)
297
-
298
- unless null || default.nil?
299
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
300
- end
301
-
302
- change_column table_name, column_name, column.sql_type, :null => null
303
- end
304
-
305
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
306
- column = column_for(table_name, column_name)
307
-
308
- # remove :null option if its value is the same as current column definition
309
- # otherwise Oracle will raise error
310
- if options.has_key?(:null) && options[:null] == column.null
311
- options[:null] = nil
312
- end
313
- if type.to_sym == :virtual
314
- type = options[:type]
315
- end
316
- change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} "
317
- change_column_sql << "#{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" if type
318
-
319
- add_column_options!(change_column_sql, options.merge(:type=>type, :column_name=>column_name, :table_name=>table_name))
320
-
321
- change_column_sql << tablespace_for((type_to_sql(type).downcase.to_sym), nil, options[:table_name], options[:column_name]) if type
322
-
323
- execute(change_column_sql)
324
- ensure
325
- clear_table_columns_cache(table_name)
326
- end
327
-
328
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
329
- execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
330
- self.all_schema_indexes = nil
331
- rename_column_indexes(table_name, column_name, new_column_name)
332
- ensure
333
- clear_table_columns_cache(table_name)
334
- end
335
-
336
- def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
337
- execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
338
- ensure
339
- clear_table_columns_cache(table_name)
340
- self.all_schema_indexes = nil
341
- end
342
-
343
- def add_comment(table_name, column_name, comment) #:nodoc:
344
- return if comment.blank?
345
- execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
346
- end
347
-
348
- def add_table_comment(table_name, comment) #:nodoc:
349
- return if comment.blank?
350
- execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
351
- end
352
-
353
- def table_comment(table_name) #:nodoc:
354
- (owner, table_name, db_link) = @connection.describe(table_name)
355
- select_value <<-SQL
356
- SELECT comments FROM all_tab_comments#{db_link}
357
- WHERE owner = '#{owner}'
358
- AND table_name = '#{table_name}'
359
- SQL
360
- end
361
-
362
- def column_comment(table_name, column_name) #:nodoc:
363
- (owner, table_name, db_link) = @connection.describe(table_name)
364
- select_value <<-SQL
365
- SELECT comments FROM all_col_comments#{db_link}
366
- WHERE owner = '#{owner}'
367
- AND table_name = '#{table_name}'
368
- AND column_name = '#{column_name.upcase}'
369
- SQL
370
- end
371
-
372
- # Maps logical Rails types to Oracle-specific data types.
373
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
374
- # Ignore options for :text and :binary columns
375
- return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s)
376
-
377
- super
378
- end
379
-
380
- def tablespace(table_name)
381
- select_value <<-SQL
382
- SELECT tablespace_name
383
- FROM user_tables
384
- WHERE table_name='#{table_name.to_s.upcase}'
385
- SQL
386
- end
387
-
388
- private
389
-
390
- def tablespace_for(obj_type, tablespace_option, table_name=nil, column_name=nil)
391
- tablespace_sql = ''
392
- if tablespace = (tablespace_option || default_tablespace_for(obj_type))
393
- tablespace_sql << if [:blob, :clob].include?(obj_type.to_sym)
394
- " LOB (#{quote_column_name(column_name)}) STORE AS #{column_name.to_s[0..10]}_#{table_name.to_s[0..14]}_ls (TABLESPACE #{tablespace})"
395
- else
396
- " TABLESPACE #{tablespace}"
397
- end
398
- end
399
- tablespace_sql
400
- end
401
-
402
- def default_tablespace_for(type)
403
- (default_tablespaces[type] || default_tablespaces[native_database_types[type][:name]]) rescue nil
404
- end
405
-
406
-
407
- def column_for(table_name, column_name)
408
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
409
- raise "No such column: #{table_name}.#{column_name}"
410
- end
411
- column
412
- end
413
-
414
- def create_sequence_and_trigger(table_name, options)
415
- seq_name = options[:sequence_name] || default_sequence_name(table_name)
416
- seq_start_value = options[:sequence_start_value] || default_sequence_start_value
417
- execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
418
-
419
- create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
420
- end
421
-
422
- def create_primary_key_trigger(table_name, options)
423
- seq_name = options[:sequence_name] || default_sequence_name(table_name)
424
- trigger_name = options[:trigger_name] || default_trigger_name(table_name)
425
- primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
426
- execute compress_lines(<<-SQL)
427
- CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
428
- BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
429
- BEGIN
430
- IF inserting THEN
431
- IF :new.#{quote_column_name(primary_key)} IS NULL THEN
432
- SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
433
- END IF;
434
- END IF;
435
- END;
436
- SQL
437
- end
438
-
439
- def default_trigger_name(table_name)
440
- # truncate table name if necessary to fit in max length of identifier
441
- "#{table_name.to_s[0,table_name_length-4]}_pkt"
442
- end
443
-
444
- end
445
- end
446
- end
447
-
448
- ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
449
- include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatements
450
- end
@@ -1,267 +0,0 @@
1
- require 'digest/sha1'
2
-
3
- module ActiveRecord
4
- module ConnectionAdapters
5
- module OracleEnhancedSchemaStatementsExt
6
- def supports_foreign_keys? #:nodoc:
7
- true
8
- end
9
-
10
- # Create primary key trigger (so that you can skip primary key value in INSERT statement).
11
- # By default trigger name will be "table_name_pkt", you can override the name with
12
- # :trigger_name option (but it is not recommended to override it as then this trigger will
13
- # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
14
- #
15
- # add_primary_key_trigger :users
16
- #
17
- # You can also create primary key trigger using +create_table+ with :primary_key_trigger
18
- # option:
19
- #
20
- # create_table :users, :primary_key_trigger => true do |t|
21
- # # ...
22
- # end
23
- #
24
- def add_primary_key_trigger(table_name, options={})
25
- # call the same private method that is used for create_table :primary_key_trigger => true
26
- create_primary_key_trigger(table_name, options)
27
- end
28
-
29
- def table_definition_tablespace
30
- # TODO: Support specifying an :index_tablespace option in create_table?
31
- tablespace_sql = ''
32
- if tablespace = default_tablespace_for(:index)
33
- tablespace_sql << " USING INDEX TABLESPACE #{tablespace}"
34
- end
35
- tablespace_sql
36
- end
37
-
38
- # Adds a new foreign key to the +from_table+, referencing the primary key of +to_table+
39
- # (syntax and partial implementation taken from http://github.com/matthuhiggins/foreigner)
40
- #
41
- # The foreign key will be named after the from and to tables unless you pass
42
- # <tt>:name</tt> as an option.
43
- #
44
- # === Examples
45
- # ==== Creating a foreign key
46
- # add_foreign_key(:comments, :posts)
47
- # generates
48
- # ALTER TABLE comments ADD CONSTRAINT
49
- # comments_post_id_fk FOREIGN KEY (post_id) REFERENCES posts (id)
50
- #
51
- # ==== Creating a named foreign key
52
- # add_foreign_key(:comments, :posts, :name => 'comments_belongs_to_posts')
53
- # generates
54
- # ALTER TABLE comments ADD CONSTRAINT
55
- # comments_belongs_to_posts FOREIGN KEY (post_id) REFERENCES posts (id)
56
- #
57
- # ==== Creating a cascading foreign_key on a custom column
58
- # add_foreign_key(:people, :people, :column => 'best_friend_id', :dependent => :nullify)
59
- # generates
60
- # ALTER TABLE people ADD CONSTRAINT
61
- # people_best_friend_id_fk FOREIGN KEY (best_friend_id) REFERENCES people (id)
62
- # ON DELETE SET NULL
63
- #
64
- # ==== Creating a composite foreign key
65
- # add_foreign_key(:comments, :posts, :columns => ['post_id', 'author_id'], :name => 'comments_post_fk')
66
- # generates
67
- # ALTER TABLE comments ADD CONSTRAINT
68
- # comments_post_fk FOREIGN KEY (post_id, author_id) REFERENCES posts (post_id, author_id)
69
- #
70
- # === Supported options
71
- # [:column]
72
- # Specify the column name on the from_table that references the to_table. By default this is guessed
73
- # to be the singular name of the to_table with "_id" suffixed. So a to_table of :posts will use "post_id"
74
- # as the default <tt>:column</tt>.
75
- # [:columns]
76
- # An array of column names when defining composite foreign keys. An alias of <tt>:column</tt> provided for improved readability.
77
- # [:primary_key]
78
- # Specify the column name on the to_table that is referenced by this foreign key. By default this is
79
- # assumed to be "id". Ignored when defining composite foreign keys.
80
- # [:name]
81
- # Specify the name of the foreign key constraint. This defaults to use from_table and foreign key column.
82
- # [:dependent]
83
- # If set to <tt>:delete</tt>, the associated records in from_table are deleted when records in to_table table are deleted.
84
- # If set to <tt>:nullify</tt>, the foreign key column is set to +NULL+.
85
- def add_foreign_key(from_table, to_table, options = {})
86
- columns = options[:column] || options[:columns] || "#{to_table.to_s.singularize}_id"
87
- constraint_name = foreign_key_constraint_name(from_table, columns, options)
88
- sql = "ALTER TABLE #{quote_table_name(from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} "
89
- sql << foreign_key_definition(to_table, options)
90
- execute sql
91
- end
92
-
93
- def foreign_key_definition(to_table, options = {}) #:nodoc:
94
- columns = Array(options[:column] || options[:columns])
95
-
96
- if columns.size > 1
97
- # composite foreign key
98
- columns_sql = columns.map {|c| quote_column_name(c)}.join(',')
99
- references = options[:references] || columns
100
- references_sql = references.map {|c| quote_column_name(c)}.join(',')
101
- else
102
- columns_sql = quote_column_name(columns.first || "#{to_table.to_s.singularize}_id")
103
- references = options[:references] ? options[:references].first : nil
104
- references_sql = quote_column_name(options[:primary_key] || references || "id")
105
- end
106
-
107
- table_name = ActiveRecord::Migrator.proper_table_name(to_table)
108
-
109
- sql = "FOREIGN KEY (#{columns_sql}) REFERENCES #{quote_table_name(table_name)}(#{references_sql})"
110
-
111
- case options[:dependent]
112
- when :nullify
113
- sql << " ON DELETE SET NULL"
114
- when :delete
115
- sql << " ON DELETE CASCADE"
116
- end
117
- sql
118
- end
119
-
120
- # Remove the given foreign key from the table.
121
- #
122
- # ===== Examples
123
- # ====== Remove the suppliers_company_id_fk in the suppliers table.
124
- # remove_foreign_key :suppliers, :companies
125
- # ====== Remove the foreign key named accounts_branch_id_fk in the accounts table.
126
- # remove_foreign_key :accounts, :column => :branch_id
127
- # ====== Remove the foreign key named party_foreign_key in the accounts table.
128
- # remove_foreign_key :accounts, :name => :party_foreign_key
129
- def remove_foreign_key(from_table, options)
130
- if Hash === options
131
- constraint_name = foreign_key_constraint_name(from_table, options[:column], options)
132
- else
133
- constraint_name = foreign_key_constraint_name(from_table, "#{options.to_s.singularize}_id")
134
- end
135
- execute "ALTER TABLE #{quote_table_name(from_table)} DROP CONSTRAINT #{quote_column_name(constraint_name)}"
136
- end
137
-
138
- private
139
-
140
- def foreign_key_constraint_name(table_name, columns, options = {})
141
- columns = Array(columns)
142
- constraint_name = original_name = options[:name] || "#{table_name}_#{columns.join('_')}_fk"
143
-
144
- return constraint_name if constraint_name.length <= OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
145
-
146
- # leave just first three letters from each word
147
- constraint_name = constraint_name.split('_').map{|w| w[0,3]}.join('_')
148
- # generate unique name using hash function
149
- if constraint_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
150
- constraint_name = 'c'+Digest::SHA1.hexdigest(original_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1]
151
- end
152
- @logger.warn "#{adapter_name} shortened foreign key constraint name #{original_name} to #{constraint_name}" if @logger
153
- constraint_name
154
- end
155
-
156
-
157
- public
158
-
159
- # get table foreign keys for schema dump
160
- def foreign_keys(table_name) #:nodoc:
161
- (owner, desc_table_name, db_link) = @connection.describe(table_name)
162
-
163
- fk_info = select_all(<<-SQL, 'Foreign Keys')
164
- SELECT r.table_name to_table
165
- ,rc.column_name references_column
166
- ,cc.column_name
167
- ,c.constraint_name name
168
- ,c.delete_rule
169
- FROM user_constraints#{db_link} c, user_cons_columns#{db_link} cc,
170
- user_constraints#{db_link} r, user_cons_columns#{db_link} rc
171
- WHERE c.owner = '#{owner}'
172
- AND c.table_name = '#{desc_table_name}'
173
- AND c.constraint_type = 'R'
174
- AND cc.owner = c.owner
175
- AND cc.constraint_name = c.constraint_name
176
- AND r.constraint_name = c.r_constraint_name
177
- AND r.owner = c.owner
178
- AND rc.owner = r.owner
179
- AND rc.constraint_name = r.constraint_name
180
- AND rc.position = cc.position
181
- ORDER BY name, to_table, column_name, references_column
182
- SQL
183
-
184
- fks = {}
185
-
186
- fk_info.map do |row|
187
- name = oracle_downcase(row['name'])
188
- fks[name] ||= { :columns => [], :to_table => oracle_downcase(row['to_table']), :references => [] }
189
- fks[name][:columns] << oracle_downcase(row['column_name'])
190
- fks[name][:references] << oracle_downcase(row['references_column'])
191
- case row['delete_rule']
192
- when 'CASCADE'
193
- fks[name][:dependent] = :delete
194
- when 'SET NULL'
195
- fks[name][:dependent] = :nullify
196
- end
197
- end
198
-
199
- fks.map do |k, v|
200
- options = {:name => k, :columns => v[:columns], :references => v[:references], :dependent => v[:dependent]}
201
- OracleEnhancedForeignKeyDefinition.new(table_name, v[:to_table], options)
202
- end
203
- end
204
-
205
- # REFERENTIAL INTEGRITY ====================================
206
-
207
- def disable_referential_integrity(&block) #:nodoc:
208
- sql_constraints = <<-SQL
209
- SELECT constraint_name, owner, table_name
210
- FROM user_constraints
211
- WHERE constraint_type = 'R'
212
- AND status = 'ENABLED'
213
- SQL
214
- old_constraints = select_all(sql_constraints)
215
- begin
216
- old_constraints.each do |constraint|
217
- execute "ALTER TABLE #{constraint["table_name"]} DISABLE CONSTRAINT #{constraint["constraint_name"]}"
218
- end
219
- yield
220
- ensure
221
- old_constraints.each do |constraint|
222
- execute "ALTER TABLE #{constraint["table_name"]} ENABLE CONSTRAINT #{constraint["constraint_name"]}"
223
- end
224
- end
225
- end
226
-
227
- # Add synonym to existing table or view or sequence. Can be used to create local synonym to
228
- # remote table in other schema or in other database
229
- # Examples:
230
- #
231
- # add_synonym :posts, "blog.posts"
232
- # add_synonym :posts_seq, "blog.posts_seq"
233
- # add_synonym :employees, "hr.employees@dblink", :force => true
234
- #
235
- def add_synonym(name, table_name, options = {})
236
- sql = "CREATE"
237
- if options[:force] == true
238
- sql << " OR REPLACE"
239
- end
240
- sql << " SYNONYM #{quote_table_name(name)} FOR #{quote_table_name(table_name)}"
241
- execute sql
242
- end
243
-
244
- # Remove existing synonym to table or view or sequence
245
- # Example:
246
- #
247
- # remove_synonym :posts, "blog.posts"
248
- #
249
- def remove_synonym(name)
250
- execute "DROP SYNONYM #{quote_table_name(name)}"
251
- end
252
-
253
- # get synonyms for schema dump
254
- def synonyms #:nodoc:
255
- select_all("SELECT synonym_name, table_owner, table_name, db_link FROM all_synonyms WHERE owner = SYS_CONTEXT('userenv', 'current_schema')").collect do |row|
256
- OracleEnhancedSynonymDefinition.new(oracle_downcase(row['synonym_name']),
257
- oracle_downcase(row['table_owner']), oracle_downcase(row['table_name']), oracle_downcase(row['db_link']))
258
- end
259
- end
260
-
261
- end
262
- end
263
- end
264
-
265
- ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
266
- include ActiveRecord::ConnectionAdapters::OracleEnhancedSchemaStatementsExt
267
- end