activerecord-oracle_enhanced-adapter 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
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