activerecord-oracle_enhanced-adapter 1.2.1 → 1.2.2

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 (30) hide show
  1. data/History.txt +34 -0
  2. data/README.rdoc +10 -5
  3. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +1 -1
  4. data/lib/active_record/connection_adapters/oracle_enhanced.rake +4 -0
  5. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +534 -170
  6. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +53 -3
  7. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +10 -10
  8. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +3 -3
  9. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +3 -3
  10. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +86 -58
  11. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +105 -68
  12. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +27 -1
  13. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +164 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +122 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +2 -2
  17. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -1
  18. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +230 -455
  19. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +37 -1
  20. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +1 -1
  21. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +6 -2
  22. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +21 -4
  23. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +63 -0
  24. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +1 -1
  25. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +1 -3
  26. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +1 -1
  27. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +255 -0
  28. data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +720 -0
  29. data/spec/spec_helper.rb +38 -7
  30. metadata +13 -15
@@ -1,3 +1,37 @@
1
+ == 1.2.2 2009-09-28
2
+
3
+ * Enhancements
4
+ * improved RDoc documentation of public methods
5
+ * structure dump optionally (database.yml environment has db_stored_code: yes) extracts
6
+ packages, procedures, functions, views, triggers and synonyms
7
+ * automatically generated too long index names are shortened down to 30 characters
8
+ * create tables with primary key triggers
9
+ * use 'set_sequence_name :autogenerated' for inserting into legacy tables with trigger populated primary keys
10
+ * access to tables over database link (need to define local synonym to remote table and use local synonym in set_table_name)
11
+ * [JRuby] support JDBC connection using TNS_ADMIN environment variable and TNS database alias
12
+ * changed cursor_sharing option default from 'similar' to 'force'
13
+ * optional dbms_output logging to ActiveRecord log file (requires ruby-plsql gem)
14
+ * use add_foreign_key and remove_foreign_key to define foreign key constraints
15
+ (the same syntax as in http://github.com/matthuhiggins/foreigner and similar
16
+ to http://github.com/eyestreet/active_record_oracle_extensions)
17
+ * raise RecordNotUnique and InvalidForeignKey exceptions if caused by corresponding ORA errors
18
+ (these new exceptions are supported just by current ActiveRecord master branch)
19
+ * implemented disable_referential_integrity
20
+ (enables safe loading of fixtures in schema with foreign key constraints)
21
+ * use add_synonym and remove_synonym to define database synonyms
22
+ * add_foreign_key and add_synonym are also exported to schema.rb
23
+ * Bug fixes:
24
+ * [JRuby] do not raise LoadError if ojdbc14.jar cannot be required (rely on application server to add it to class path)
25
+ * [JRuby] 'execute' can be used to create triggers with :NEW reference
26
+ * support create_table without a block
27
+ * support create_table with Symbol table name
28
+ * use ActiveRecord functionality to do time zone conversion
29
+ * rake tasks such as db:test:clone are redefined only if oracle_enhanced is current adapter in use
30
+ * VARCHAR2 and CHAR column sizes are defined in characters and not in bytes (expected behavior from ActiveRecord)
31
+ * set_date_columns, set_datetime_columns, ignore_table_columns will work after reestablishing connection
32
+ * ignore :limit option for :text and :binary columns in migrations
33
+ * patches for ActiveRecord schema dumper to remove table prefixes and suffixes from schema.rb
34
+
1
35
  == 1.2.1 2009-06-07
2
36
 
3
37
  * Enhancements
@@ -1,13 +1,15 @@
1
- = activerecord-oracle_enhanced-adapter
1
+ = Activerecord Oracle enhanced adapter
2
2
 
3
- * http://rubyforge.org/projects/oracle-enhanced/
3
+ * http://github.com/rsim/oracle-enhanced
4
4
 
5
5
  == DESCRIPTION:
6
6
 
7
7
  Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases
8
8
  from Rails which are extracted from current real projects' monkey patches of original Oracle adapter.
9
9
 
10
- See http://github.com/rsim/oracle-enhanced/wikis for more information.
10
+ See http://github.com/rsim/oracle-enhanced/wikis for usage information.
11
+
12
+ See http://oracle-enhanced.rubyforge.org/rdoc for detailed API documentation.
11
13
 
12
14
  For questions and feature discussion please use http://groups.google.com/group/oracle-enhanced
13
15
 
@@ -22,12 +24,12 @@ Bugs and enhancement requests can be reported at http://rsim.lighthouseapp.com/p
22
24
  * MRI - requires ruby-oci8 1.x or 2.x library to connect to Oracle
23
25
  * Ruby/YARV 1.9.1 - requires ruby-oci8 2.x library to connect to Oracle
24
26
  unicode_utils gem is recommended for Unicode aware string upcase and downcase
25
- * JRuby - uses JDBC driver ojdbc14.jar to connect to Oracle (should be in JRUBY_HOME/lib or in PATH)
27
+ * JRuby - uses JDBC driver ojdbc14.jar to connect to Oracle (should be in JRUBY_HOME/lib or in Java class path)
26
28
  * Requires ruby-plsql gem to support custom create, update and delete methods (but can be used without ruby-plsql if this functionality is not needed)
27
29
 
28
30
  == INSTALL:
29
31
 
30
- * sudo gem install activerecord-oracle_enhanced-adapter
32
+ * [sudo] gem install activerecord-oracle_enhanced-adapter
31
33
 
32
34
  == CONTRIBUTORS:
33
35
 
@@ -39,6 +41,9 @@ Bugs and enhancement requests can be reported at http://rsim.lighthouseapp.com/p
39
41
  * Edgars Beigarts
40
42
  * Lachlan Laycock
41
43
  * toddwf
44
+ * Anton Jenkins
45
+ * Dave Smylie
46
+ * Alex Rothenberg
42
47
 
43
48
  == LICENSE:
44
49
 
@@ -1,4 +1,4 @@
1
- class ActiveRecord::ConnectionAdapters::OracleAdapter < ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
1
+ class ActiveRecord::ConnectionAdapters::OracleAdapter < ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter #:nodoc:
2
2
  def adapter_name
3
3
  'Oracle'
4
4
  end
@@ -20,6 +20,10 @@ namespace :db do
20
20
  if ActiveRecord::Base.connection.supports_migrations?
21
21
  File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
22
22
  end
23
+ if abcs[RAILS_ENV]['structure_dump'] == "db_stored_code"
24
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_db_stored_code }
25
+ end
26
+
23
27
  end
24
28
  end
25
29
 
@@ -32,9 +32,12 @@ require 'active_record/connection_adapters/abstract_adapter'
32
32
 
33
33
  require 'active_record/connection_adapters/oracle_enhanced_connection'
34
34
 
35
+ require 'digest/sha1'
36
+
35
37
  module ActiveRecord
36
38
  class Base
37
- def self.oracle_enhanced_connection(config)
39
+ # Establishes a connection to the database that's used by all Active Record objects.
40
+ def self.oracle_enhanced_connection(config) #:nodoc:
38
41
  if config[:emulate_oracle_adapter] == true
39
42
  # allows the enhanced adapter to look like the OracleAdapter. Useful to pick up
40
43
  # conditionals in the rails activerecord test suite
@@ -47,22 +50,30 @@ module ActiveRecord
47
50
  end
48
51
  end
49
52
 
50
- # RSI: specify table columns which should be ifnored
53
+ # Specify table columns which should be ignored by ActiveRecord, e.g.:
54
+ #
55
+ # ignore_table_columns :attribute1, :attribute2
51
56
  def self.ignore_table_columns(*args)
52
57
  connection.ignore_table_columns(table_name,*args)
53
58
  end
54
59
 
55
- # RSI: specify which table columns should be treated as date (without time)
60
+ # Specify which table columns should be typecasted to Date (without time), e.g.:
61
+ #
62
+ # set_date_columns :created_on, :updated_on
56
63
  def self.set_date_columns(*args)
57
64
  connection.set_type_for_columns(table_name,:date,*args)
58
65
  end
59
66
 
60
- # RSI: specify which table columns should be treated as datetime
67
+ # Specify which table columns should be typecasted to Time (or DateTime), e.g.:
68
+ #
69
+ # set_datetime_columns :created_date, :updated_date
61
70
  def self.set_datetime_columns(*args)
62
71
  connection.set_type_for_columns(table_name,:datetime,*args)
63
72
  end
64
73
 
65
- # RSI: specify which table columns should be treated as booleans
74
+ # Specify which table columns should be typecasted to boolean values +true+ or +false+, e.g.:
75
+ #
76
+ # set_boolean_columns :is_valid, :is_completed
66
77
  def self.set_boolean_columns(*args)
67
78
  connection.set_type_for_columns(table_name,:boolean,*args)
68
79
  end
@@ -79,7 +90,7 @@ module ActiveRecord
79
90
  private :enhanced_write_lobs
80
91
 
81
92
  class << self
82
- # RSI: patch ORDER BY to work with LOBs
93
+ # patch ORDER BY to work with LOBs
83
94
  def add_order_with_lobs!(sql, order, scope = :auto)
84
95
  if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter)
85
96
  order = connection.lob_order_by_expression(self, order) if order
@@ -98,11 +109,13 @@ module ActiveRecord
98
109
  add_order_without_lobs!(sql, order, scope = :auto)
99
110
  end
100
111
  private :add_order_with_lobs!
112
+ #:stopdoc:
101
113
  alias_method :add_order_without_lobs!, :add_order!
102
114
  alias_method :add_order!, :add_order_with_lobs!
115
+ #:startdoc:
103
116
  end
104
117
 
105
- # RSI: get table comment from schema definition
118
+ # Get table comment from schema definition.
106
119
  def self.table_comment
107
120
  connection.table_comment(self.table_name)
108
121
  end
@@ -110,24 +123,24 @@ module ActiveRecord
110
123
 
111
124
 
112
125
  module ConnectionAdapters #:nodoc:
113
- class OracleEnhancedColumn < Column #:nodoc:
126
+ class OracleEnhancedColumn < Column
114
127
 
115
- attr_reader :table_name, :forced_column_type
128
+ attr_reader :table_name, :forced_column_type #:nodoc:
116
129
 
117
- def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil)
130
+ def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil) #:nodoc:
118
131
  @table_name = table_name
119
132
  @forced_column_type = forced_column_type
120
133
  super(name, default, sql_type, null)
121
134
  end
122
135
 
123
- def type_cast(value)
136
+ def type_cast(value) #:nodoc:
124
137
  return guess_date_or_time(value) if type == :datetime && OracleEnhancedAdapter.emulate_dates
125
138
  super
126
139
  end
127
140
 
128
141
  # convert something to a boolean
129
- # RSI: added y as boolean value
130
- def self.value_to_boolean(value)
142
+ # added y as boolean value
143
+ def self.value_to_boolean(value) #:nodoc:
131
144
  if value == true || value == false
132
145
  value
133
146
  elsif value.is_a?(String) && value.blank?
@@ -137,20 +150,20 @@ module ActiveRecord
137
150
  end
138
151
  end
139
152
 
140
- # RSI: convert Time or DateTime value to Date for :date columns
141
- def self.string_to_date(string)
153
+ # convert Time or DateTime value to Date for :date columns
154
+ def self.string_to_date(string) #:nodoc:
142
155
  return string.to_date if string.is_a?(Time) || string.is_a?(DateTime)
143
156
  super
144
157
  end
145
158
 
146
- # RSI: convert Date value to Time for :datetime columns
147
- def self.string_to_time(string)
159
+ # convert Date value to Time for :datetime columns
160
+ def self.string_to_time(string) #:nodoc:
148
161
  return string.to_time if string.is_a?(Date) && !OracleEnhancedAdapter.emulate_dates
149
162
  super
150
163
  end
151
164
 
152
- # RSI: get column comment from schema definition
153
- # will work only if using default ActiveRecord connection
165
+ # Get column comment from schema definition.
166
+ # Will work only if using default ActiveRecord connection.
154
167
  def comment
155
168
  ActiveRecord::Base.connection.column_comment(@table_name, name)
156
169
  end
@@ -171,7 +184,7 @@ module ActiveRecord
171
184
  when /time/i then :datetime
172
185
  when /decimal|numeric|number/i
173
186
  return :integer if extract_scale(field_type) == 0
174
- # RSI: if column name is ID or ends with _ID
187
+ # if column name is ID or ends with _ID
175
188
  return :integer if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name)
176
189
  :decimal
177
190
  else super
@@ -186,21 +199,21 @@ module ActiveRecord
186
199
  class <<self
187
200
  protected
188
201
 
189
- def fallback_string_to_date(string)
202
+ def fallback_string_to_date(string) #:nodoc:
190
203
  if OracleEnhancedAdapter.string_to_date_format || OracleEnhancedAdapter.string_to_time_format
191
204
  return (string_to_date_or_time_using_format(string).to_date rescue super)
192
205
  end
193
206
  super
194
207
  end
195
208
 
196
- def fallback_string_to_time(string)
209
+ def fallback_string_to_time(string) #:nodoc:
197
210
  if OracleEnhancedAdapter.string_to_time_format || OracleEnhancedAdapter.string_to_date_format
198
211
  return (string_to_date_or_time_using_format(string).to_time rescue super)
199
212
  end
200
213
  super
201
214
  end
202
215
 
203
- def string_to_date_or_time_using_format(string)
216
+ def string_to_date_or_time_using_format(string) #:nodoc:
204
217
  if OracleEnhancedAdapter.string_to_time_format && dt=Date._strptime(string, OracleEnhancedAdapter.string_to_time_format)
205
218
  return Time.mktime(*dt.values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :wday))
206
219
  end
@@ -211,54 +224,94 @@ module ActiveRecord
211
224
  end
212
225
 
213
226
 
214
- # This is an Oracle/OCI adapter for the ActiveRecord persistence
215
- # framework. It relies upon the OCI8 driver, which works with Oracle 8i
216
- # and above. Most recent development has been on Debian Linux against
217
- # a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13.
218
- # See: http://rubyforge.org/projects/ruby-oci8/
227
+ # Oracle enhanced adapter will work with both
228
+ # Ruby 1.8/1.9 ruby-oci8 gem (which provides interface to Oracle OCI client)
229
+ # or with JRuby and Oracle JDBC driver.
230
+ #
231
+ # It should work with Oracle 9i, 10g and 11g databases.
232
+ # Limited set of functionality should work on Oracle 8i as well but several features
233
+ # rely on newer functionality in Oracle database.
219
234
  #
220
235
  # Usage notes:
221
236
  # * Key generation assumes a "${table_name}_seq" sequence is available
222
237
  # for all tables; the sequence name can be changed using
223
238
  # ActiveRecord::Base.set_sequence_name. When using Migrations, these
224
239
  # sequences are created automatically.
240
+ # Use set_sequence_name :autogenerated with legacy tables that have
241
+ # triggers that populate primary keys automatically.
225
242
  # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
226
243
  # Consequently some hacks are employed to map data back to Date or Time
227
- # in Ruby. If the column_name ends in _time it's created as a Ruby Time.
228
- # Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else
229
- # it's a Ruby Time. This is a bit nasty - but if you use Duck Typing
230
- # you'll probably not care very much. In 9i and up it's tempting to
231
- # map DATE to Date and TIMESTAMP to Time, but too many databases use
232
- # DATE for both. Timezones and sub-second precision on timestamps are
244
+ # in Ruby. Timezones and sub-second precision on timestamps are
233
245
  # not supported.
234
246
  # * Default values that are functions (such as "SYSDATE") are not
235
247
  # supported. This is a restriction of the way ActiveRecord supports
236
248
  # default values.
237
- # * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which
238
- # is supported in Oracle9i and later. You will need to use #finder_sql for
239
- # has_and_belongs_to_many associations to run against Oracle8.
240
249
  #
241
250
  # Required parameters:
242
251
  #
243
252
  # * <tt>:username</tt>
244
253
  # * <tt>:password</tt>
245
- # * <tt>:database</tt>
254
+ # * <tt>:database</tt> - either TNS alias or connection string for OCI client or database name in JDBC connection string
255
+ #
256
+ # Optional parameters:
257
+ #
258
+ # * <tt>:host</tt> - host name for JDBC connection, defaults to "localhost"
259
+ # * <tt>:port</tt> - port number for JDBC connection, defaults to 1521
260
+ # * <tt>:privilege</tt> - set "SYSDBA" if you want to connect with this privilege
261
+ # * <tt>:allow_concurrency</tt> - set to "true" if non-blocking mode should be enabled (just for OCI client)
262
+ # * <tt>:prefetch_rows</tt> - how many rows should be fetched at one time to increase performance, defaults to 100
263
+ # * <tt>:cursor_sharing</tt> - cursor sharing mode to minimize amount of unique statements, defaults to "force"
264
+ # * <tt>:nls_length_semantics</tt> - semantics of size of VARCHAR2 and CHAR columns, defaults to "CHAR"
265
+ # (meaning that size specifies number of characters and not bytes)
246
266
  class OracleEnhancedAdapter < AbstractAdapter
247
267
 
248
- @@emulate_booleans = true
268
+ ##
269
+ # :singleton-method:
270
+ # By default, the OracleEnhancedAdapter will consider all columns of type <tt>NUMBER(1)</tt>
271
+ # as boolean. If you wish to disable this emulation you can add the following line
272
+ # to your initializer file:
273
+ #
274
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = false
249
275
  cattr_accessor :emulate_booleans
250
-
251
- @@emulate_dates = false
276
+ self.emulate_booleans = true
277
+
278
+ ##
279
+ # :singleton-method:
280
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
281
+ # to Time or DateTime (if value is out of Time value range) value.
282
+ # If you wish that DATE values with hour, minutes and seconds equal to 0 are typecasted
283
+ # to Date then you can add the following line to your initializer file:
284
+ #
285
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates = true
286
+ #
287
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
288
+ # that Date columns are explicily defined with +set_date_columns+ method.
252
289
  cattr_accessor :emulate_dates
253
-
254
- # RSI: set to true if columns with DATE in their name should be emulated as date
255
- @@emulate_dates_by_column_name = false
290
+ self.emulate_dates = false
291
+
292
+ ##
293
+ # :singleton-method:
294
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
295
+ # to Time or DateTime (if value is out of Time value range) value.
296
+ # If you wish that DATE columns with "date" in their name (e.g. "creation_date") are typecasted
297
+ # to Date then you can add the following line to your initializer file:
298
+ #
299
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
300
+ #
301
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
302
+ # that Date columns are explicily defined with +set_date_columns+ method.
256
303
  cattr_accessor :emulate_dates_by_column_name
304
+ self.emulate_dates_by_column_name = false
305
+
306
+ # Check column name to identify if it is Date (and not Time) column.
307
+ # Is used if +emulate_dates_by_column_name+ option is set to +true+.
308
+ # Override this method definition in initializer file if different Date column recognition is needed.
257
309
  def self.is_date_column?(name, table_name = nil)
258
310
  name =~ /(^|_)date(_|$)/i
259
311
  end
260
- # RSI: instance method uses at first check if column type defined at class level
261
- def is_date_column?(name, table_name = nil)
312
+
313
+ # instance method uses at first check if column type defined at class level
314
+ def is_date_column?(name, table_name = nil) #:nodoc:
262
315
  case get_type_for_column(table_name, name)
263
316
  when nil
264
317
  self.class.is_date_column?(name, table_name)
@@ -269,28 +322,63 @@ module ActiveRecord
269
322
  end
270
323
  end
271
324
 
272
- # RSI: set to true if NUMBER columns with ID at the end of their name should be emulated as integers
273
- @@emulate_integers_by_column_name = false
325
+ ##
326
+ # :singleton-method:
327
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>NUMBER</tt>
328
+ # (without precision or scale) to Float or BigDecimal value.
329
+ # If you wish that NUMBER columns with name "id" or that end with "_id" are typecasted
330
+ # to Integer then you can add the following line to your initializer file:
331
+ #
332
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true
274
333
  cattr_accessor :emulate_integers_by_column_name
334
+ self.emulate_integers_by_column_name = false
335
+
336
+ # Check column name to identify if it is Integer (and not Float or BigDecimal) column.
337
+ # Is used if +emulate_integers_by_column_name+ option is set to +true+.
338
+ # Override this method definition in initializer file if different Integer column recognition is needed.
275
339
  def self.is_integer_column?(name, table_name = nil)
276
340
  name =~ /(^|_)id$/i
277
341
  end
278
342
 
279
- # RSI: set to true if CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
280
- # should be emulated as booleans
281
- @@emulate_booleans_from_strings = false
343
+ ##
344
+ # :singleton-method:
345
+ # If you wish that CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
346
+ # are typecasted to booleans then you can add the following line to your initializer file:
347
+ #
348
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans_from_strings = true
282
349
  cattr_accessor :emulate_booleans_from_strings
350
+ self.emulate_booleans_from_strings = false
351
+
352
+ # Check column name to identify if it is boolean (and not String) column.
353
+ # Is used if +emulate_booleans_from_strings+ option is set to +true+.
354
+ # Override this method definition in initializer file if different boolean column recognition is needed.
283
355
  def self.is_boolean_column?(name, field_type, table_name = nil)
284
356
  return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type)
285
357
  field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i)
286
358
  end
359
+
360
+ # How boolean value should be quoted to String.
361
+ # Used if +emulate_booleans_from_strings+ option is set to +true+.
287
362
  def self.boolean_to_string(bool)
288
363
  bool ? "Y" : "N"
289
364
  end
290
365
 
291
- # RSI: use to set NLS specific date formats which will be used when assigning string to :date and :datetime columns
292
- @@string_to_date_format = @@string_to_time_format = nil
293
- cattr_accessor :string_to_date_format, :string_to_time_format
366
+ # Specify non-default date format that should be used when assigning string values to :date columns, e.g.:
367
+ #
368
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_date_format = “%d.%m.%Y”
369
+ cattr_accessor :string_to_date_format
370
+ self.string_to_date_format = nil
371
+
372
+ # Specify non-default time format that should be used when assigning string values to :datetime columns, e.g.:
373
+ #
374
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_time_format = “%d.%m.%Y %H:%M:%S”
375
+ cattr_accessor :string_to_time_format
376
+ self.string_to_time_format = nil
377
+
378
+ def initialize(connection, logger = nil) #:nodoc:
379
+ super
380
+ @quoted_column_names, @quoted_table_names = {}, {}
381
+ end
294
382
 
295
383
  def adapter_name #:nodoc:
296
384
  'OracleEnhanced'
@@ -309,53 +397,48 @@ module ActiveRecord
309
397
  :float => { :name => "NUMBER" },
310
398
  :decimal => { :name => "DECIMAL" },
311
399
  :datetime => { :name => "DATE" },
312
- # RSI: changed to native TIMESTAMP type
400
+ # changed to native TIMESTAMP type
313
401
  # :timestamp => { :name => "DATE" },
314
402
  :timestamp => { :name => "TIMESTAMP" },
315
403
  :time => { :name => "DATE" },
316
404
  :date => { :name => "DATE" },
317
405
  :binary => { :name => "BLOB" },
318
- # RSI: if emulate_booleans_from_strings then store booleans in VARCHAR2
406
+ # if emulate_booleans_from_strings then store booleans in VARCHAR2
319
407
  :boolean => emulate_booleans_from_strings ?
320
408
  { :name => "VARCHAR2", :limit => 1 } : { :name => "NUMBER", :limit => 1 }
321
409
  }
322
410
  end
323
411
 
324
- def table_alias_length
325
- 30
326
- end
412
+ # maximum length of Oracle identifiers
413
+ IDENTIFIER_MAX_LENGTH = 30
327
414
 
328
- # Returns an array of arrays containing the field values.
329
- # Order is the same as that returned by #columns.
330
- def select_rows(sql, name = nil)
331
- # last parameter indicates to return also column list
332
- result, columns = select(sql, name, true)
333
- result.map{ |v| columns.map{|c| v[c]} }
415
+ def table_alias_length #:nodoc:
416
+ IDENTIFIER_MAX_LENGTH
334
417
  end
335
418
 
336
419
  # QUOTING ==================================================
337
420
  #
338
421
  # see: abstract/quoting.rb
339
422
 
340
- # camelCase column names need to be quoted; not that anyone using Oracle
341
- # would really do this, but handling this case means we pass the test...
342
423
  def quote_column_name(name) #:nodoc:
343
- name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : quote_oracle_reserved_words(name)
424
+ # camelCase column names need to be quoted; not that anyone using Oracle
425
+ # would really do this, but handling this case means we pass the test...
426
+ @quoted_column_names[name] = name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : quote_oracle_reserved_words(name)
344
427
  end
345
428
 
346
429
  # unescaped table name should start with letter and
347
430
  # contain letters, digits, _, $ or #
348
431
  # can be prefixed with schema name
349
432
  # CamelCase table names should be quoted
350
- def self.valid_table_name?(name)
433
+ def self.valid_table_name?(name) #:nodoc:
351
434
  name = name.to_s
352
- name =~ /^([A-Za-z_0-9]+\.)?[a-z][a-z_0-9\$#]*$/ ||
353
- name =~ /^([A-Za-z_0-9]+\.)?[A-Z][A-Z_0-9\$#]*$/ ? true : false
435
+ name =~ /\A([A-Za-z_0-9]+\.)?[a-z][a-z_0-9\$#]*(@[A-Za-z_0-9\.]+)?\Z/ ||
436
+ name =~ /\A([A-Za-z_0-9]+\.)?[A-Z][A-Z_0-9\$#]*(@[A-Za-z_0-9\.]+)?\Z/ ? true : false
354
437
  end
355
438
 
356
- # abstract_adapter calls quote_column_name from quote_table_name, so prevent that
357
- def quote_table_name(name)
358
- if self.class.valid_table_name?(name)
439
+ def quote_table_name(name) #:nodoc:
440
+ # abstract_adapter calls quote_column_name from quote_table_name, so prevent that
441
+ @quoted_table_names[name] ||= if self.class.valid_table_name?(name)
359
442
  name
360
443
  else
361
444
  "\"#{name}\""
@@ -371,10 +454,10 @@ module ActiveRecord
371
454
  case column.type
372
455
  when :text, :binary
373
456
  %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
374
- # RSI: TIMESTAMP support
457
+ # NLS_DATE_FORMAT independent TIMESTAMP support
375
458
  when :timestamp
376
459
  quote_timestamp_with_to_timestamp(value)
377
- # RSI: NLS_DATE_FORMAT independent DATE support
460
+ # NLS_DATE_FORMAT independent DATE support
378
461
  when :date, :time, :datetime
379
462
  quote_date_with_to_date(value)
380
463
  else
@@ -389,25 +472,25 @@ module ActiveRecord
389
472
  end
390
473
  end
391
474
 
392
- def quoted_true
475
+ def quoted_true #:nodoc:
393
476
  return "'#{self.class.boolean_to_string(true)}'" if emulate_booleans_from_strings
394
477
  "1"
395
478
  end
396
479
 
397
- def quoted_false
480
+ def quoted_false #:nodoc:
398
481
  return "'#{self.class.boolean_to_string(false)}'" if emulate_booleans_from_strings
399
482
  "0"
400
483
  end
401
484
 
402
- # RSI: should support that composite_primary_keys gem will pass date as string
403
- def quote_date_with_to_date(value)
404
- value = value.to_s(:db) if value.acts_like?(:date) || value.acts_like?(:time)
485
+ def quote_date_with_to_date(value) #:nodoc:
486
+ # should support that composite_primary_keys gem will pass date as string
487
+ value = quoted_date(value) if value.acts_like?(:date) || value.acts_like?(:time)
405
488
  "TO_DATE('#{value}','YYYY-MM-DD HH24:MI:SS')"
406
489
  end
407
490
 
408
- def quote_timestamp_with_to_timestamp(value)
491
+ def quote_timestamp_with_to_timestamp(value) #:nodoc:
409
492
  # add up to 9 digits of fractional seconds to inserted time
410
- value = "#{value.to_s(:db)}.#{("%.6f"%value.to_f).split('.')[1]}" if value.acts_like?(:time)
493
+ value = "#{quoted_date(value)}.#{("%.6f"%value.to_f).split('.')[1]}" if value.acts_like?(:time)
411
494
  "TO_TIMESTAMP('#{value}','YYYY-MM-DD HH24:MI:SS.FF6')"
412
495
  end
413
496
 
@@ -417,19 +500,21 @@ module ActiveRecord
417
500
  # If SQL statement fails due to lost connection then reconnect
418
501
  # and retry SQL statement if autocommit mode is enabled.
419
502
  # By default this functionality is disabled.
503
+ attr_reader :auto_retry #:nodoc:
420
504
  @auto_retry = false
421
- attr_reader :auto_retry
422
- def auto_retry=(value)
505
+
506
+ def auto_retry=(value) #:nodoc:
423
507
  @auto_retry = value
424
508
  @connection.auto_retry = value if @connection
425
509
  end
426
510
 
511
+ # return raw OCI8 or JDBC connection
427
512
  def raw_connection
428
513
  @connection.raw_connection
429
514
  end
430
515
 
431
516
  # Returns true if the connection is active.
432
- def active?
517
+ def active? #:nodoc:
433
518
  # Pings the connection to check if it's still good. Note that an
434
519
  # #active? method is also available, but that simply returns the
435
520
  # last known state, which isn't good enough if the connection has
@@ -440,31 +525,61 @@ module ActiveRecord
440
525
  end
441
526
 
442
527
  # Reconnects to the database.
443
- def reconnect!
528
+ def reconnect! #:nodoc:
444
529
  @connection.reset!
445
530
  rescue OracleEnhancedConnectionException => e
446
- @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
531
+ @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger
447
532
  end
448
533
 
449
534
  # Disconnects from the database.
450
- def disconnect!
535
+ def disconnect! #:nodoc:
451
536
  @connection.logoff rescue nil
452
537
  end
453
538
 
454
-
455
539
  # DATABASE STATEMENTS ======================================
456
540
  #
457
541
  # see: abstract/database_statements.rb
458
542
 
459
- def execute(sql, name = nil) #:nodoc:
460
- log(sql, name) { @connection.exec sql }
543
+ # Executes a SQL statement
544
+ def execute(sql, name = nil)
545
+ # hack to pass additional "with_returning" option without changing argument list
546
+ log(sql, name) { sql.instance_variable_get(:@with_returning) ? @connection.exec_with_returning(sql) : @connection.exec(sql) }
461
547
  end
462
548
 
549
+ # Returns an array of arrays containing the field values.
550
+ # Order is the same as that returned by #columns.
551
+ def select_rows(sql, name = nil)
552
+ # last parameter indicates to return also column list
553
+ result, columns = select(sql, name, true)
554
+ result.map{ |v| columns.map{|c| v[c]} }
555
+ end
556
+
557
+ # Executes an INSERT statement and returns the new record's ID
558
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
559
+ # if primary key value is already prefetched from sequence
560
+ # or if there is no primary key
561
+ if id_value || pk.nil?
562
+ execute(sql, name)
563
+ return id_value
564
+ end
565
+
566
+ sql_with_returning = sql.dup << @connection.returning_clause(quote_column_name(pk))
567
+ # hack to pass additional "with_returning" option without changing argument list
568
+ sql_with_returning.instance_variable_set(:@with_returning, true)
569
+ clear_query_cache
570
+ execute(sql_with_returning, name)
571
+ end
572
+ protected :insert_sql
573
+
574
+ AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze #:nodoc:
575
+
463
576
  # Returns the next sequence value from a sequence generator. Not generally
464
577
  # called directly; used by ActiveRecord to get the next primary key value
465
578
  # when inserting a new database record (see #prefetch_primary_key?).
466
579
  def next_sequence_value(sequence_name)
467
- select_one("select #{sequence_name}.nextval id from dual")['id']
580
+ # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
581
+ return nil if sequence_name == AUTOGENERATED_SEQUENCE_NAME
582
+ select_one("SELECT #{quote_table_name(sequence_name)}.NEXTVAL id FROM dual")['id']
468
583
  end
469
584
 
470
585
  def begin_db_transaction #:nodoc:
@@ -484,7 +599,7 @@ module ActiveRecord
484
599
  end
485
600
 
486
601
  def add_limit_offset!(sql, options) #:nodoc:
487
- # RSI: added to_i for limit and offset to protect from SQL injection
602
+ # added to_i for limit and offset to protect from SQL injection
488
603
  offset = (options[:offset] || 0).to_i
489
604
 
490
605
  if limit = options[:limit]
@@ -495,19 +610,29 @@ module ActiveRecord
495
610
  end
496
611
  end
497
612
 
613
+ @@do_not_prefetch_primary_key = {}
614
+
498
615
  # Returns true for Oracle adapter (since Oracle requires primary key
499
616
  # values to be pre-fetched before insert). See also #next_sequence_value.
500
617
  def prefetch_primary_key?(table_name = nil)
501
- true
618
+ ! @@do_not_prefetch_primary_key[table_name.to_s]
502
619
  end
503
620
 
504
- def default_sequence_name(table, column) #:nodoc:
505
- quote_table_name("#{table}_seq")
621
+ # used just in tests to clear prefetch primary key flag for all tables
622
+ def clear_prefetch_primary_key #:nodoc:
623
+ @@do_not_prefetch_primary_key = {}
506
624
  end
507
625
 
626
+ # Returns default sequence name for table.
627
+ # Will take all or first 26 characters of table name and append _seq suffix
628
+ def default_sequence_name(table_name, primary_key = nil)
629
+ # TODO: remove schema prefix if present before truncating
630
+ # truncate table name if necessary to fit in max length of identifier
631
+ "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_seq"
632
+ end
508
633
 
509
634
  # Inserts the given fixture into the table. Overridden to properly handle lobs.
510
- def insert_fixture(fixture, table_name)
635
+ def insert_fixture(fixture, table_name) #:nodoc:
511
636
  super
512
637
 
513
638
  klass = fixture.class_name.constantize rescue nil
@@ -517,7 +642,7 @@ module ActiveRecord
517
642
  end
518
643
 
519
644
  # Writes LOB values from attributes, as indicated by the LOB columns of klass.
520
- def write_lobs(table_name, klass, attributes)
645
+ def write_lobs(table_name, klass, attributes) #:nodoc:
521
646
  # is class with composite primary key>
522
647
  is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
523
648
  if is_with_cpk
@@ -527,7 +652,7 @@ module ActiveRecord
527
652
  end
528
653
  klass.columns.select { |col| col.sql_type =~ /LOB$/i }.each do |col|
529
654
  value = attributes[col.name]
530
- # RSI: changed sequence of next two lines - should check if value is nil before converting to yaml
655
+ # changed sequence of next two lines - should check if value is nil before converting to yaml
531
656
  next if value.nil? || (value == '')
532
657
  value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
533
658
  uncached do
@@ -538,14 +663,14 @@ module ActiveRecord
538
663
  lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE",
539
664
  'Writable Large Object')[col.name]
540
665
  end
541
- @connection.write_lob(lob, value, col.type == :binary)
666
+ @connection.write_lob(lob, value.to_s, col.type == :binary)
542
667
  end
543
668
  end
544
669
  end
545
670
 
546
- # RSI: change LOB column for ORDER BY clause
671
+ # change LOB column for ORDER BY clause
547
672
  # just first 100 characters are taken for ordering
548
- def lob_order_by_expression(klass, order)
673
+ def lob_order_by_expression(klass, order) #:nodoc:
549
674
  return order if order.nil?
550
675
  changed = false
551
676
  new_order = order.to_s.strip.split(/, */).map do |order_by_col|
@@ -568,21 +693,21 @@ module ActiveRecord
568
693
  select_one("select sys_context('userenv','db_name') db from dual")["db"]
569
694
  end
570
695
 
571
- # RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
572
696
  def tables(name = nil) #:nodoc:
697
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
573
698
  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']}
574
699
  end
575
700
 
576
- cattr_accessor :all_schema_indexes
701
+ cattr_accessor :all_schema_indexes #:nodoc:
577
702
 
578
703
  # This method selects all indexes at once, and caches them in a class variable.
579
704
  # Subsequent index calls get them from the variable, without going to the DB.
580
- def indexes(table_name, name = nil)
581
- (owner, table_name) = @connection.describe(table_name)
705
+ def indexes(table_name, name = nil) #:nodoc:
706
+ (owner, table_name, db_link) = @connection.describe(table_name)
582
707
  unless all_schema_indexes
583
708
  result = select_all(<<-SQL)
584
709
  SELECT lower(i.table_name) as table_name, lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
585
- FROM all_indexes i, all_ind_columns c
710
+ FROM all_indexes#{db_link} i, all_ind_columns#{db_link} c
586
711
  WHERE i.owner = '#{owner}'
587
712
  AND i.table_owner = '#{owner}'
588
713
  AND c.index_name = i.index_name
@@ -611,57 +736,88 @@ module ActiveRecord
611
736
  all_schema_indexes.select{|i| i.table == table_name}
612
737
  end
613
738
 
614
- # RSI: set ignored columns for table
615
- def ignore_table_columns(table_name, *args)
616
- @ignore_table_columns ||= {}
617
- @ignore_table_columns[table_name] ||= []
618
- @ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
619
- @ignore_table_columns[table_name].uniq!
739
+ @@ignore_table_columns = nil #:nodoc:
740
+
741
+ # set ignored columns for table
742
+ def ignore_table_columns(table_name, *args) #:nodoc:
743
+ @@ignore_table_columns ||= {}
744
+ @@ignore_table_columns[table_name] ||= []
745
+ @@ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
746
+ @@ignore_table_columns[table_name].uniq!
620
747
  end
621
748
 
622
- def ignored_table_columns(table_name)
623
- @ignore_table_columns ||= {}
624
- @ignore_table_columns[table_name]
749
+ def ignored_table_columns(table_name) #:nodoc:
750
+ @@ignore_table_columns ||= {}
751
+ @@ignore_table_columns[table_name]
625
752
  end
626
753
 
627
- # RSI: set explicit type for specified table columns
628
- def set_type_for_columns(table_name, column_type, *args)
629
- @table_column_type ||= {}
630
- @table_column_type[table_name] ||= {}
754
+ # used just in tests to clear ignored table columns
755
+ def clear_ignored_table_columns #:nodoc:
756
+ @@ignore_table_columns = nil
757
+ end
758
+
759
+ @@table_column_type = nil #:nodoc:
760
+
761
+ # set explicit type for specified table columns
762
+ def set_type_for_columns(table_name, column_type, *args) #:nodoc:
763
+ @@table_column_type ||= {}
764
+ @@table_column_type[table_name] ||= {}
631
765
  args.each do |col|
632
- @table_column_type[table_name][col.to_s.downcase] = column_type
766
+ @@table_column_type[table_name][col.to_s.downcase] = column_type
633
767
  end
634
768
  end
635
769
 
636
- def get_type_for_column(table_name, column_name)
637
- @table_column_type && @table_column_type[table_name] && @table_column_type[table_name][column_name.to_s.downcase]
770
+ def get_type_for_column(table_name, column_name) #:nodoc:
771
+ @@table_column_type && @@table_column_type[table_name] && @@table_column_type[table_name][column_name.to_s.downcase]
638
772
  end
639
773
 
640
- def clear_types_for_columns
641
- @table_column_type = nil
774
+ # used just in tests to clear column data type definitions
775
+ def clear_types_for_columns #:nodoc:
776
+ @@table_column_type = nil
777
+ end
778
+
779
+ # check if table has primary key trigger with _pkt suffix
780
+ def has_primary_key_trigger?(table_name, owner = nil, desc_table_name = nil, db_link = nil)
781
+ (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
782
+
783
+ trigger_name = default_trigger_name(table_name).upcase
784
+ pkt_sql = <<-SQL
785
+ SELECT trigger_name
786
+ FROM all_triggers#{db_link}
787
+ WHERE owner = '#{owner}'
788
+ AND trigger_name = '#{trigger_name}'
789
+ AND table_owner = '#{owner}'
790
+ AND table_name = '#{desc_table_name}'
791
+ AND status = 'ENABLED'
792
+ SQL
793
+ select_value(pkt_sql) ? true : false
642
794
  end
643
795
 
644
796
  def columns(table_name, name = nil) #:nodoc:
645
- # RSI: get ignored_columns by original table name
797
+ # get ignored_columns by original table name
646
798
  ignored_columns = ignored_table_columns(table_name)
647
799
 
648
- (owner, desc_table_name) = @connection.describe(table_name)
800
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
801
+
802
+ if has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
803
+ @@do_not_prefetch_primary_key[table_name] = true
804
+ end
649
805
 
650
806
  table_cols = <<-SQL
651
807
  select column_name as name, data_type as sql_type, data_default, nullable,
652
808
  decode(data_type, 'NUMBER', data_precision,
653
809
  'FLOAT', data_precision,
654
- 'VARCHAR2', data_length,
655
- 'CHAR', data_length,
810
+ 'VARCHAR2', decode(char_used, 'C', char_length, data_length),
811
+ 'CHAR', decode(char_used, 'C', char_length, data_length),
656
812
  null) as limit,
657
813
  decode(data_type, 'NUMBER', data_scale, null) as scale
658
- from all_tab_columns
814
+ from all_tab_columns#{db_link}
659
815
  where owner = '#{owner}'
660
816
  and table_name = '#{desc_table_name}'
661
817
  order by column_id
662
818
  SQL
663
819
 
664
- # RSI: added deletion of ignored columns
820
+ # added deletion of ignored columns
665
821
  select_all(table_cols, name).delete_if do |row|
666
822
  ignored_columns && ignored_columns.include?(row['name'].downcase)
667
823
  end.map do |row|
@@ -681,18 +837,52 @@ module ActiveRecord
681
837
  row['data_default'],
682
838
  row['sql_type'],
683
839
  row['nullable'] == 'Y',
684
- # RSI: pass table name for table specific column definitions
840
+ # pass table name for table specific column definitions
685
841
  table_name,
686
- # RSI: pass column type if specified in class definition
842
+ # pass column type if specified in class definition
687
843
  get_type_for_column(table_name, oracle_downcase(row['name'])))
688
844
  end
689
845
  end
690
846
 
691
- # RSI: default sequence start with value
692
- @@default_sequence_start_value = 10000
847
+ ##
848
+ # :singleton-method:
849
+ # Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
850
+ #
851
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 1
693
852
  cattr_accessor :default_sequence_start_value
853
+ self.default_sequence_start_value = 10000
694
854
 
695
- def create_table(name, options = {}, &block) #:nodoc:
855
+ # Additional options for +create_table+ method in migration files.
856
+ #
857
+ # You can specify individual starting value in table creation migration file, e.g.:
858
+ #
859
+ # create_table :users, :sequence_start_value => 100 do |t|
860
+ # # ...
861
+ # end
862
+ #
863
+ # You can also specify other sequence definition additional parameters, e.g.:
864
+ #
865
+ # create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t|
866
+ # # ...
867
+ # end
868
+ #
869
+ # Create primary key trigger (so that you can skip primary key value in INSERT statement).
870
+ # By default trigger name will be "table_name_pkt", you can override the name with
871
+ # :trigger_name option (but it is not recommended to override it as then this trigger will
872
+ # not be detected by ActiveRecord model and it will still do prefetching of sequence value).
873
+ # Example:
874
+ #
875
+ # create_table :users, :primary_key_trigger => true do |t|
876
+ # # ...
877
+ # end
878
+ #
879
+ # It is possible to add table and column comments in table creation migration files:
880
+ #
881
+ # create_table :employees, :comment => “Employees and contractors” do |t|
882
+ # t.string :first_name, :comment => “Given name”
883
+ # t.string :last_name, :comment => “Surname”
884
+ # end
885
+ def create_table(name, options = {}, &block)
696
886
  create_sequence = options[:id] != false
697
887
  column_comments = {}
698
888
  super(name, options) do |t|
@@ -719,14 +909,12 @@ module ActiveRecord
719
909
  end
720
910
  end
721
911
 
722
- result = block.call(t)
912
+ result = block.call(t) if block
723
913
  create_sequence = create_sequence || t.create_sequence
724
914
  column_comments = t.column_comments if t.column_comments
725
915
  end
726
916
 
727
- seq_name = options[:sequence_name] || quote_table_name("#{name}_seq")
728
- seq_start_value = options[:sequence_start_value] || default_sequence_start_value
729
- execute "CREATE SEQUENCE #{seq_name} START WITH #{seq_start_value}" if create_sequence
917
+ create_sequence_and_trigger(name, options) if create_sequence
730
918
 
731
919
  add_table_comment name, options[:comment]
732
920
  column_comments.each do |column_name, comment|
@@ -742,12 +930,12 @@ module ActiveRecord
742
930
 
743
931
  def drop_table(name, options = {}) #:nodoc:
744
932
  super(name)
745
- seq_name = options[:sequence_name] || quote_table_name("#{name}_seq")
746
- execute "DROP SEQUENCE #{seq_name}" rescue nil
933
+ seq_name = options[:sequence_name] || default_sequence_name(name)
934
+ execute "DROP SEQUENCE #{quote_table_name(seq_name)}" rescue nil
747
935
  end
748
936
 
749
937
  # clear cached indexes when adding new index
750
- def add_index(table_name, column_name, options = {})
938
+ def add_index(table_name, column_name, options = {}) #:nodoc:
751
939
  self.all_schema_indexes = nil
752
940
  super
753
941
  end
@@ -757,8 +945,28 @@ module ActiveRecord
757
945
  self.all_schema_indexes = nil
758
946
  execute "DROP INDEX #{index_name(table_name, options)}"
759
947
  end
948
+
949
+ # returned shortened index name if default is too large
950
+ def index_name(table_name, options) #:nodoc:
951
+ default_name = super(table_name, options)
952
+ return default_name if default_name.length <= IDENTIFIER_MAX_LENGTH
953
+
954
+ # remove 'index', 'on' and 'and' keywords
955
+ shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
956
+
957
+ # leave just first three letters from each word
958
+ if shortened_name.length > IDENTIFIER_MAX_LENGTH
959
+ shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
960
+ end
961
+ # generate unique name using hash function
962
+ if shortened_name.length > OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH
963
+ shortened_name = 'i'+Digest::SHA1.hexdigest(default_name)[0,OracleEnhancedAdapter::IDENTIFIER_MAX_LENGTH-1]
964
+ end
965
+ @logger.warn "#{adapter_name} shortened default index name #{default_name} to #{shortened_name}" if @logger
966
+ shortened_name
967
+ end
760
968
 
761
- def add_column(table_name, column_name, type, options = {})
969
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
762
970
  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])}"
763
971
  options[:type] = type
764
972
  add_column_options!(add_column_sql, options)
@@ -769,7 +977,7 @@ module ActiveRecord
769
977
  execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
770
978
  end
771
979
 
772
- def change_column_null(table_name, column_name, null, default = nil)
980
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
773
981
  column = column_for(table_name, column_name)
774
982
 
775
983
  unless null || default.nil?
@@ -802,45 +1010,52 @@ module ActiveRecord
802
1010
  execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
803
1011
  end
804
1012
 
805
- # RSI: table and column comments
806
- def add_comment(table_name, column_name, comment)
1013
+ def add_comment(table_name, column_name, comment) #:nodoc:
807
1014
  return if comment.blank?
808
1015
  execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
809
1016
  end
810
1017
 
811
- def add_table_comment(table_name, comment)
1018
+ def add_table_comment(table_name, comment) #:nodoc:
812
1019
  return if comment.blank?
813
1020
  execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
814
1021
  end
815
1022
 
816
- def table_comment(table_name)
817
- (owner, table_name) = @connection.describe(table_name)
1023
+ def table_comment(table_name) #:nodoc:
1024
+ (owner, table_name, db_link) = @connection.describe(table_name)
818
1025
  select_value <<-SQL
819
- SELECT comments FROM all_tab_comments
1026
+ SELECT comments FROM all_tab_comments#{db_link}
820
1027
  WHERE owner = '#{owner}'
821
1028
  AND table_name = '#{table_name}'
822
1029
  SQL
823
1030
  end
824
1031
 
825
- def column_comment(table_name, column_name)
826
- (owner, table_name) = @connection.describe(table_name)
1032
+ def column_comment(table_name, column_name) #:nodoc:
1033
+ (owner, table_name, db_link) = @connection.describe(table_name)
827
1034
  select_value <<-SQL
828
- SELECT comments FROM all_col_comments
1035
+ SELECT comments FROM all_col_comments#{db_link}
829
1036
  WHERE owner = '#{owner}'
830
1037
  AND table_name = '#{table_name}'
831
1038
  AND column_name = '#{column_name.upcase}'
832
1039
  SQL
833
1040
  end
834
1041
 
1042
+ # Maps logical Rails types to Oracle-specific data types.
1043
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
1044
+ # Ignore options for :text and :binary columns
1045
+ return super(type, nil, nil, nil) if ['text', 'binary'].include?(type.to_s)
1046
+
1047
+ super
1048
+ end
1049
+
835
1050
  # Find a table's primary key and sequence.
836
1051
  # *Note*: Only primary key is implemented - sequence will be nil.
837
- def pk_and_sequence_for(table_name)
838
- (owner, table_name) = @connection.describe(table_name)
1052
+ def pk_and_sequence_for(table_name) #:nodoc:
1053
+ (owner, table_name, db_link) = @connection.describe(table_name)
839
1054
 
840
- # RSI: changed select from all_constraints to user_constraints - much faster in large data dictionaries
1055
+ # changed select from all_constraints to user_constraints - much faster in large data dictionaries
841
1056
  pks = select_values(<<-SQL, 'Primary Key')
842
1057
  select cc.column_name
843
- from user_constraints c, user_cons_columns cc
1058
+ from user_constraints#{db_link} c, user_cons_columns#{db_link} cc
844
1059
  where c.owner = '#{owner}'
845
1060
  and c.table_name = '#{table_name}'
846
1061
  and c.constraint_type = 'P'
@@ -857,7 +1072,7 @@ module ActiveRecord
857
1072
  structure << "create sequence #{seq.to_a.first.last};\n\n"
858
1073
  end
859
1074
 
860
- # RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
1075
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
861
1076
  select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |structure, table|
862
1077
  ddl = "create table #{table.to_a.first.last} (\n "
863
1078
  cols = select_all(%Q{
@@ -885,17 +1100,59 @@ module ActiveRecord
885
1100
  end
886
1101
  end
887
1102
 
1103
+ # Extract all stored procedures, packages, synonyms and views.
1104
+ def structure_dump_db_stored_code #:nodoc:
1105
+ structure = "\n"
1106
+ select_all("select distinct name, type
1107
+ from all_source
1108
+ where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION')
1109
+ and owner = sys_context('userenv','session_user')").inject("\n\n") do |structure, source|
1110
+ ddl = "create or replace \n "
1111
+ lines = select_all(%Q{
1112
+ select text
1113
+ from all_source
1114
+ where name = '#{source['name']}'
1115
+ and type = '#{source['type']}'
1116
+ and owner = sys_context('userenv','session_user')
1117
+ order by line
1118
+ }).map do |row|
1119
+ ddl << row['text'] if row['text'].size > 1
1120
+ end
1121
+ ddl << ";"
1122
+ structure << ddl << "\n"
1123
+ end
1124
+
1125
+ # export views
1126
+ select_all("select view_name, text from user_views").inject(structure) do |structure, view|
1127
+ ddl = "create or replace view #{view['view_name']} AS\n "
1128
+ # any views with empty lines will cause OCI to barf when loading. remove blank lines =/
1129
+ ddl << view['text'].gsub(/^\n/, '')
1130
+ ddl << ";\n\n"
1131
+ structure << ddl
1132
+ end
1133
+
1134
+ # export synonyms
1135
+ select_all("select owner, synonym_name, table_name, table_owner
1136
+ from all_synonyms
1137
+ where table_owner = sys_context('userenv','session_user') ").inject(structure) do |structure, synonym|
1138
+ ddl = "create or replace #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']} for #{synonym['table_owner']}.#{synonym['table_name']};\n\n"
1139
+ structure << ddl;
1140
+ end
1141
+ end
1142
+
1143
+
888
1144
  def structure_drop #:nodoc:
889
1145
  s = select_all("select sequence_name from user_sequences order by 1").inject("") do |drop, seq|
890
1146
  drop << "drop sequence #{seq.to_a.first.last};\n\n"
891
1147
  end
892
1148
 
893
- # RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
1149
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
894
1150
  select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |drop, table|
895
1151
  drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
896
1152
  end
897
1153
  end
898
1154
 
1155
+
899
1156
  def add_column_options!(sql, options) #:nodoc:
900
1157
  type = options[:type] || ((column = options[:column]) && column.type)
901
1158
  type = type && type.to_sym
@@ -926,7 +1183,7 @@ module ActiveRecord
926
1183
  # making every row the same.
927
1184
  #
928
1185
  # distinct("posts.id", "posts.created_at desc")
929
- def distinct(columns, order_by)
1186
+ def distinct(columns, order_by) #:nodoc:
930
1187
  return "DISTINCT #{columns}" if order_by.blank?
931
1188
 
932
1189
  # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
@@ -942,7 +1199,7 @@ module ActiveRecord
942
1199
  # ORDER BY clause for the passed order option.
943
1200
  #
944
1201
  # Uses column aliases as defined by #distinct.
945
- def add_order_by_for_association_limiting!(sql, options)
1202
+ def add_order_by_for_association_limiting!(sql, options) #:nodoc:
946
1203
  return sql if options[:order].blank?
947
1204
 
948
1205
  order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
@@ -952,6 +1209,19 @@ module ActiveRecord
952
1209
  sql << " ORDER BY #{order}"
953
1210
  end
954
1211
 
1212
+ protected
1213
+
1214
+ def translate_exception(exception, message) #:nodoc:
1215
+ case @connection.error_code(exception)
1216
+ when 1
1217
+ RecordNotUnique.new(message, exception)
1218
+ when 2291
1219
+ InvalidForeignKey.new(message, exception)
1220
+ else
1221
+ super
1222
+ end
1223
+ end
1224
+
955
1225
  private
956
1226
 
957
1227
  def select(sql, name = nil, return_column_names = false)
@@ -971,22 +1241,107 @@ module ActiveRecord
971
1241
  column
972
1242
  end
973
1243
 
1244
+ def create_sequence_and_trigger(table_name, options)
1245
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
1246
+ seq_start_value = options[:sequence_start_value] || default_sequence_start_value
1247
+ execute "CREATE SEQUENCE #{quote_table_name(seq_name)} START WITH #{seq_start_value}"
1248
+
1249
+ create_primary_key_trigger(table_name, options) if options[:primary_key_trigger]
1250
+ end
1251
+
1252
+ def create_primary_key_trigger(table_name, options)
1253
+ seq_name = options[:sequence_name] || default_sequence_name(table_name)
1254
+ trigger_name = options[:trigger_name] || default_trigger_name(table_name)
1255
+ primary_key = options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)
1256
+ execute compress_lines(<<-SQL)
1257
+ CREATE OR REPLACE TRIGGER #{quote_table_name(trigger_name)}
1258
+ BEFORE INSERT ON #{quote_table_name(table_name)} FOR EACH ROW
1259
+ BEGIN
1260
+ IF inserting THEN
1261
+ IF :new.#{quote_column_name(primary_key)} IS NULL THEN
1262
+ SELECT #{quote_table_name(seq_name)}.NEXTVAL INTO :new.#{quote_column_name(primary_key)} FROM dual;
1263
+ END IF;
1264
+ END IF;
1265
+ END;
1266
+ SQL
1267
+ end
1268
+
1269
+ def default_trigger_name(table_name)
1270
+ # truncate table name if necessary to fit in max length of identifier
1271
+ "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_pkt"
1272
+ end
1273
+
1274
+ def compress_lines(string, spaced = true)
1275
+ string.split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
1276
+ end
1277
+
1278
+ public
1279
+ # DBMS_OUTPUT =============================================
1280
+ #
1281
+ # PL/SQL in Oracle uses dbms_output for logging print statements
1282
+ # These methods stick that output into the Rails log so Ruby and PL/SQL
1283
+ # code can can be debugged together in a single application
1284
+ DBMS_OUTPUT_BUFFER_SIZE = 10000 #can be 1-1000000
1285
+ # Turn DBMS_Output logging on
1286
+ def enable_dbms_output
1287
+ set_dbms_output_plsql_connection
1288
+ @enable_dbms_output = true
1289
+ plsql(:dbms_output).sys.dbms_output.enable(DBMS_OUTPUT_BUFFER_SIZE)
1290
+ end
1291
+ # Turn DBMS_Output logging off
1292
+ def disable_dbms_output
1293
+ set_dbms_output_plsql_connection
1294
+ @enable_dbms_output = false
1295
+ plsql(:dbms_output).sys.dbms_output.disable
1296
+ end
1297
+ # Is DBMS_Output logging enabled?
1298
+ def dbms_output_enabled?
1299
+ @enable_dbms_output
1300
+ end
1301
+
1302
+ protected
1303
+ def log(sql, name) #:nodoc:
1304
+ super sql, name
1305
+ ensure
1306
+ log_dbms_output if dbms_output_enabled?
1307
+ end
1308
+
1309
+ private
1310
+
1311
+ def set_dbms_output_plsql_connection
1312
+ raise OracleEnhancedConnectionException, "ruby-plsql gem is required for logging DBMS output" unless self.respond_to?(:plsql)
1313
+ # do not reset plsql connection if it is the same (as resetting will clear PL/SQL metadata cache)
1314
+ unless plsql(:dbms_output).connection && plsql(:dbms_output).connection.raw_connection == raw_connection
1315
+ plsql(:dbms_output).connection = raw_connection
1316
+ end
1317
+ end
1318
+
1319
+ def log_dbms_output
1320
+ while true do
1321
+ result = plsql(:dbms_output).sys.dbms_output.get_line(:line => '', :status => 0)
1322
+ break unless result[:status] == 0
1323
+ @logger.debug "DBMS_OUTPUT: #{result[:line]}"
1324
+ end
1325
+ end
1326
+
974
1327
  end
975
1328
  end
976
1329
  end
977
1330
 
978
- # RSI: Added LOB writing callback for sessions stored in database
1331
+ # Added LOB writing callback for sessions stored in database
979
1332
  # Otherwise it is not working as Session class is defined before OracleAdapter is loaded in Rails 2.0
980
1333
  if defined?(CGI::Session::ActiveRecordStore::Session)
981
1334
  if !CGI::Session::ActiveRecordStore::Session.respond_to?(:after_save_callback_chain) ||
982
1335
  CGI::Session::ActiveRecordStore::Session.after_save_callback_chain.detect{|cb| cb.method == :enhanced_write_lobs}.nil?
1336
+ #:stopdoc:
983
1337
  class CGI::Session::ActiveRecordStore::Session
984
1338
  after_save :enhanced_write_lobs
985
1339
  end
1340
+ #:startdoc:
986
1341
  end
987
1342
  end
988
1343
 
989
- # RSI: load custom create, update, delete methods functionality
1344
+ # Load custom create, update, delete methods functionality
990
1345
  # rescue LoadError if ruby-plsql gem cannot be loaded
991
1346
  begin
992
1347
  require 'active_record/connection_adapters/oracle_enhanced_procedures'
@@ -997,21 +1352,30 @@ rescue LoadError
997
1352
  end
998
1353
  end
999
1354
 
1000
- # RSI: load additional methods for composite_primary_keys support
1355
+ # Load additional methods for composite_primary_keys support
1001
1356
  require 'active_record/connection_adapters/oracle_enhanced_cpk'
1002
1357
 
1003
- # RSI: load patch for dirty tracking methods
1358
+ # Load patch for dirty tracking methods
1004
1359
  require 'active_record/connection_adapters/oracle_enhanced_dirty'
1005
1360
 
1006
- # RSI: load rake tasks definitions
1361
+ # Load rake tasks definitions
1007
1362
  begin
1008
1363
  require 'active_record/connection_adapters/oracle_enhanced_tasks'
1009
1364
  rescue LoadError
1010
1365
  end if defined?(RAILS_ROOT)
1011
1366
 
1012
- # handles quoting of oracle reserved words
1367
+ # Handles quoting of oracle reserved words
1013
1368
  require 'active_record/connection_adapters/oracle_enhanced_reserved_words'
1014
1369
 
1015
- # add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1370
+ # Patches and enhancements for schema dumper
1371
+ require 'active_record/connection_adapters/oracle_enhanced_schema_dumper'
1372
+
1373
+ # Extensions for schema definition statements
1374
+ require 'active_record/connection_adapters/oracle_enhanced_schema_statements_ext'
1375
+
1376
+ # Extensions for schema definition
1377
+ require 'active_record/connection_adapters/oracle_enhanced_schema_definitions'
1378
+
1379
+ # Add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1016
1380
  require 'active_record/connection_adapters/oracle_enhanced_core_ext'
1017
1381