c3-activerecord-oracle_enhanced-adapter 1.2.4

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 (38) hide show
  1. data/History.txt +182 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +32 -0
  4. data/README.rdoc +77 -0
  5. data/Rakefile +49 -0
  6. data/VERSION +1 -0
  7. data/activerecord-oracle_enhanced-adapter.gemspec +79 -0
  8. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced.rake +51 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1664 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +121 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +393 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +389 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +163 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +168 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +213 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  24. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +477 -0
  25. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_structure_dumper_spec.rb +267 -0
  26. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +206 -0
  27. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
  28. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +107 -0
  29. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +984 -0
  30. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +67 -0
  31. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +93 -0
  32. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +370 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +218 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +784 -0
  36. data/spec/spec.opts +6 -0
  37. data/spec/spec_helper.rb +114 -0
  38. metadata +137 -0
@@ -0,0 +1,5 @@
1
+ class ActiveRecord::ConnectionAdapters::OracleAdapter < ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter #:nodoc:
2
+ def adapter_name
3
+ 'Oracle'
4
+ end
5
+ end
@@ -0,0 +1,51 @@
1
+ # RSI: implementation idea taken from JDBC adapter
2
+ def redefine_task(*args, &block)
3
+ task_name = Hash === args.first ? args.first.keys[0] : args.first
4
+ existing_task = Rake.application.lookup task_name
5
+ if existing_task
6
+ class << existing_task; public :instance_variable_set; end
7
+ existing_task.instance_variable_set "@prerequisites", FileList[]
8
+ existing_task.instance_variable_set "@actions", []
9
+ end
10
+ task(*args, &block)
11
+ end
12
+
13
+ namespace :db do
14
+
15
+ namespace :structure do
16
+ redefine_task :dump => :environment do
17
+ abcs = ActiveRecord::Base.configurations
18
+ ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
19
+ File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
20
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_fk_constraints }
21
+ if ActiveRecord::Base.connection.supports_migrations?
22
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
23
+ end
24
+ if abcs[RAILS_ENV]['structure_dump'] == "db_stored_code"
25
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_db_stored_code }
26
+ end
27
+
28
+ end
29
+ end
30
+
31
+ namespace :test do
32
+ redefine_task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
33
+ abcs = ActiveRecord::Base.configurations
34
+ ActiveRecord::Base.establish_connection(:test)
35
+ IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |ddl|
36
+ ddl.chop! if ddl.last == ";"
37
+ ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
38
+ end
39
+ end
40
+
41
+ redefine_task :purge => :environment do
42
+ abcs = ActiveRecord::Base.configurations
43
+ ActiveRecord::Base.establish_connection(:test)
44
+ ActiveRecord::Base.connection.full_drop.split("\n\n").each do |ddl|
45
+ ddl.chop! if ddl.last == ";"
46
+ ActiveRecord::Base.connection.execute(ddl) unless ddl.blank?
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,1664 @@
1
+ # -*- coding: utf-8 -*-
2
+ # oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g, 11g
3
+ #
4
+ # Authors or original oracle_adapter: Graham Jenkins, Michael Schoen
5
+ #
6
+ # Current maintainer: Raimonds Simanovskis (http://blog.rayapps.com)
7
+ #
8
+ #########################################################################
9
+ #
10
+ # See History.txt for changes added to original oracle_adapter.rb
11
+ #
12
+ #########################################################################
13
+ #
14
+ # From original oracle_adapter.rb:
15
+ #
16
+ # Implementation notes:
17
+ # 1. Redefines (safely) a method in ActiveRecord to make it possible to
18
+ # implement an autonumbering solution for Oracle.
19
+ # 2. The OCI8 driver is patched to properly handle values for LONG and
20
+ # TIMESTAMP columns. The driver-author has indicated that a future
21
+ # release of the driver will obviate this patch.
22
+ # 3. LOB support is implemented through an after_save callback.
23
+ # 4. Oracle does not offer native LIMIT and OFFSET options; this
24
+ # functionality is mimiced through the use of nested selects.
25
+ # See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
26
+ #
27
+ # Do what you want with this code, at your own peril, but if any
28
+ # significant portion of my code remains then please acknowledge my
29
+ # contribution.
30
+ # portions Copyright 2005 Graham Jenkins
31
+
32
+ require 'active_record/connection_adapters/abstract_adapter'
33
+
34
+ require 'active_record/connection_adapters/oracle_enhanced_connection'
35
+
36
+ require 'digest/sha1'
37
+
38
+ module ActiveRecord
39
+ class Base
40
+ # Establishes a connection to the database that's used by all Active Record objects.
41
+ def self.oracle_enhanced_connection(config) #:nodoc:
42
+ if config[:emulate_oracle_adapter] == true
43
+ # allows the enhanced adapter to look like the OracleAdapter. Useful to pick up
44
+ # conditionals in the rails activerecord test suite
45
+ require 'active_record/connection_adapters/emulation/oracle_adapter'
46
+ ConnectionAdapters::OracleAdapter.new(
47
+ ConnectionAdapters::OracleEnhancedConnection.create(config), logger)
48
+ else
49
+ ConnectionAdapters::OracleEnhancedAdapter.new(
50
+ ConnectionAdapters::OracleEnhancedConnection.create(config), logger)
51
+ end
52
+ end
53
+
54
+ # Specify table columns which should be ignored by ActiveRecord, e.g.:
55
+ #
56
+ # ignore_table_columns :attribute1, :attribute2
57
+ def self.ignore_table_columns(*args)
58
+ connection.ignore_table_columns(table_name,*args)
59
+ end
60
+
61
+ # Specify which table columns should be typecasted to Date (without time), e.g.:
62
+ #
63
+ # set_date_columns :created_on, :updated_on
64
+ def self.set_date_columns(*args)
65
+ connection.set_type_for_columns(table_name,:date,*args)
66
+ end
67
+
68
+ # Specify which table columns should be typecasted to Time (or DateTime), e.g.:
69
+ #
70
+ # set_datetime_columns :created_date, :updated_date
71
+ def self.set_datetime_columns(*args)
72
+ connection.set_type_for_columns(table_name,:datetime,*args)
73
+ end
74
+
75
+ # Specify which table columns should be typecasted to boolean values +true+ or +false+, e.g.:
76
+ #
77
+ # set_boolean_columns :is_valid, :is_completed
78
+ def self.set_boolean_columns(*args)
79
+ connection.set_type_for_columns(table_name,:boolean,*args)
80
+ end
81
+
82
+ # Specify which table columns should be typecasted to integer values.
83
+ # Might be useful to force NUMBER(1) column to be integer and not boolean, or force NUMBER column without
84
+ # scale to be retrieved as integer and not decimal. Example:
85
+ #
86
+ # set_integer_columns :version_number, :object_identifier
87
+ def self.set_integer_columns(*args)
88
+ connection.set_type_for_columns(table_name,:integer,*args)
89
+ end
90
+
91
+ # Specify which table columns should be typecasted to string values.
92
+ # Might be useful to specify that columns should be string even if its name matches boolean column criteria.
93
+ #
94
+ # set_integer_columns :active_flag
95
+ def self.set_string_columns(*args)
96
+ connection.set_type_for_columns(table_name,:string,*args)
97
+ end
98
+
99
+ # After setting large objects to empty, select the OCI8::LOB
100
+ # and write back the data.
101
+ after_save :enhanced_write_lobs
102
+ def enhanced_write_lobs #:nodoc:
103
+ if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
104
+ !(self.class.custom_create_method || self.class.custom_update_method)
105
+ connection.write_lobs(self.class.table_name, self.class, attributes)
106
+ end
107
+ end
108
+ private :enhanced_write_lobs
109
+
110
+ class << self
111
+ # patch ORDER BY to work with LOBs
112
+ def add_order_with_lobs!(sql, order, scope = :auto)
113
+ if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter)
114
+ order = connection.lob_order_by_expression(self, order) if order
115
+
116
+ orig_scope = scope
117
+ scope = scope(:find) if :auto == scope
118
+ if scope
119
+ new_scope_order = connection.lob_order_by_expression(self, scope[:order])
120
+ if new_scope_order != scope[:order]
121
+ scope = scope.merge(:order => new_scope_order)
122
+ else
123
+ scope = orig_scope
124
+ end
125
+ end
126
+ end
127
+ add_order_without_lobs!(sql, order, scope = :auto)
128
+ end
129
+ private :add_order_with_lobs!
130
+ #:stopdoc:
131
+ alias_method :add_order_without_lobs!, :add_order!
132
+ alias_method :add_order!, :add_order_with_lobs!
133
+ #:startdoc:
134
+ end
135
+
136
+ # Get table comment from schema definition.
137
+ def self.table_comment
138
+ connection.table_comment(self.table_name)
139
+ end
140
+ end
141
+
142
+
143
+ module ConnectionAdapters #:nodoc:
144
+ class OracleEnhancedColumn < Column
145
+
146
+ attr_reader :table_name, :forced_column_type #:nodoc:
147
+
148
+ def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil) #:nodoc:
149
+ @table_name = table_name
150
+ @forced_column_type = forced_column_type
151
+ super(name, default, sql_type, null)
152
+ end
153
+
154
+ def type_cast(value) #:nodoc:
155
+ return guess_date_or_time(value) if type == :datetime && OracleEnhancedAdapter.emulate_dates
156
+ super
157
+ end
158
+
159
+ # convert something to a boolean
160
+ # added y as boolean value
161
+ def self.value_to_boolean(value) #:nodoc:
162
+ if value == true || value == false
163
+ value
164
+ elsif value.is_a?(String) && value.blank?
165
+ nil
166
+ else
167
+ %w(true t 1 y +).include?(value.to_s.downcase)
168
+ end
169
+ end
170
+
171
+ # convert Time or DateTime value to Date for :date columns
172
+ def self.string_to_date(string) #:nodoc:
173
+ return string.to_date if string.is_a?(Time) || string.is_a?(DateTime)
174
+ super
175
+ end
176
+
177
+ # convert Date value to Time for :datetime columns
178
+ def self.string_to_time(string) #:nodoc:
179
+ return string.to_time if string.is_a?(Date) && !OracleEnhancedAdapter.emulate_dates
180
+ super
181
+ end
182
+
183
+ # Get column comment from schema definition.
184
+ # Will work only if using default ActiveRecord connection.
185
+ def comment
186
+ ActiveRecord::Base.connection.column_comment(@table_name, name)
187
+ end
188
+
189
+ private
190
+ def simplified_type(field_type)
191
+ forced_column_type ||
192
+ case field_type
193
+ when /decimal|numeric|number/i
194
+ return :boolean if OracleEnhancedAdapter.emulate_booleans && field_type == 'NUMBER(1)'
195
+ return :integer if extract_scale(field_type) == 0
196
+ # if column name is ID or ends with _ID
197
+ return :integer if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name)
198
+ :decimal
199
+ when /char/i
200
+ return :boolean if OracleEnhancedAdapter.emulate_booleans_from_strings &&
201
+ OracleEnhancedAdapter.is_boolean_column?(name, field_type, table_name)
202
+ :string
203
+ when /date/i
204
+ forced_column_type ||
205
+ (:date if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name)) ||
206
+ :datetime
207
+ when /timestamp/i then :timestamp
208
+ when /time/i then :datetime
209
+ else super
210
+ end
211
+ end
212
+
213
+ def guess_date_or_time(value)
214
+ value.respond_to?(:hour) && (value.hour == 0 and value.min == 0 and value.sec == 0) ?
215
+ Date.new(value.year, value.month, value.day) : value
216
+ end
217
+
218
+ class << self
219
+ protected
220
+
221
+ def fallback_string_to_date(string) #:nodoc:
222
+ if OracleEnhancedAdapter.string_to_date_format || OracleEnhancedAdapter.string_to_time_format
223
+ return (string_to_date_or_time_using_format(string).to_date rescue super)
224
+ end
225
+ super
226
+ end
227
+
228
+ def fallback_string_to_time(string) #:nodoc:
229
+ if OracleEnhancedAdapter.string_to_time_format || OracleEnhancedAdapter.string_to_date_format
230
+ return (string_to_date_or_time_using_format(string).to_time rescue super)
231
+ end
232
+ super
233
+ end
234
+
235
+ def string_to_date_or_time_using_format(string) #:nodoc:
236
+ if OracleEnhancedAdapter.string_to_time_format && dt=Date._strptime(string, OracleEnhancedAdapter.string_to_time_format)
237
+ return Time.mktime(*dt.values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :wday))
238
+ end
239
+ DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format)
240
+ end
241
+
242
+ end
243
+ end
244
+
245
+
246
+ # Oracle enhanced adapter will work with both
247
+ # Ruby 1.8/1.9 ruby-oci8 gem (which provides interface to Oracle OCI client)
248
+ # or with JRuby and Oracle JDBC driver.
249
+ #
250
+ # It should work with Oracle 9i, 10g and 11g databases.
251
+ # Limited set of functionality should work on Oracle 8i as well but several features
252
+ # rely on newer functionality in Oracle database.
253
+ #
254
+ # Usage notes:
255
+ # * Key generation assumes a "${table_name}_seq" sequence is available
256
+ # for all tables; the sequence name can be changed using
257
+ # ActiveRecord::Base.set_sequence_name. When using Migrations, these
258
+ # sequences are created automatically.
259
+ # Use set_sequence_name :autogenerated with legacy tables that have
260
+ # triggers that populate primary keys automatically.
261
+ # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
262
+ # Consequently some hacks are employed to map data back to Date or Time
263
+ # in Ruby. Timezones and sub-second precision on timestamps are
264
+ # not supported.
265
+ # * Default values that are functions (such as "SYSDATE") are not
266
+ # supported. This is a restriction of the way ActiveRecord supports
267
+ # default values.
268
+ #
269
+ # Required parameters:
270
+ #
271
+ # * <tt>:username</tt>
272
+ # * <tt>:password</tt>
273
+ # * <tt>:database</tt> - either TNS alias or connection string for OCI client or database name in JDBC connection string
274
+ #
275
+ # Optional parameters:
276
+ #
277
+ # * <tt>:host</tt> - host name for JDBC connection, defaults to "localhost"
278
+ # * <tt>:port</tt> - port number for JDBC connection, defaults to 1521
279
+ # * <tt>:privilege</tt> - set "SYSDBA" if you want to connect with this privilege
280
+ # * <tt>:allow_concurrency</tt> - set to "true" if non-blocking mode should be enabled (just for OCI client)
281
+ # * <tt>:prefetch_rows</tt> - how many rows should be fetched at one time to increase performance, defaults to 100
282
+ # * <tt>:cursor_sharing</tt> - cursor sharing mode to minimize amount of unique statements, defaults to "force"
283
+ # * <tt>:nls_length_semantics</tt> - semantics of size of VARCHAR2 and CHAR columns, defaults to "CHAR"
284
+ # (meaning that size specifies number of characters and not bytes)
285
+ # * <tt>:time_zone</tt> - database session time zone
286
+ # (it is recommended to set it using ENV['TZ'] which will be then also used for database session time zone)
287
+ class OracleEnhancedAdapter < AbstractAdapter
288
+
289
+ ##
290
+ # :singleton-method:
291
+ # By default, the OracleEnhancedAdapter will consider all columns of type <tt>NUMBER(1)</tt>
292
+ # as boolean. If you wish to disable this emulation you can add the following line
293
+ # to your initializer file:
294
+ #
295
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = false
296
+ cattr_accessor :emulate_booleans
297
+ self.emulate_booleans = true
298
+
299
+ ##
300
+ # :singleton-method:
301
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
302
+ # to Time or DateTime (if value is out of Time value range) value.
303
+ # If you wish that DATE values with hour, minutes and seconds equal to 0 are typecasted
304
+ # to Date then you can add the following line to your initializer file:
305
+ #
306
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates = true
307
+ #
308
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
309
+ # that Date columns are explicily defined with +set_date_columns+ method.
310
+ cattr_accessor :emulate_dates
311
+ self.emulate_dates = false
312
+
313
+ ##
314
+ # :singleton-method:
315
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
316
+ # to Time or DateTime (if value is out of Time value range) value.
317
+ # If you wish that DATE columns with "date" in their name (e.g. "creation_date") are typecasted
318
+ # to Date then you can add the following line to your initializer file:
319
+ #
320
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
321
+ #
322
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
323
+ # that Date columns are explicily defined with +set_date_columns+ method.
324
+ cattr_accessor :emulate_dates_by_column_name
325
+ self.emulate_dates_by_column_name = false
326
+
327
+ # Check column name to identify if it is Date (and not Time) column.
328
+ # Is used if +emulate_dates_by_column_name+ option is set to +true+.
329
+ # Override this method definition in initializer file if different Date column recognition is needed.
330
+ def self.is_date_column?(name, table_name = nil)
331
+ name =~ /(^|_)date(_|$)/i
332
+ end
333
+
334
+ # instance method uses at first check if column type defined at class level
335
+ def is_date_column?(name, table_name = nil) #:nodoc:
336
+ case get_type_for_column(table_name, name)
337
+ when nil
338
+ self.class.is_date_column?(name, table_name)
339
+ when :date
340
+ true
341
+ else
342
+ false
343
+ end
344
+ end
345
+
346
+ ##
347
+ # :singleton-method:
348
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>NUMBER</tt>
349
+ # (without precision or scale) to Float or BigDecimal value.
350
+ # If you wish that NUMBER columns with name "id" or that end with "_id" are typecasted
351
+ # to Integer then you can add the following line to your initializer file:
352
+ #
353
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true
354
+ cattr_accessor :emulate_integers_by_column_name
355
+ self.emulate_integers_by_column_name = false
356
+
357
+ # Check column name to identify if it is Integer (and not Float or BigDecimal) column.
358
+ # Is used if +emulate_integers_by_column_name+ option is set to +true+.
359
+ # Override this method definition in initializer file if different Integer column recognition is needed.
360
+ def self.is_integer_column?(name, table_name = nil)
361
+ name =~ /(^|_)id$/i
362
+ end
363
+
364
+ ##
365
+ # :singleton-method:
366
+ # If you wish that CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
367
+ # are typecasted to booleans then you can add the following line to your initializer file:
368
+ #
369
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans_from_strings = true
370
+ cattr_accessor :emulate_booleans_from_strings
371
+ self.emulate_booleans_from_strings = false
372
+
373
+ # Check column name to identify if it is boolean (and not String) column.
374
+ # Is used if +emulate_booleans_from_strings+ option is set to +true+.
375
+ # Override this method definition in initializer file if different boolean column recognition is needed.
376
+ def self.is_boolean_column?(name, field_type, table_name = nil)
377
+ return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type)
378
+ field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i)
379
+ end
380
+
381
+ # How boolean value should be quoted to String.
382
+ # Used if +emulate_booleans_from_strings+ option is set to +true+.
383
+ def self.boolean_to_string(bool)
384
+ bool ? "Y" : "N"
385
+ end
386
+
387
+ ##
388
+ # :singleton-method:
389
+ # Specify non-default date format that should be used when assigning string values to :date columns, e.g.:
390
+ #
391
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_date_format = “%d.%m.%Y”
392
+ cattr_accessor :string_to_date_format
393
+ self.string_to_date_format = nil
394
+
395
+ ##
396
+ # :singleton-method:
397
+ # Specify non-default time format that should be used when assigning string values to :datetime columns, e.g.:
398
+ #
399
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_time_format = “%d.%m.%Y %H:%M:%S”
400
+ cattr_accessor :string_to_time_format
401
+ self.string_to_time_format = nil
402
+
403
+ def initialize(connection, logger = nil) #:nodoc:
404
+ super
405
+ @quoted_column_names, @quoted_table_names = {}, {}
406
+ end
407
+
408
+ ADAPTER_NAME = 'OracleEnhanced'.freeze
409
+
410
+ def adapter_name #:nodoc:
411
+ ADAPTER_NAME
412
+ end
413
+
414
+ def supports_migrations? #:nodoc:
415
+ true
416
+ end
417
+
418
+ def supports_savepoints? #:nodoc:
419
+ true
420
+ end
421
+
422
+ #:stopdoc:
423
+ NATIVE_DATABASE_TYPES = {
424
+ :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
425
+ :string => { :name => "VARCHAR2", :limit => 255 },
426
+ :text => { :name => "CLOB" },
427
+ :integer => { :name => "NUMBER", :limit => 38 },
428
+ :float => { :name => "NUMBER" },
429
+ :decimal => { :name => "DECIMAL" },
430
+ :datetime => { :name => "DATE" },
431
+ # changed to native TIMESTAMP type
432
+ # :timestamp => { :name => "DATE" },
433
+ :timestamp => { :name => "TIMESTAMP" },
434
+ :time => { :name => "DATE" },
435
+ :date => { :name => "DATE" },
436
+ :binary => { :name => "BLOB" },
437
+ :boolean => { :name => "NUMBER", :limit => 1 }
438
+ }
439
+ # if emulate_booleans_from_strings then store booleans in VARCHAR2
440
+ NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
441
+ :boolean => { :name => "VARCHAR2", :limit => 1 }
442
+ )
443
+ #:startdoc:
444
+
445
+ def native_database_types #:nodoc:
446
+ emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
447
+ end
448
+
449
+ # maximum length of Oracle identifiers
450
+ IDENTIFIER_MAX_LENGTH = 30
451
+
452
+ def table_alias_length #:nodoc:
453
+ IDENTIFIER_MAX_LENGTH
454
+ end
455
+
456
+ # QUOTING ==================================================
457
+ #
458
+ # see: abstract/quoting.rb
459
+
460
+ def quote_column_name(name) #:nodoc:
461
+ # camelCase column names need to be quoted; not that anyone using Oracle
462
+ # would really do this, but handling this case means we pass the test...
463
+ @quoted_column_names[name] = name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : quote_oracle_reserved_words(name)
464
+ end
465
+
466
+ # unescaped table name should start with letter and
467
+ # contain letters, digits, _, $ or #
468
+ # can be prefixed with schema name
469
+ # CamelCase table names should be quoted
470
+ def self.valid_table_name?(name) #:nodoc:
471
+ name = name.to_s
472
+ name =~ /\A([A-Za-z_0-9]+\.)?[a-z][a-z_0-9\$#]*(@[A-Za-z_0-9\.]+)?\Z/ ||
473
+ name =~ /\A([A-Za-z_0-9]+\.)?[A-Z][A-Z_0-9\$#]*(@[A-Za-z_0-9\.]+)?\Z/ ? true : false
474
+ end
475
+
476
+ def quote_table_name(name) #:nodoc:
477
+ # abstract_adapter calls quote_column_name from quote_table_name, so prevent that
478
+ @quoted_table_names[name] ||= if self.class.valid_table_name?(name)
479
+ name
480
+ else
481
+ "\"#{name}\""
482
+ end
483
+ end
484
+
485
+ def quote_string(s) #:nodoc:
486
+ s.gsub(/'/, "''")
487
+ end
488
+
489
+ def quote(value, column = nil) #:nodoc:
490
+ if value && column
491
+ case column.type
492
+ when :text, :binary
493
+ %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
494
+ # NLS_DATE_FORMAT independent TIMESTAMP support
495
+ when :timestamp
496
+ quote_timestamp_with_to_timestamp(value)
497
+ # NLS_DATE_FORMAT independent DATE support
498
+ when :date, :time, :datetime
499
+ quote_date_with_to_date(value)
500
+ else
501
+ super
502
+ end
503
+ elsif value.acts_like?(:date)
504
+ quote_date_with_to_date(value)
505
+ elsif value.acts_like?(:time)
506
+ value.to_i == value.to_f ? quote_date_with_to_date(value) : quote_timestamp_with_to_timestamp(value)
507
+ else
508
+ super
509
+ end
510
+ end
511
+
512
+ def quoted_true #:nodoc:
513
+ return "'#{self.class.boolean_to_string(true)}'" if emulate_booleans_from_strings
514
+ "1"
515
+ end
516
+
517
+ def quoted_false #:nodoc:
518
+ return "'#{self.class.boolean_to_string(false)}'" if emulate_booleans_from_strings
519
+ "0"
520
+ end
521
+
522
+ def quote_date_with_to_date(value) #:nodoc:
523
+ # should support that composite_primary_keys gem will pass date as string
524
+ value = quoted_date(value) if value.acts_like?(:date) || value.acts_like?(:time)
525
+ "TO_DATE('#{value}','YYYY-MM-DD HH24:MI:SS')"
526
+ end
527
+
528
+ def quote_timestamp_with_to_timestamp(value) #:nodoc:
529
+ # add up to 9 digits of fractional seconds to inserted time
530
+ value = "#{quoted_date(value)}:#{("%.6f"%value.to_f).split('.')[1]}" if value.acts_like?(:time)
531
+ "TO_TIMESTAMP('#{value}','YYYY-MM-DD HH24:MI:SS:FF6')"
532
+ end
533
+
534
+ # CONNECTION MANAGEMENT ====================================
535
+ #
536
+
537
+ # If SQL statement fails due to lost connection then reconnect
538
+ # and retry SQL statement if autocommit mode is enabled.
539
+ # By default this functionality is disabled.
540
+ attr_reader :auto_retry #:nodoc:
541
+ @auto_retry = false
542
+
543
+ def auto_retry=(value) #:nodoc:
544
+ @auto_retry = value
545
+ @connection.auto_retry = value if @connection
546
+ end
547
+
548
+ # return raw OCI8 or JDBC connection
549
+ def raw_connection
550
+ @connection.raw_connection
551
+ end
552
+
553
+ # Returns true if the connection is active.
554
+ def active? #:nodoc:
555
+ # Pings the connection to check if it's still good. Note that an
556
+ # #active? method is also available, but that simply returns the
557
+ # last known state, which isn't good enough if the connection has
558
+ # gone stale since the last use.
559
+ @connection.ping
560
+ rescue OracleEnhancedConnectionException
561
+ false
562
+ end
563
+
564
+ # Reconnects to the database.
565
+ def reconnect! #:nodoc:
566
+ @connection.reset!
567
+ rescue OracleEnhancedConnectionException => e
568
+ @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger
569
+ end
570
+
571
+ # Disconnects from the database.
572
+ def disconnect! #:nodoc:
573
+ @connection.logoff rescue nil
574
+ end
575
+
576
+ # DATABASE STATEMENTS ======================================
577
+ #
578
+ # see: abstract/database_statements.rb
579
+
580
+ # Executes a SQL statement
581
+ def execute(sql, name = nil)
582
+ # hack to pass additional "with_returning" option without changing argument list
583
+ log(sql, name) { sql.instance_variable_get(:@with_returning) ? @connection.exec_with_returning(sql) : @connection.exec(sql) }
584
+ end
585
+
586
+ # Returns an array of arrays containing the field values.
587
+ # Order is the same as that returned by #columns.
588
+ def select_rows(sql, name = nil)
589
+ # last parameter indicates to return also column list
590
+ result, columns = select(sql, name, true)
591
+ result.map{ |v| columns.map{|c| v[c]} }
592
+ end
593
+
594
+ # Executes an INSERT statement and returns the new record's ID
595
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
596
+ # if primary key value is already prefetched from sequence
597
+ # or if there is no primary key
598
+ if id_value || pk.nil?
599
+ execute(sql, name)
600
+ return id_value
601
+ end
602
+
603
+ sql_with_returning = sql.dup << @connection.returning_clause(quote_column_name(pk))
604
+ # hack to pass additional "with_returning" option without changing argument list
605
+ sql_with_returning.instance_variable_set(:@with_returning, true)
606
+ clear_query_cache
607
+ execute(sql_with_returning, name)
608
+ end
609
+ protected :insert_sql
610
+
611
+ # use in set_sequence_name to avoid fetching primary key value from sequence
612
+ AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze
613
+
614
+ # Returns the next sequence value from a sequence generator. Not generally
615
+ # called directly; used by ActiveRecord to get the next primary key value
616
+ # when inserting a new database record (see #prefetch_primary_key?).
617
+ def next_sequence_value(sequence_name)
618
+ # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
619
+ return nil if sequence_name == AUTOGENERATED_SEQUENCE_NAME
620
+ select_one("SELECT #{quote_table_name(sequence_name)}.NEXTVAL id FROM dual")['id']
621
+ end
622
+
623
+ def begin_db_transaction #:nodoc:
624
+ @connection.autocommit = false
625
+ end
626
+
627
+ def commit_db_transaction #:nodoc:
628
+ @connection.commit
629
+ ensure
630
+ @connection.autocommit = true
631
+ end
632
+
633
+ def rollback_db_transaction #:nodoc:
634
+ @connection.rollback
635
+ ensure
636
+ @connection.autocommit = true
637
+ end
638
+
639
+ def create_savepoint #:nodoc:
640
+ execute("SAVEPOINT #{current_savepoint_name}")
641
+ end
642
+
643
+ def rollback_to_savepoint #:nodoc:
644
+ execute("ROLLBACK TO #{current_savepoint_name}")
645
+ end
646
+
647
+ def release_savepoint #:nodoc:
648
+ # there is no RELEASE SAVEPOINT statement in Oracle
649
+ end
650
+
651
+ def add_limit_offset!(sql, options) #:nodoc:
652
+ # added to_i for limit and offset to protect from SQL injection
653
+ offset = (options[:offset] || 0).to_i
654
+
655
+ if limit = options[:limit]
656
+ limit = limit.to_i
657
+ sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
658
+ elsif offset > 0
659
+ sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
660
+ end
661
+ end
662
+
663
+ @@do_not_prefetch_primary_key = {}
664
+
665
+ # Returns true for Oracle adapter (since Oracle requires primary key
666
+ # values to be pre-fetched before insert). See also #next_sequence_value.
667
+ def prefetch_primary_key?(table_name = nil)
668
+ ! @@do_not_prefetch_primary_key[table_name.to_s]
669
+ end
670
+
671
+ # used just in tests to clear prefetch primary key flag for all tables
672
+ def clear_prefetch_primary_key #:nodoc:
673
+ @@do_not_prefetch_primary_key = {}
674
+ end
675
+
676
+ # Returns default sequence name for table.
677
+ # Will take all or first 26 characters of table name and append _seq suffix
678
+ def default_sequence_name(table_name, primary_key = nil)
679
+ # TODO: remove schema prefix if present before truncating
680
+ # truncate table name if necessary to fit in max length of identifier
681
+ "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_seq"
682
+ end
683
+
684
+ # Inserts the given fixture into the table. Overridden to properly handle lobs.
685
+ def insert_fixture(fixture, table_name) #:nodoc:
686
+ super
687
+
688
+ klass = fixture.class_name.constantize rescue nil
689
+ if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
690
+ write_lobs(table_name, klass, fixture)
691
+ end
692
+ end
693
+
694
+ # Writes LOB values from attributes, as indicated by the LOB columns of klass.
695
+ def write_lobs(table_name, klass, attributes) #:nodoc:
696
+ # is class with composite primary key>
697
+ is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
698
+ if is_with_cpk
699
+ id = klass.primary_key.map {|pk| attributes[pk.to_s] }
700
+ else
701
+ id = quote(attributes[klass.primary_key])
702
+ end
703
+ klass.columns.select { |col| col.sql_type =~ /LOB$/i }.each do |col|
704
+ value = attributes[col.name]
705
+ # changed sequence of next two lines - should check if value is nil before converting to yaml
706
+ next if value.nil? || (value == '')
707
+ value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
708
+ uncached do
709
+ if is_with_cpk
710
+ lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.composite_where_clause(id)} FOR UPDATE",
711
+ 'Writable Large Object')[col.name]
712
+ else
713
+ lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE",
714
+ 'Writable Large Object')[col.name]
715
+ end
716
+ @connection.write_lob(lob, value.to_s, col.type == :binary)
717
+ end
718
+ end
719
+ end
720
+
721
+ # change LOB column for ORDER BY clause
722
+ # just first 100 characters are taken for ordering
723
+ def lob_order_by_expression(klass, order) #:nodoc:
724
+ return order if order.nil?
725
+ changed = false
726
+ new_order = order.to_s.strip.split(/, */).map do |order_by_col|
727
+ column_name, asc_desc = order_by_col.split(/ +/)
728
+ if column = klass.columns.detect { |col| col.name == column_name && col.sql_type =~ /LOB$/i}
729
+ changed = true
730
+ "DBMS_LOB.SUBSTR(#{column_name},100,1) #{asc_desc}"
731
+ else
732
+ order_by_col
733
+ end
734
+ end.join(', ')
735
+ changed ? new_order : order
736
+ end
737
+
738
+ # SCHEMA STATEMENTS ========================================
739
+ #
740
+ # see: abstract/schema_statements.rb
741
+
742
+ # Current database name
743
+ def current_database
744
+ select_value("select sys_context('userenv','db_name') from dual")
745
+ end
746
+
747
+ # Current database session user
748
+ def current_user
749
+ select_value("select sys_context('userenv','session_user') from dual")
750
+ end
751
+
752
+ # Default tablespace name of current user
753
+ def default_tablespace
754
+ select_value("select lower(default_tablespace) from user_users where username = sys_context('userenv','session_user')")
755
+ end
756
+
757
+ def tables(name = nil) #:nodoc:
758
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
759
+ select_all("select decode(table_name,upper(table_name),lower(table_name),table_name) name from all_tables where owner = sys_context('userenv','session_user')").map {|t| t['name']}
760
+ end
761
+
762
+ cattr_accessor :all_schema_indexes #:nodoc:
763
+
764
+ # This method selects all indexes at once, and caches them in a class variable.
765
+ # Subsequent index calls get them from the variable, without going to the DB.
766
+ def indexes(table_name, name = nil) #:nodoc:
767
+ (owner, table_name, db_link) = @connection.describe(table_name)
768
+ unless all_schema_indexes
769
+ default_tablespace_name = default_tablespace
770
+ result = select_all(<<-SQL)
771
+ SELECT lower(i.table_name) as table_name, lower(i.index_name) as index_name, i.uniqueness, lower(i.tablespace_name) as tablespace_name, lower(c.column_name) as column_name, e.column_expression as column_expression
772
+ FROM all_indexes#{db_link} i
773
+ JOIN all_ind_columns#{db_link} c on c.index_name = i.index_name and c.index_owner = i.owner
774
+ LEFT OUTER JOIN all_ind_expressions#{db_link} e on e.index_name = i.index_name and e.index_owner = i.owner and e.column_position = c.column_position
775
+ WHERE i.owner = '#{owner}'
776
+ AND i.table_owner = '#{owner}'
777
+ AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
778
+ ORDER BY i.index_name, c.column_position
779
+ SQL
780
+
781
+ current_index = nil
782
+ self.all_schema_indexes = []
783
+
784
+ result.each do |row|
785
+ # have to keep track of indexes because above query returns dups
786
+ # there is probably a better query we could figure out
787
+ if current_index != row['index_name']
788
+ all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE",
789
+ row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], [])
790
+ current_index = row['index_name']
791
+ end
792
+ all_schema_indexes.last.columns << (row['column_expression'].nil? ? row['column_name'] : row['column_expression'].gsub('"','').downcase)
793
+ end
794
+ end
795
+
796
+ # Return the indexes just for the requested table, since AR is structured that way
797
+ table_name = table_name.downcase
798
+ all_schema_indexes.select{|i| i.table == table_name}
799
+ end
800
+
801
+ @@ignore_table_columns = nil #:nodoc:
802
+
803
+ # set ignored columns for table
804
+ def ignore_table_columns(table_name, *args) #:nodoc:
805
+ @@ignore_table_columns ||= {}
806
+ @@ignore_table_columns[table_name] ||= []
807
+ @@ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
808
+ @@ignore_table_columns[table_name].uniq!
809
+ end
810
+
811
+ def ignored_table_columns(table_name) #:nodoc:
812
+ @@ignore_table_columns ||= {}
813
+ @@ignore_table_columns[table_name]
814
+ end
815
+
816
+ # used just in tests to clear ignored table columns
817
+ def clear_ignored_table_columns #:nodoc:
818
+ @@ignore_table_columns = nil
819
+ end
820
+
821
+ @@table_column_type = nil #:nodoc:
822
+
823
+ # set explicit type for specified table columns
824
+ def set_type_for_columns(table_name, column_type, *args) #:nodoc:
825
+ @@table_column_type ||= {}
826
+ @@table_column_type[table_name] ||= {}
827
+ args.each do |col|
828
+ @@table_column_type[table_name][col.to_s.downcase] = column_type
829
+ end
830
+ end
831
+
832
+ def get_type_for_column(table_name, column_name) #:nodoc:
833
+ @@table_column_type && @@table_column_type[table_name] && @@table_column_type[table_name][column_name.to_s.downcase]
834
+ end
835
+
836
+ # used just in tests to clear column data type definitions
837
+ def clear_types_for_columns #:nodoc:
838
+ @@table_column_type = nil
839
+ end
840
+
841
+ # check if table has primary key trigger with _pkt suffix
842
+ def has_primary_key_trigger?(table_name, owner = nil, desc_table_name = nil, db_link = nil)
843
+ (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
844
+
845
+ trigger_name = default_trigger_name(table_name).upcase
846
+ pkt_sql = <<-SQL
847
+ SELECT trigger_name
848
+ FROM all_triggers#{db_link}
849
+ WHERE owner = '#{owner}'
850
+ AND trigger_name = '#{trigger_name}'
851
+ AND table_owner = '#{owner}'
852
+ AND table_name = '#{desc_table_name}'
853
+ AND status = 'ENABLED'
854
+ SQL
855
+ select_value(pkt_sql) ? true : false
856
+ end
857
+
858
+ ##
859
+ # :singleton-method:
860
+ # Cache column description between requests.
861
+ # Could be used in development environment to avoid selecting table columns from data dictionary tables for each request.
862
+ # This can speed up request processing in development mode if development database is not on local computer.
863
+ #
864
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = true
865
+ cattr_accessor :cache_columns
866
+ self.cache_columns = false
867
+
868
+ def columns(table_name, name = nil) #:nodoc:
869
+ # Don't double cache if config.cache_classes is turned on
870
+ if @@cache_columns && !(defined?(Rails) && Rails.configuration.cache_classes)
871
+ @@columns_cache ||= {}
872
+ @@columns_cache[table_name] ||= columns_without_cache(table_name, name)
873
+ else
874
+ columns_without_cache(table_name, name)
875
+ end
876
+ end
877
+
878
+ def columns_without_cache(table_name, name = nil) #:nodoc:
879
+ # get ignored_columns by original table name
880
+ ignored_columns = ignored_table_columns(table_name)
881
+
882
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
883
+
884
+ if has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
885
+ @@do_not_prefetch_primary_key[table_name] = true
886
+ end
887
+
888
+ table_cols = <<-SQL
889
+ select column_name as name, data_type as sql_type, data_default, nullable,
890
+ decode(data_type, 'NUMBER', data_precision,
891
+ 'FLOAT', data_precision,
892
+ 'VARCHAR2', decode(char_used, 'C', char_length, data_length),
893
+ 'CHAR', decode(char_used, 'C', char_length, data_length),
894
+ null) as limit,
895
+ decode(data_type, 'NUMBER', data_scale, null) as scale
896
+ from all_tab_columns#{db_link}
897
+ where owner = '#{owner}'
898
+ and table_name = '#{desc_table_name}'
899
+ order by column_id
900
+ SQL
901
+
902
+ # added deletion of ignored columns
903
+ select_all(table_cols, name).delete_if do |row|
904
+ ignored_columns && ignored_columns.include?(row['name'].downcase)
905
+ end.map do |row|
906
+ limit, scale = row['limit'], row['scale']
907
+ if limit || scale
908
+ row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
909
+ end
910
+
911
+ # clean up odd default spacing from Oracle
912
+ if row['data_default']
913
+ row['data_default'].sub!(/^(.*?)\s*$/, '\1')
914
+
915
+ # If a default contains a newline these cleanup regexes need to
916
+ # match newlines.
917
+ row['data_default'].sub!(/^'(.*)'$/m, '\1')
918
+ row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
919
+ end
920
+
921
+ OracleEnhancedColumn.new(oracle_downcase(row['name']),
922
+ row['data_default'],
923
+ row['sql_type'],
924
+ row['nullable'] == 'Y',
925
+ # pass table name for table specific column definitions
926
+ table_name,
927
+ # pass column type if specified in class definition
928
+ get_type_for_column(table_name, oracle_downcase(row['name'])))
929
+ end
930
+ end
931
+
932
+ # used just in tests to clear column cache
933
+ def clear_columns_cache #:nodoc:
934
+ @@columns_cache = nil
935
+ end
936
+
937
+ # used in migrations to clear column cache for specified table
938
+ def clear_table_columns_cache(table_name)
939
+ @@columns_cache[table_name.to_s] = nil if @@cache_columns
940
+ end
941
+
942
+ ##
943
+ # :singleton-method:
944
+ # Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
945
+ #
946
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 1
947
+ cattr_accessor :default_sequence_start_value
948
+ self.default_sequence_start_value = 10000
949
+
950
+ # Additional options for +create_table+ method in migration files.
951
+ #
952
+ # You can specify individual starting value in table creation migration file, e.g.:
953
+ #
954
+ # create_table :users, :sequence_start_value => 100 do |t|
955
+ # # ...
956
+ # end
957
+ #
958
+ # You can also specify other sequence definition additional parameters, e.g.:
959
+ #
960
+ # create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t|
961
+ # # ...
962
+ # end
963
+ #
964
+ # Create primary key trigger (so that you can skip primary key value in INSERT statement).
965
+ # By default trigger name will be "table_name_pkt", you can override the name with
966
+ # :trigger_name option (but it is not recommended to override it as then this trigger will
967
+ # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
968
+ # Example:
969
+ #
970
+ # create_table :users, :primary_key_trigger => true do |t|
971
+ # # ...
972
+ # end
973
+ #
974
+ # It is possible to add table and column comments in table creation migration files:
975
+ #
976
+ # create_table :employees, :comment => “Employees and contractors” do |t|
977
+ # t.string :first_name, :comment => “Given name”
978
+ # t.string :last_name, :comment => “Surname”
979
+ # end
980
+
981
+ def create_table(name, options = {}, &block)
982
+ create_sequence = options[:id] != false
983
+ column_comments = {}
984
+
985
+ table_definition = TableDefinition.new(self)
986
+ table_definition.primary_key(options[:primary_key] || Base.get_primary_key(name.to_s.singularize)) unless options[:id] == false
987
+
988
+ # store that primary key was defined in create_table block
989
+ unless create_sequence
990
+ class << table_definition
991
+ attr_accessor :create_sequence
992
+ def primary_key(*args)
993
+ self.create_sequence = true
994
+ super(*args)
995
+ end
996
+ end
997
+ end
998
+
999
+ # store column comments
1000
+ class << table_definition
1001
+ attr_accessor :column_comments
1002
+ def column(name, type, options = {})
1003
+ if options[:comment]
1004
+ self.column_comments ||= {}
1005
+ self.column_comments[name] = options[:comment]
1006
+ end
1007
+ super(name, type, options)
1008
+ end
1009
+ end
1010
+
1011
+ result = block.call(table_definition) if block
1012
+ create_sequence = create_sequence || table_definition.create_sequence
1013
+ column_comments = table_definition.column_comments if table_definition.column_comments
1014
+
1015
+
1016
+ if options[:force] && table_exists?(name)
1017
+ drop_table(name, options)
1018
+ end
1019
+
1020
+ create_sql = "CREATE#{' GLOBAL TEMPORARY' if options[:temporary]} TABLE "
1021
+ create_sql << "#{quote_table_name(name)} ("
1022
+ create_sql << table_definition.to_sql
1023
+ create_sql << ") #{options[:options]}"
1024
+ execute create_sql
1025
+
1026
+ create_sequence_and_trigger(name, options) if create_sequence
1027
+
1028
+ add_table_comment name, options[:comment]
1029
+ column_comments.each do |column_name, comment|
1030
+ add_comment name, column_name, comment
1031
+ end
1032
+
1033
+ end
1034
+
1035
+ def rename_table(name, new_name) #:nodoc:
1036
+ execute "RENAME #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
1037
+ execute "RENAME #{quote_table_name("#{name}_seq")} TO #{quote_table_name("#{new_name}_seq")}" rescue nil
1038
+ end
1039
+
1040
+ def drop_table(name, options = {}) #:nodoc:
1041
+ super(name)
1042
+ seq_name = options[:sequence_name] || default_sequence_name(name)
1043
+ execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
1044
+ ensure
1045
+ clear_table_columns_cache(name)
1046
+ end
1047
+
1048
+ # clear cached indexes when adding new index
1049
+ def add_index(table_name, column_name, options = {}) #:nodoc:
1050
+ self.all_schema_indexes = nil
1051
+ column_names = Array(column_name)
1052
+ index_name = index_name(table_name, :column => column_names)
1053
+
1054
+ if Hash === options # legacy support, since this param was a string
1055
+ index_type = options[:unique] ? "UNIQUE" : ""
1056
+ index_name = options[:name] || index_name
1057
+ tablespace = if options[:tablespace]
1058
+ " TABLESPACE #{options[:tablespace]}"
1059
+ else
1060
+ ""
1061
+ end
1062
+ else
1063
+ index_type = options
1064
+ end
1065
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
1066
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})#{tablespace}"
1067
+ end
1068
+
1069
+ # clear cached indexes when removing index
1070
+ def remove_index(table_name, options = {}) #:nodoc:
1071
+ self.all_schema_indexes = nil
1072
+ execute "DROP INDEX #{index_name(table_name, options)}"
1073
+ end
1074
+
1075
+ # returned shortened index name if default is too large
1076
+ def index_name(table_name, options) #:nodoc:
1077
+ default_name = super(table_name, options)
1078
+ return default_name if default_name.length <= IDENTIFIER_MAX_LENGTH
1079
+
1080
+ # remove 'index', 'on' and 'and' keywords
1081
+ shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
1082
+
1083
+ # leave just first three letters from each word
1084
+ if shortened_name.length > IDENTIFIER_MAX_LENGTH
1085
+ shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
1086
+ end
1087
+ # generate unique name using hash function
1088
+ if shortened_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
1089
+ shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1]
1090
+ end
1091
+ @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
1092
+ shortened_name
1093
+ end
1094
+
1095
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
1096
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
1097
+ options[:type] = type
1098
+ add_column_options!(add_column_sql, options)
1099
+ execute(add_column_sql)
1100
+ ensure
1101
+ clear_table_columns_cache(table_name)
1102
+ end
1103
+
1104
+ def change_column_default(table_name, column_name, default) #:nodoc:
1105
+ execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
1106
+ ensure
1107
+ clear_table_columns_cache(table_name)
1108
+ end
1109
+
1110
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
1111
+ column = column_for(table_name, column_name)
1112
+
1113
+ unless null || default.nil?
1114
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
1115
+ end
1116
+
1117
+ change_column table_name, column_name, column.sql_type, :null => null
1118
+ end
1119
+
1120
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
1121
+ column = column_for(table_name, column_name)
1122
+
1123
+ # remove :null option if its value is the same as current column definition
1124
+ # otherwise Oracle will raise error
1125
+ if options.has_key?(:null) && options[:null] == column.null
1126
+ options[:null] = nil
1127
+ end
1128
+
1129
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
1130
+ options[:type] = type
1131
+ add_column_options!(change_column_sql, options)
1132
+ execute(change_column_sql)
1133
+ ensure
1134
+ clear_table_columns_cache(table_name)
1135
+ end
1136
+
1137
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
1138
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
1139
+ ensure
1140
+ clear_table_columns_cache(table_name)
1141
+ end
1142
+
1143
+ def remove_column(table_name, column_name) #:nodoc:
1144
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
1145
+ ensure
1146
+ clear_table_columns_cache(table_name)
1147
+ end
1148
+
1149
+ def add_comment(table_name, column_name, comment) #:nodoc:
1150
+ return if comment.blank?
1151
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
1152
+ end
1153
+
1154
+ def add_table_comment(table_name, comment) #:nodoc:
1155
+ return if comment.blank?
1156
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
1157
+ end
1158
+
1159
+ def table_comment(table_name) #:nodoc:
1160
+ (owner, table_name, db_link) = @connection.describe(table_name)
1161
+ select_value <<-SQL
1162
+ SELECT comments FROM all_tab_comments#{db_link}
1163
+ WHERE owner = '#{owner}'
1164
+ AND table_name = '#{table_name}'
1165
+ SQL
1166
+ end
1167
+
1168
+ def column_comment(table_name, column_name) #:nodoc:
1169
+ (owner, table_name, db_link) = @connection.describe(table_name)
1170
+ select_value <<-SQL
1171
+ SELECT comments FROM all_col_comments#{db_link}
1172
+ WHERE owner = '#{owner}'
1173
+ AND table_name = '#{table_name}'
1174
+ AND column_name = '#{column_name.upcase}'
1175
+ SQL
1176
+ end
1177
+
1178
+ # Maps logical Rails types to Oracle-specific data types.
1179
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
1180
+ # Ignore options for :text and :binary columns
1181
+ return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s)
1182
+
1183
+ super
1184
+ end
1185
+
1186
+ # Find a table's primary key and sequence.
1187
+ # *Note*: Only primary key is implemented - sequence will be nil.
1188
+ def pk_and_sequence_for(table_name) #:nodoc:
1189
+ (owner, table_name, db_link) = @connection.describe(table_name)
1190
+
1191
+ # changed select from all_constraints to user_constraints - much faster in large data dictionaries
1192
+ pks = select_values(<<-SQL, 'Primary Key')
1193
+ select cc.column_name
1194
+ from user_constraints#{db_link} c, user_cons_columns#{db_link} cc
1195
+ where c.owner = '#{owner}'
1196
+ and c.table_name = '#{table_name}'
1197
+ and c.constraint_type = 'P'
1198
+ and cc.owner = c.owner
1199
+ and cc.constraint_name = c.constraint_name
1200
+ SQL
1201
+
1202
+ # only support single column keys
1203
+ pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
1204
+ end
1205
+
1206
+ def structure_dump #:nodoc:
1207
+ s = select_all("select sequence_name from user_sequences order by 1").inject("") do |structure, seq|
1208
+ structure << "create sequence #{seq.to_a.first.last}#{STATEMENT_TOKEN}"
1209
+ end
1210
+
1211
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
1212
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |structure, table|
1213
+ table_name = table['table_name']
1214
+ virtual_columns = virtual_columns_for(table_name)
1215
+ ddl = "create#{ ' global temporary' if temporary_table?(table_name)} table #{table_name} (\n "
1216
+ cols = select_all(%Q{
1217
+ select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
1218
+ from user_tab_columns
1219
+ where table_name = '#{table_name}'
1220
+ order by column_id
1221
+ }).map do |row|
1222
+ if(v = virtual_columns.find {|col| col['column_name'] == row['column_name']})
1223
+ structure_dump_virtual_column(row, v['data_default'])
1224
+ else
1225
+ structure_dump_column(row)
1226
+ end
1227
+ end
1228
+ ddl << cols.join(",\n ")
1229
+ ddl << structure_dump_constraints(table_name)
1230
+ ddl << "\n)#{STATEMENT_TOKEN}"
1231
+ structure << ddl
1232
+ structure << structure_dump_indexes(table_name)
1233
+ end
1234
+ end
1235
+
1236
+ def structure_dump_virtual_column(column, data_default) #:nodoc:
1237
+ data_default = data_default.gsub(/"/, '')
1238
+ col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
1239
+ if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
1240
+ col << "(#{column['data_precision'].to_i}"
1241
+ col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
1242
+ col << ')'
1243
+ elsif column['data_type'].include?('CHAR')
1244
+ length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
1245
+ col << "(#{length})"
1246
+ end
1247
+ col << " GENERATED ALWAYS AS (#{data_default}) VIRTUAL"
1248
+ end
1249
+
1250
+ def structure_dump_column(column) #:nodoc:
1251
+ col = "#{column['column_name'].downcase} #{column['data_type'].downcase}"
1252
+ if column['data_type'] =='NUMBER' and !column['data_precision'].nil?
1253
+ col << "(#{column['data_precision'].to_i}"
1254
+ col << ",#{column['data_scale'].to_i}" if !column['data_scale'].nil?
1255
+ col << ')'
1256
+ elsif column['data_type'].include?('CHAR')
1257
+ length = column['char_used'] == 'C' ? column['char_length'].to_i : column['data_length'].to_i
1258
+ col << "(#{length})"
1259
+ end
1260
+ col << " default #{column['data_default']}" if !column['data_default'].nil?
1261
+ col << ' not null' if column['nullable'] == 'N'
1262
+ col
1263
+ end
1264
+
1265
+ def structure_dump_constraints(table) #:nodoc:
1266
+ out = [structure_dump_primary_key(table), structure_dump_unique_keys(table)].flatten.compact
1267
+ out.length > 0 ? ",\n#{out.join(",\n")}" : ''
1268
+ end
1269
+
1270
+ def structure_dump_primary_key(table) #:nodoc:
1271
+ opts = {:name => '', :cols => []}
1272
+ pks = select_all(<<-SQL, "Primary Keys")
1273
+ select a.constraint_name, a.column_name, a.position
1274
+ from user_cons_columns a
1275
+ join user_constraints c
1276
+ on a.constraint_name = c.constraint_name
1277
+ where c.table_name = '#{table.upcase}'
1278
+ and c.constraint_type = 'P'
1279
+ and c.owner = sys_context('userenv', 'session_user')
1280
+ SQL
1281
+ pks.each do |row|
1282
+ opts[:name] = row['constraint_name']
1283
+ opts[:cols][row['position']-1] = row['column_name']
1284
+ end
1285
+ opts[:cols].length > 0 ? " CONSTRAINT #{opts[:name]} PRIMARY KEY (#{opts[:cols].join(',')})" : nil
1286
+ end
1287
+
1288
+ def structure_dump_unique_keys(table) #:nodoc:
1289
+ keys = {}
1290
+ uks = select_all(<<-SQL, "Primary Keys")
1291
+ select a.constraint_name, a.column_name, a.position
1292
+ from user_cons_columns a
1293
+ join user_constraints c
1294
+ on a.constraint_name = c.constraint_name
1295
+ where c.table_name = '#{table.upcase}'
1296
+ and c.constraint_type = 'U'
1297
+ and c.owner = sys_context('userenv', 'session_user')
1298
+ SQL
1299
+ uks.each do |uk|
1300
+ keys[uk['constraint_name']] ||= []
1301
+ keys[uk['constraint_name']][uk['position']-1] = uk['column_name']
1302
+ end
1303
+ keys.map do |k,v|
1304
+ " CONSTRAINT #{k} UNIQUE (#{v.join(',')})"
1305
+ end
1306
+ end
1307
+
1308
+ def structure_dump_fk_constraints #:nodoc:
1309
+ fks = select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").map do |table|
1310
+ if respond_to?(:foreign_keys) && (foreign_keys = foreign_keys(table["table_name"])).any?
1311
+ foreign_keys.map do |fk|
1312
+ column = fk.options[:column] || "#{fk.to_table.to_s.singularize}_id"
1313
+ constraint_name = foreign_key_constraint_name(fk.from_table, column, fk.options)
1314
+ sql = "ALTER TABLE #{quote_table_name(fk.from_table)} ADD CONSTRAINT #{quote_column_name(constraint_name)} "
1315
+ sql << "#{foreign_key_definition(fk.to_table, fk.options)}"
1316
+ end
1317
+ end
1318
+ end.flatten.compact.join(STATEMENT_TOKEN)
1319
+ fks.length > 1 ? "#{fks}#{STATEMENT_TOKEN}" : ''
1320
+ end
1321
+
1322
+ # Extract all stored procedures, packages, synonyms and views.
1323
+ def structure_dump_db_stored_code #:nodoc:
1324
+ structure = ""
1325
+ select_all("select distinct name, type
1326
+ from all_source
1327
+ where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION', 'TRIGGER', 'TYPE')
1328
+ and owner = sys_context('userenv','session_user') order by type").each do |source|
1329
+ ddl = "create or replace \n "
1330
+ lines = select_all(%Q{
1331
+ select text
1332
+ from all_source
1333
+ where name = '#{source['name']}'
1334
+ and type = '#{source['type']}'
1335
+ and owner = sys_context('userenv','session_user')
1336
+ order by line
1337
+ }).map do |row|
1338
+ ddl << row['text'] if row['text'].size > 1
1339
+ end
1340
+ ddl << ";" unless ddl.strip.last == ";"
1341
+ structure << ddl << STATEMENT_TOKEN
1342
+ end
1343
+
1344
+ # export views
1345
+ select_all("select view_name, text from user_views").each do |view|
1346
+ ddl = "create or replace view #{view['view_name']} AS\n "
1347
+ # any views with empty lines will cause OCI to barf when loading. remove blank lines =/
1348
+ ddl << view['text'].gsub(/^\n/, '')
1349
+ structure << ddl << STATEMENT_TOKEN
1350
+ end
1351
+
1352
+ # export synonyms
1353
+ select_all("select owner, synonym_name, table_name, table_owner
1354
+ from all_synonyms
1355
+ where owner = sys_context('userenv','session_user') ").each do |synonym|
1356
+ ddl = "create or replace #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']} for #{synonym['table_owner']}.#{synonym['table_name']}"
1357
+ structure << ddl << STATEMENT_TOKEN
1358
+ end
1359
+
1360
+ structure
1361
+ end
1362
+
1363
+ def structure_dump_indexes(table_name) #:nodoc:
1364
+ statements = indexes(table_name).map do |options|
1365
+ #def add_index(table_name, column_name, options = {})
1366
+ column_names = options[:columns]
1367
+ options = {:name => options[:name], :unique => options[:unique]}
1368
+ index_name = index_name(table_name, :column => column_names)
1369
+ if Hash === options # legacy support, since this param was a string
1370
+ index_type = options[:unique] ? "UNIQUE" : ""
1371
+ index_name = options[:name] || index_name
1372
+ else
1373
+ index_type = options
1374
+ end
1375
+ quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ")
1376
+ "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})"
1377
+ end
1378
+ statements.length > 0 ? "#{statements.join(STATEMENT_TOKEN)}#{STATEMENT_TOKEN}" : ''
1379
+ end
1380
+
1381
+ def structure_drop #:nodoc:
1382
+ s = select_all("select sequence_name from user_sequences order by 1").inject("") do |drop, seq|
1383
+ drop << "drop sequence #{seq.to_a.first.last};\n\n"
1384
+ end
1385
+
1386
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
1387
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |drop, table|
1388
+ drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
1389
+ end
1390
+ end
1391
+
1392
+ def temp_table_drop #:nodoc:
1393
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
1394
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') and temporary = 'Y' order by 1").inject('') do |drop, table|
1395
+ drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
1396
+ end
1397
+ end
1398
+
1399
+ def full_drop(preserve_tables=false) #:nodoc:
1400
+ s = preserve_tables ? [] : [structure_drop]
1401
+ s << temp_table_drop if preserve_tables
1402
+ s << drop_sql_for_feature("view")
1403
+ s << drop_sql_for_feature("synonym")
1404
+ s << drop_sql_for_feature("type")
1405
+ s << drop_sql_for_object("package")
1406
+ s << drop_sql_for_object("function")
1407
+ s << drop_sql_for_object("procedure")
1408
+ s.join("\n\n")
1409
+ end
1410
+
1411
+ def add_column_options!(sql, options) #:nodoc:
1412
+ type = options[:type] || ((column = options[:column]) && column.type)
1413
+ type = type && type.to_sym
1414
+ # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
1415
+ if options_include_default?(options)
1416
+ if type == :text
1417
+ sql << " DEFAULT #{quote(options[:default])}"
1418
+ else
1419
+ # from abstract adapter
1420
+ sql << " DEFAULT #{quote(options[:default], options[:column])}"
1421
+ end
1422
+ end
1423
+ # must explicitly add NULL or NOT NULL to allow change_column to work on migrations
1424
+ if options[:null] == false
1425
+ sql << " NOT NULL"
1426
+ elsif options[:null] == true
1427
+ sql << " NULL" unless type == :primary_key
1428
+ end
1429
+ end
1430
+
1431
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
1432
+ #
1433
+ # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
1434
+ # queries. However, with those columns included in the SELECT DISTINCT list, you
1435
+ # won't actually get a distinct list of the column you want (presuming the column
1436
+ # has duplicates with multiple values for the ordered-by columns. So we use the
1437
+ # FIRST_VALUE function to get a single (first) value for each column, effectively
1438
+ # making every row the same.
1439
+ #
1440
+ # distinct("posts.id", "posts.created_at desc")
1441
+ def distinct(columns, order_by) #:nodoc:
1442
+ return "DISTINCT #{columns}" if order_by.blank?
1443
+
1444
+ # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
1445
+ # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
1446
+ order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
1447
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
1448
+ "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
1449
+ end
1450
+ sql = "DISTINCT #{columns}, "
1451
+ sql << order_columns * ", "
1452
+ end
1453
+
1454
+ def temporary_table?(table_name) #:nodoc:
1455
+ select_value("select temporary from user_tables where table_name = '#{table_name.upcase}'") == 'Y'
1456
+ end
1457
+
1458
+ # statements separator used in structure dump
1459
+ STATEMENT_TOKEN = "\n\n--@@@--\n\n"
1460
+
1461
+ # ORDER BY clause for the passed order option.
1462
+ #
1463
+ # Uses column aliases as defined by #distinct.
1464
+ def add_order_by_for_association_limiting!(sql, options) #:nodoc:
1465
+ return sql if options[:order].blank?
1466
+
1467
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
1468
+ order.map! {|s| $1 if s =~ / (.*)/}
1469
+ order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
1470
+
1471
+ sql << " ORDER BY #{order}"
1472
+ end
1473
+
1474
+ protected
1475
+
1476
+ def translate_exception(exception, message) #:nodoc:
1477
+ case @connection.error_code(exception)
1478
+ when 1
1479
+ RecordNotUnique.new(message, exception)
1480
+ when 2291
1481
+ InvalidForeignKey.new(message, exception)
1482
+ else
1483
+ super
1484
+ end
1485
+ end
1486
+
1487
+ private
1488
+
1489
+ def select(sql, name = nil, return_column_names = false)
1490
+ log(sql, name) do
1491
+ @connection.select(sql, name, return_column_names)
1492
+ end
1493
+ end
1494
+
1495
+ def oracle_downcase(column_name)
1496
+ @connection.oracle_downcase(column_name)
1497
+ end
1498
+
1499
+ def column_for(table_name, column_name)
1500
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
1501
+ raise "No such column: #{table_name}.#{column_name}"
1502
+ end
1503
+ column
1504
+ end
1505
+
1506
+ def create_sequence_and_trigger(table_name, options)
1507
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
1508
+ seq_start_value = options[:sequence_start_value] || default_sequence_start_value
1509
+ execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
1510
+
1511
+ create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
1512
+ end
1513
+
1514
+ def create_primary_key_trigger(table_name, options)
1515
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
1516
+ trigger_name = options[:trigger_name] || default_trigger_name(table_name)
1517
+ primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
1518
+ execute compress_lines(<<-SQL)
1519
+ CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
1520
+ BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
1521
+ BEGIN
1522
+ IF inserting THEN
1523
+ IF :new.#{quote_column_name(primary_key)} IS NULL THEN
1524
+ SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
1525
+ END IF;
1526
+ END IF;
1527
+ END;
1528
+ SQL
1529
+ end
1530
+
1531
+ def default_trigger_name(table_name)
1532
+ # truncate table name if necessary to fit in max length of identifier
1533
+ "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_pkt"
1534
+ end
1535
+
1536
+ def compress_lines(string, spaced = true)
1537
+ string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
1538
+ end
1539
+
1540
+ # virtual columns are an 11g feature. This returns [] if feature is not
1541
+ # present or none are found.
1542
+ # return [{'column_name' => 'FOOS', 'data_default' => '...'}, ...]
1543
+ def virtual_columns_for(table)
1544
+ begin
1545
+ select_all <<-SQL
1546
+ select column_name, data_default
1547
+ from user_tab_cols
1548
+ where virtual_column='YES'
1549
+ and table_name='#{table.upcase}'
1550
+ SQL
1551
+ # feature not supported previous to 11g
1552
+ rescue ActiveRecord::StatementInvalid => e
1553
+ []
1554
+ end
1555
+ end
1556
+
1557
+ def drop_sql_for_feature(type)
1558
+ select_values("select 'DROP #{type.upcase} \"' || #{type}_name || '\";' from user_#{type.tableize}").join("\n\n")
1559
+ end
1560
+
1561
+ def drop_sql_for_object(type)
1562
+ select_values("select 'DROP #{type.upcase} ' || object_name || ';' from user_objects where object_type = '#{type.upcase}'").join("\n\n")
1563
+ end
1564
+
1565
+ public
1566
+ # DBMS_OUTPUT =============================================
1567
+ #
1568
+ # PL/SQL in Oracle uses dbms_output for logging print statements
1569
+ # These methods stick that output into the Rails log so Ruby and PL/SQL
1570
+ # code can can be debugged together in a single application
1571
+
1572
+ # Maximum DBMS_OUTPUT buffer size
1573
+ DBMS_OUTPUT_BUFFER_SIZE = 10000 # can be 1-1000000
1574
+
1575
+ # Turn DBMS_Output logging on
1576
+ def enable_dbms_output
1577
+ set_dbms_output_plsql_connection
1578
+ @enable_dbms_output = true
1579
+ plsql(:dbms_output).sys.dbms_output.enable(DBMS_OUTPUT_BUFFER_SIZE)
1580
+ end
1581
+ # Turn DBMS_Output logging off
1582
+ def disable_dbms_output
1583
+ set_dbms_output_plsql_connection
1584
+ @enable_dbms_output = false
1585
+ plsql(:dbms_output).sys.dbms_output.disable
1586
+ end
1587
+ # Is DBMS_Output logging enabled?
1588
+ def dbms_output_enabled?
1589
+ @enable_dbms_output
1590
+ end
1591
+
1592
+ protected
1593
+ def log(sql, name) #:nodoc:
1594
+ super sql, name
1595
+ ensure
1596
+ log_dbms_output if dbms_output_enabled?
1597
+ end
1598
+
1599
+ private
1600
+
1601
+ def set_dbms_output_plsql_connection
1602
+ raise OracleEnhancedConnectionException, "ruby-plsql gem is required for logging DBMS output" unless self.respond_to?(:plsql)
1603
+ # do not reset plsql connection if it is the same (as resetting will clear PL/SQL metadata cache)
1604
+ unless plsql(:dbms_output).connection && plsql(:dbms_output).connection.raw_connection == raw_connection
1605
+ plsql(:dbms_output).connection = raw_connection
1606
+ end
1607
+ end
1608
+
1609
+ def log_dbms_output
1610
+ while true do
1611
+ result = plsql(:dbms_output).sys.dbms_output.get_line(:line => '', :status => 0)
1612
+ break unless result[:status] == 0
1613
+ @logger.debug "DBMS_OUTPUT: #{result[:line]}"
1614
+ end
1615
+ end
1616
+
1617
+ end
1618
+ end
1619
+ end
1620
+
1621
+ # Added LOB writing callback for sessions stored in database
1622
+ # Otherwise it is not working as Session class is defined before OracleAdapter is loaded in Rails 2.0
1623
+ if defined?(CGI::Session::ActiveRecordStore::Session)
1624
+ if !CGI::Session::ActiveRecordStore::Session.respond_to?(:after_save_callback_chain) ||
1625
+ CGI::Session::ActiveRecordStore::Session.after_save_callback_chain.detect{|cb| cb.method == :enhanced_write_lobs}.nil?
1626
+ #:stopdoc:
1627
+ class CGI::Session::ActiveRecordStore::Session
1628
+ after_save :enhanced_write_lobs
1629
+ end
1630
+ #:startdoc:
1631
+ end
1632
+ end
1633
+
1634
+ # Load custom create, update, delete methods functionality
1635
+ require 'active_record/connection_adapters/oracle_enhanced_procedures'
1636
+
1637
+ # Load additional methods for composite_primary_keys support
1638
+ require 'active_record/connection_adapters/oracle_enhanced_cpk'
1639
+
1640
+ # Load patch for dirty tracking methods
1641
+ require 'active_record/connection_adapters/oracle_enhanced_dirty'
1642
+
1643
+ # Load rake tasks definitions
1644
+ begin
1645
+ require 'active_record/connection_adapters/oracle_enhanced_tasks'
1646
+ rescue LoadError
1647
+ end if defined?(RAILS_ROOT)
1648
+
1649
+ # Handles quoting of oracle reserved words
1650
+ require 'active_record/connection_adapters/oracle_enhanced_reserved_words'
1651
+
1652
+ # Patches and enhancements for schema dumper
1653
+ require 'active_record/connection_adapters/oracle_enhanced_schema_dumper'
1654
+
1655
+ # Extensions for schema definition statements
1656
+ require 'active_record/connection_adapters/oracle_enhanced_schema_statements_ext'
1657
+
1658
+ # Extensions for schema definition
1659
+ require 'active_record/connection_adapters/oracle_enhanced_schema_definitions'
1660
+
1661
+ # Add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1662
+ require 'active_record/connection_adapters/oracle_enhanced_core_ext'
1663
+
1664
+ require 'active_record/connection_adapters/oracle_enhanced_version'