ctreatma-activerecord-oracle_enhanced-adapter 1.4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +51 -0
  3. data/History.md +269 -0
  4. data/License.txt +20 -0
  5. data/README.md +378 -0
  6. data/RUNNING_TESTS.md +45 -0
  7. data/Rakefile +46 -0
  8. data/VERSION +1 -0
  9. data/activerecord-oracle_enhanced-adapter.gemspec +130 -0
  10. data/ctreatma-activerecord-oracle_enhanced-adapter.gemspec +129 -0
  11. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1390 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +106 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +136 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
  18. data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +328 -0
  19. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
  20. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  21. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
  22. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +553 -0
  23. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +492 -0
  24. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
  25. data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +213 -0
  26. data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +252 -0
  27. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +373 -0
  28. data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +265 -0
  29. data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +290 -0
  30. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
  31. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
  32. data/lib/activerecord-oracle_enhanced-adapter.rb +25 -0
  33. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +749 -0
  34. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +310 -0
  35. data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +426 -0
  36. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +19 -0
  37. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +113 -0
  38. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +1330 -0
  39. data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +69 -0
  40. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +121 -0
  41. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
  42. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +374 -0
  43. data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +380 -0
  44. data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1112 -0
  45. data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +323 -0
  46. data/spec/spec_helper.rb +185 -0
  47. metadata +287 -0
@@ -0,0 +1,41 @@
1
+ # ActiveRecord 2.3 patches
2
+ if ActiveRecord::VERSION::MAJOR == 2 && ActiveRecord::VERSION::MINOR == 3
3
+ require "active_record/associations"
4
+
5
+ ActiveRecord::Associations::ClassMethods.module_eval do
6
+ private
7
+ def tables_in_string(string)
8
+ return [] if string.blank?
9
+ if self.connection.adapter_name == "OracleEnhanced"
10
+ # always convert table names to downcase as in Oracle quoted table names are in uppercase
11
+ # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
12
+ string.scan(/([a-zA-Z_][\.\w]+).?\./).flatten.map(&:downcase).uniq - ['raw_sql_']
13
+ else
14
+ string.scan(/([\.a-zA-Z_]+).?\./).flatten
15
+ end
16
+ end
17
+ end
18
+
19
+ ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation.class_eval do
20
+ protected
21
+ def aliased_table_name_for(name, suffix = nil)
22
+ # always downcase quoted table name as Oracle quoted table names are in uppercase
23
+ if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{active_record.connection.quote_table_name(name).downcase}\son}
24
+ @join_dependency.table_aliases[name] += 1
25
+ end
26
+
27
+ unless @join_dependency.table_aliases[name].zero?
28
+ # if the table name has been used, then use an alias
29
+ name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}#{suffix}"
30
+ table_index = @join_dependency.table_aliases[name]
31
+ @join_dependency.table_aliases[name] += 1
32
+ name = name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0
33
+ else
34
+ @join_dependency.table_aliases[name] += 1
35
+ end
36
+
37
+ name
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,1390 @@
1
+ # -*- coding: utf-8 -*-
2
+ # oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g, 11g
3
+ #
4
+ # Authors or original oracle_adapter: Graham Jenkins, Michael Schoen
5
+ #
6
+ # Current maintainer: Raimonds Simanovskis (http://blog.rayapps.com)
7
+ #
8
+ #########################################################################
9
+ #
10
+ # See History.md for changes added to original oracle_adapter.rb
11
+ #
12
+ #########################################################################
13
+ #
14
+ # From original oracle_adapter.rb:
15
+ #
16
+ # Implementation notes:
17
+ # 1. Redefines (safely) a method in ActiveRecord to make it possible to
18
+ # implement an autonumbering solution for Oracle.
19
+ # 2. The OCI8 driver is patched to properly handle values for LONG and
20
+ # TIMESTAMP columns. The driver-author has indicated that a future
21
+ # release of the driver will obviate this patch.
22
+ # 3. LOB support is implemented through an after_save callback.
23
+ # 4. Oracle does not offer native LIMIT and OFFSET options; this
24
+ # functionality is mimiced through the use of nested selects.
25
+ # See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
26
+ #
27
+ # Do what you want with this code, at your own peril, but if any
28
+ # significant portion of my code remains then please acknowledge my
29
+ # contribution.
30
+ # portions Copyright 2005 Graham Jenkins
31
+
32
+ # ActiveRecord 2.2 does not load version file automatically
33
+ require 'active_record/version' unless defined?(ActiveRecord::VERSION)
34
+
35
+ require 'active_record/connection_adapters/abstract_adapter'
36
+ require 'active_record/connection_adapters/oracle_enhanced_connection'
37
+
38
+ require 'active_record/connection_adapters/oracle_enhanced_base_ext'
39
+ require 'active_record/connection_adapters/oracle_enhanced_column'
40
+
41
+ require 'digest/sha1'
42
+
43
+ module ActiveRecord
44
+ module ConnectionAdapters #:nodoc:
45
+
46
+ # Oracle enhanced adapter will work with both
47
+ # Ruby 1.8/1.9 ruby-oci8 gem (which provides interface to Oracle OCI client)
48
+ # or with JRuby and Oracle JDBC driver.
49
+ #
50
+ # It should work with Oracle 9i, 10g and 11g databases.
51
+ # Limited set of functionality should work on Oracle 8i as well but several features
52
+ # rely on newer functionality in Oracle database.
53
+ #
54
+ # Usage notes:
55
+ # * Key generation assumes a "${table_name}_seq" sequence is available
56
+ # for all tables; the sequence name can be changed using
57
+ # ActiveRecord::Base.set_sequence_name. When using Migrations, these
58
+ # sequences are created automatically.
59
+ # Use set_sequence_name :autogenerated with legacy tables that have
60
+ # triggers that populate primary keys automatically.
61
+ # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
62
+ # Consequently some hacks are employed to map data back to Date or Time
63
+ # in Ruby. Timezones and sub-second precision on timestamps are
64
+ # not supported.
65
+ # * Default values that are functions (such as "SYSDATE") are not
66
+ # supported. This is a restriction of the way ActiveRecord supports
67
+ # default values.
68
+ #
69
+ # Required parameters:
70
+ #
71
+ # * <tt>:username</tt>
72
+ # * <tt>:password</tt>
73
+ # * <tt>:database</tt> - either TNS alias or connection string for OCI client or database name in JDBC connection string
74
+ #
75
+ # Optional parameters:
76
+ #
77
+ # * <tt>:host</tt> - host name for JDBC connection, defaults to "localhost"
78
+ # * <tt>:port</tt> - port number for JDBC connection, defaults to 1521
79
+ # * <tt>:privilege</tt> - set "SYSDBA" if you want to connect with this privilege
80
+ # * <tt>:allow_concurrency</tt> - set to "true" if non-blocking mode should be enabled (just for OCI client)
81
+ # * <tt>:prefetch_rows</tt> - how many rows should be fetched at one time to increase performance, defaults to 100
82
+ # * <tt>:cursor_sharing</tt> - cursor sharing mode to minimize amount of unique statements, defaults to "force"
83
+ # * <tt>:time_zone</tt> - database session time zone
84
+ # (it is recommended to set it using ENV['TZ'] which will be then also used for database session time zone)
85
+ #
86
+ # Optionals NLS parameters:
87
+ #
88
+ # * <tt>:nls_calendar</tt>
89
+ # * <tt>:nls_characterset</tt>
90
+ # * <tt>:nls_comp</tt>
91
+ # * <tt>:nls_currency</tt>
92
+ # * <tt>:nls_date_format</tt> - format for :date columns, defaults to <tt>YYYY-MM-DD HH24:MI:SS</tt>
93
+ # * <tt>:nls_date_language</tt>
94
+ # * <tt>:nls_dual_currency</tt>
95
+ # * <tt>:nls_iso_currency</tt>
96
+ # * <tt>:nls_language</tt>
97
+ # * <tt>:nls_length_semantics</tt> - semantics of size of VARCHAR2 and CHAR columns, defaults to <tt>CHAR</tt>
98
+ # (meaning that size specifies number of characters and not bytes)
99
+ # * <tt>:nls_nchar_characterset</tt>
100
+ # * <tt>:nls_nchar_conv_excp</tt>
101
+ # * <tt>:nls_numeric_characters</tt>
102
+ # * <tt>:nls_sort</tt>
103
+ # * <tt>:nls_territory</tt>
104
+ # * <tt>:nls_timestamp_format</tt> - format for :timestamp columns, defaults to <tt>YYYY-MM-DD HH24:MI:SS:FF6</tt>
105
+ # * <tt>:nls_timestamp_tz_format</tt>
106
+ # * <tt>:nls_time_format</tt>
107
+ # * <tt>:nls_time_tz_format</tt>
108
+ #
109
+ class OracleEnhancedAdapter < AbstractAdapter
110
+
111
+ ##
112
+ # :singleton-method:
113
+ # By default, the OracleEnhancedAdapter will consider all columns of type <tt>NUMBER(1)</tt>
114
+ # as boolean. If you wish to disable this emulation you can add the following line
115
+ # to your initializer file:
116
+ #
117
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = false
118
+ cattr_accessor :emulate_booleans
119
+ self.emulate_booleans = true
120
+
121
+ ##
122
+ # :singleton-method:
123
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
124
+ # to Time or DateTime (if value is out of Time value range) value.
125
+ # If you wish that DATE values with hour, minutes and seconds equal to 0 are typecasted
126
+ # to Date then you can add the following line to your initializer file:
127
+ #
128
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates = true
129
+ #
130
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
131
+ # that Date columns are explicily defined with +set_date_columns+ method.
132
+ cattr_accessor :emulate_dates
133
+ self.emulate_dates = false
134
+
135
+ ##
136
+ # :singleton-method:
137
+ # OracleEnhancedAdapter will use the default tablespace, but if you want specific types of
138
+ # objects to go into specific tablespaces, specify them like this in an initializer:
139
+ #
140
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_tablespaces =
141
+ # {:clob => 'TS_LOB', :blob => 'TS_LOB', :index => 'TS_INDEX', :table => 'TS_DATA'}
142
+ #
143
+ # Using the :tablespace option where available (e.g create_table) will take precedence
144
+ # over these settings.
145
+ cattr_accessor :default_tablespaces
146
+ self.default_tablespaces={}
147
+
148
+ ##
149
+ # :singleton-method:
150
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
151
+ # to Time or DateTime (if value is out of Time value range) value.
152
+ # If you wish that DATE columns with "date" in their name (e.g. "creation_date") are typecasted
153
+ # to Date then you can add the following line to your initializer file:
154
+ #
155
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
156
+ #
157
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
158
+ # that Date columns are explicily defined with +set_date_columns+ method.
159
+ cattr_accessor :emulate_dates_by_column_name
160
+ self.emulate_dates_by_column_name = false
161
+
162
+ # Check column name to identify if it is Date (and not Time) column.
163
+ # Is used if +emulate_dates_by_column_name+ option is set to +true+.
164
+ # Override this method definition in initializer file if different Date column recognition is needed.
165
+ def self.is_date_column?(name, table_name = nil)
166
+ name =~ /(^|_)date(_|$)/i
167
+ end
168
+
169
+ # instance method uses at first check if column type defined at class level
170
+ def is_date_column?(name, table_name = nil) #:nodoc:
171
+ case get_type_for_column(table_name, name)
172
+ when nil
173
+ self.class.is_date_column?(name, table_name)
174
+ when :date
175
+ true
176
+ else
177
+ false
178
+ end
179
+ end
180
+
181
+ ##
182
+ # :singleton-method:
183
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>NUMBER</tt>
184
+ # (without precision or scale) to Float or BigDecimal value.
185
+ # If you wish that NUMBER columns with name "id" or that end with "_id" are typecasted
186
+ # to Integer then you can add the following line to your initializer file:
187
+ #
188
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true
189
+ cattr_accessor :emulate_integers_by_column_name
190
+ self.emulate_integers_by_column_name = false
191
+
192
+ # Check column name to identify if it is Integer (and not Float or BigDecimal) column.
193
+ # Is used if +emulate_integers_by_column_name+ option is set to +true+.
194
+ # Override this method definition in initializer file if different Integer column recognition is needed.
195
+ def self.is_integer_column?(name, table_name = nil)
196
+ name =~ /(^|_)id$/i
197
+ end
198
+
199
+ ##
200
+ # :singleton-method:
201
+ # If you wish that CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
202
+ # are typecasted to booleans then you can add the following line to your initializer file:
203
+ #
204
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans_from_strings = true
205
+ cattr_accessor :emulate_booleans_from_strings
206
+ self.emulate_booleans_from_strings = false
207
+
208
+ # Check column name to identify if it is boolean (and not String) column.
209
+ # Is used if +emulate_booleans_from_strings+ option is set to +true+.
210
+ # Override this method definition in initializer file if different boolean column recognition is needed.
211
+ def self.is_boolean_column?(name, field_type, table_name = nil)
212
+ return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type)
213
+ field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i)
214
+ end
215
+
216
+ # How boolean value should be quoted to String.
217
+ # Used if +emulate_booleans_from_strings+ option is set to +true+.
218
+ def self.boolean_to_string(bool)
219
+ bool ? "Y" : "N"
220
+ end
221
+
222
+ ##
223
+ # :singleton-method:
224
+ # Specify non-default date format that should be used when assigning string values to :date columns, e.g.:
225
+ #
226
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_date_format = “%d.%m.%Y”
227
+ cattr_accessor :string_to_date_format
228
+ self.string_to_date_format = nil
229
+
230
+ ##
231
+ # :singleton-method:
232
+ # Specify non-default time format that should be used when assigning string values to :datetime columns, e.g.:
233
+ #
234
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_time_format = “%d.%m.%Y %H:%M:%S”
235
+ cattr_accessor :string_to_time_format
236
+ self.string_to_time_format = nil
237
+
238
+ class StatementPool
239
+ include Enumerable
240
+
241
+ def initialize(connection, max = 300)
242
+ @connection = connection
243
+ @max = max
244
+ @cache = {}
245
+ end
246
+
247
+ def each(&block); @cache.each(&block); end
248
+ def key?(key); @cache.key?(key); end
249
+ def [](key); @cache[key]; end
250
+ def length; @cache.length; end
251
+ def delete(key); @cache.delete(key); end
252
+
253
+ def []=(sql, key)
254
+ while @max <= @cache.size
255
+ @cache.shift.last.close
256
+ end
257
+ @cache[sql] = key
258
+ end
259
+
260
+ def clear
261
+ @cache.values.each do |cursor|
262
+ cursor.close
263
+ end
264
+ @cache.clear
265
+ end
266
+ end
267
+
268
+ def initialize(connection, logger, config) #:nodoc:
269
+ super(connection, logger)
270
+ @quoted_column_names, @quoted_table_names = {}, {}
271
+ @config = config
272
+ @statements = StatementPool.new(connection, config.fetch(:statement_limit) { 250 })
273
+ @enable_dbms_output = false
274
+ @visitor = Arel::Visitors::Oracle.new self if defined?(Arel::Visitors::Oracle)
275
+ end
276
+
277
+ def self.visitor_for(pool) # :nodoc:
278
+ Arel::Visitors::Oracle.new(pool)
279
+ end
280
+
281
+ ADAPTER_NAME = 'OracleEnhanced'.freeze
282
+
283
+ def adapter_name #:nodoc:
284
+ ADAPTER_NAME
285
+ end
286
+
287
+ def supports_migrations? #:nodoc:
288
+ true
289
+ end
290
+
291
+ def supports_primary_key? #:nodoc:
292
+ true
293
+ end
294
+
295
+ def supports_savepoints? #:nodoc:
296
+ true
297
+ end
298
+
299
+ #:stopdoc:
300
+ DEFAULT_NLS_PARAMETERS = {
301
+ :nls_calendar => nil,
302
+ :nls_characterset => nil,
303
+ :nls_comp => nil,
304
+ :nls_currency => nil,
305
+ :nls_date_format => 'YYYY-MM-DD HH24:MI:SS',
306
+ :nls_date_language => nil,
307
+ :nls_dual_currency => nil,
308
+ :nls_iso_currency => nil,
309
+ :nls_language => nil,
310
+ :nls_length_semantics => 'CHAR',
311
+ :nls_nchar_characterset => nil,
312
+ :nls_nchar_conv_excp => nil,
313
+ :nls_numeric_characters => nil,
314
+ :nls_sort => nil,
315
+ :nls_territory => nil,
316
+ :nls_timestamp_format => 'YYYY-MM-DD HH24:MI:SS:FF6',
317
+ :nls_timestamp_tz_format => nil,
318
+ :nls_time_format => nil,
319
+ :nls_time_tz_format => nil
320
+ }
321
+
322
+ #:stopdoc:
323
+ NATIVE_DATABASE_TYPES = {
324
+ :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
325
+ :string => { :name => "VARCHAR2", :limit => 255 },
326
+ :text => { :name => "CLOB" },
327
+ :integer => { :name => "NUMBER", :limit => 38 },
328
+ :float => { :name => "NUMBER" },
329
+ :decimal => { :name => "DECIMAL" },
330
+ :datetime => { :name => "DATE" },
331
+ # changed to native TIMESTAMP type
332
+ # :timestamp => { :name => "DATE" },
333
+ :timestamp => { :name => "TIMESTAMP" },
334
+ :time => { :name => "DATE" },
335
+ :date => { :name => "DATE" },
336
+ :binary => { :name => "BLOB" },
337
+ :boolean => { :name => "NUMBER", :limit => 1 },
338
+ :raw => { :name => "RAW", :limit => 2000 }
339
+ }
340
+ # if emulate_booleans_from_strings then store booleans in VARCHAR2
341
+ NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS = NATIVE_DATABASE_TYPES.dup.merge(
342
+ :boolean => { :name => "VARCHAR2", :limit => 1 }
343
+ )
344
+ #:startdoc:
345
+
346
+ def native_database_types #:nodoc:
347
+ emulate_booleans_from_strings ? NATIVE_DATABASE_TYPES_BOOLEAN_STRINGS : NATIVE_DATABASE_TYPES
348
+ end
349
+
350
+ # maximum length of Oracle identifiers
351
+ IDENTIFIER_MAX_LENGTH = 30
352
+
353
+ def table_alias_length #:nodoc:
354
+ IDENTIFIER_MAX_LENGTH
355
+ end
356
+
357
+ # the maximum length of a table name
358
+ def table_name_length
359
+ IDENTIFIER_MAX_LENGTH
360
+ end
361
+
362
+ # the maximum length of a column name
363
+ def column_name_length
364
+ IDENTIFIER_MAX_LENGTH
365
+ end
366
+
367
+ # the maximum length of an index name
368
+ def index_name_length
369
+ IDENTIFIER_MAX_LENGTH
370
+ end
371
+
372
+ # the maximum length of a sequence name
373
+ def sequence_name_length
374
+ IDENTIFIER_MAX_LENGTH
375
+ end
376
+
377
+ # To avoid ORA-01795: maximum number of expressions in a list is 1000
378
+ # tell ActiveRecord to limit us to 1000 ids at a time
379
+ def in_clause_length
380
+ 1000
381
+ end
382
+ alias ids_in_list_limit in_clause_length
383
+
384
+ # QUOTING ==================================================
385
+ #
386
+ # see: abstract/quoting.rb
387
+
388
+ def quote_column_name(name) #:nodoc:
389
+ name = name.to_s
390
+ @quoted_column_names[name] ||= begin
391
+ # if only valid lowercase column characters in name
392
+ if name =~ /\A[a-z][a-z_0-9\$#]*\Z/
393
+ "\"#{name.upcase}\""
394
+ else
395
+ # remove double quotes which cannot be used inside quoted identifier
396
+ "\"#{name.gsub('"', '')}\""
397
+ end
398
+ end
399
+ end
400
+
401
+ # This method is used in add_index to identify either column name (which is quoted)
402
+ # or function based index (in which case function expression is not quoted)
403
+ def quote_column_name_or_expression(name) #:nodoc:
404
+ name = name.to_s
405
+ case name
406
+ # if only valid lowercase column characters in name
407
+ when /^[a-z][a-z_0-9\$#]*$/
408
+ "\"#{name.upcase}\""
409
+ when /^[a-z][a-z_0-9\$#\-]*$/i
410
+ "\"#{name}\""
411
+ # if other characters present then assume that it is expression
412
+ # which should not be quoted
413
+ else
414
+ name
415
+ end
416
+ end
417
+
418
+ # Names must be from 1 to 30 bytes long with these exceptions:
419
+ # * Names of databases are limited to 8 bytes.
420
+ # * Names of database links can be as long as 128 bytes.
421
+ #
422
+ # Nonquoted identifiers cannot be Oracle Database reserved words
423
+ #
424
+ # Nonquoted identifiers must begin with an alphabetic character from
425
+ # your database character set
426
+ #
427
+ # Nonquoted identifiers can contain only alphanumeric characters from
428
+ # your database character set and the underscore (_), dollar sign ($),
429
+ # and pound sign (#). Database links can also contain periods (.) and
430
+ # "at" signs (@). Oracle strongly discourages you from using $ and # in
431
+ # nonquoted identifiers.
432
+ NONQUOTED_OBJECT_NAME = /[A-Za-z][A-z0-9$#]{0,29}/
433
+ NONQUOTED_DATABASE_LINK = /[A-Za-z][A-z0-9$#\.@]{0,127}/
434
+ VALID_TABLE_NAME = /\A(?:#{NONQUOTED_OBJECT_NAME}\.)?#{NONQUOTED_OBJECT_NAME}(?:@#{NONQUOTED_DATABASE_LINK})?\Z/
435
+
436
+ # unescaped table name should start with letter and
437
+ # contain letters, digits, _, $ or #
438
+ # can be prefixed with schema name
439
+ # CamelCase table names should be quoted
440
+ def self.valid_table_name?(name) #:nodoc:
441
+ name = name.to_s
442
+ name =~ VALID_TABLE_NAME && !(name =~ /[A-Z]/ && name =~ /[a-z]/) ? true : false
443
+ end
444
+
445
+ def quote_table_name(name) #:nodoc:
446
+ name = name.to_s
447
+ @quoted_table_names[name] ||= name.split('.').map{|n| n.split('@').map{|m| quote_column_name(m)}.join('@')}.join('.')
448
+ end
449
+
450
+ def quote_string(s) #:nodoc:
451
+ s.gsub(/'/, "''")
452
+ end
453
+
454
+ def quote(value, column = nil) #:nodoc:
455
+ if value && column
456
+ case column.type
457
+ when :text, :binary
458
+ %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
459
+ # NLS_DATE_FORMAT independent TIMESTAMP support
460
+ when :timestamp
461
+ quote_timestamp_with_to_timestamp(value)
462
+ # NLS_DATE_FORMAT independent DATE support
463
+ when :date, :time, :datetime
464
+ quote_date_with_to_date(value)
465
+ when :raw
466
+ quote_raw(value)
467
+ when :string
468
+ # NCHAR and NVARCHAR2 literals should be quoted with N'...'.
469
+ # Read directly instance variable as otherwise migrations with table column default values are failing
470
+ # as migrations pass ColumnDefinition object to this method.
471
+ # Check if instance variable is defined to avoid warnings about accessing undefined instance variable.
472
+ column.instance_variable_defined?('@nchar') && column.instance_variable_get('@nchar') ? 'N' << super : super
473
+ else
474
+ super
475
+ end
476
+ elsif value.acts_like?(:date)
477
+ quote_date_with_to_date(value)
478
+ elsif value.acts_like?(:time)
479
+ value.to_i == value.to_f ? quote_date_with_to_date(value) : quote_timestamp_with_to_timestamp(value)
480
+ else
481
+ super
482
+ end
483
+ end
484
+
485
+ def quoted_true #:nodoc:
486
+ return "'#{self.class.boolean_to_string(true)}'" if emulate_booleans_from_strings
487
+ "1"
488
+ end
489
+
490
+ def quoted_false #:nodoc:
491
+ return "'#{self.class.boolean_to_string(false)}'" if emulate_booleans_from_strings
492
+ "0"
493
+ end
494
+
495
+ def quote_date_with_to_date(value) #:nodoc:
496
+ # should support that composite_primary_keys gem will pass date as string
497
+ value = quoted_date(value) if value.acts_like?(:date) || value.acts_like?(:time)
498
+ "TO_DATE('#{value}','YYYY-MM-DD HH24:MI:SS')"
499
+ end
500
+
501
+ # Encode a string or byte array as string of hex codes
502
+ def self.encode_raw(value)
503
+ # When given a string, convert to a byte array.
504
+ value = value.unpack('C*') if value.is_a?(String)
505
+ value.map { |x| "%02X" % x }.join
506
+ end
507
+
508
+ # quote encoded raw value
509
+ def quote_raw(value) #:nodoc:
510
+ "'#{self.class.encode_raw(value)}'"
511
+ end
512
+
513
+ def quote_timestamp_with_to_timestamp(value) #:nodoc:
514
+ # add up to 9 digits of fractional seconds to inserted time
515
+ value = "#{quoted_date(value)}:#{("%.6f"%value.to_f).split('.')[1]}" if value.acts_like?(:time)
516
+ "TO_TIMESTAMP('#{value}','YYYY-MM-DD HH24:MI:SS:FF6')"
517
+ end
518
+
519
+ # Cast a +value+ to a type that the database understands.
520
+ def type_cast(value, column)
521
+ case value
522
+ when true, false
523
+ if emulate_booleans_from_strings || column && column.type == :string
524
+ self.class.boolean_to_string(value)
525
+ else
526
+ value ? 1 : 0
527
+ end
528
+ when Date, Time
529
+ if value.acts_like?(:time)
530
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
531
+ value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
532
+ else
533
+ value
534
+ end
535
+ else
536
+ super
537
+ end
538
+ end
539
+
540
+ # CONNECTION MANAGEMENT ====================================
541
+ #
542
+
543
+ # If SQL statement fails due to lost connection then reconnect
544
+ # and retry SQL statement if autocommit mode is enabled.
545
+ # By default this functionality is disabled.
546
+ attr_reader :auto_retry #:nodoc:
547
+ @auto_retry = false
548
+
549
+ def auto_retry=(value) #:nodoc:
550
+ @auto_retry = value
551
+ @connection.auto_retry = value if @connection
552
+ end
553
+
554
+ # return raw OCI8 or JDBC connection
555
+ def raw_connection
556
+ @connection.raw_connection
557
+ end
558
+
559
+ # Returns true if the connection is active.
560
+ def active? #:nodoc:
561
+ # Pings the connection to check if it's still good. Note that an
562
+ # #active? method is also available, but that simply returns the
563
+ # last known state, which isn't good enough if the connection has
564
+ # gone stale since the last use.
565
+ @connection.ping
566
+ rescue OracleEnhancedConnectionException
567
+ false
568
+ end
569
+
570
+ # Reconnects to the database.
571
+ def reconnect! #:nodoc:
572
+ clear_cache!
573
+ @connection.reset!
574
+ rescue OracleEnhancedConnectionException => e
575
+ @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger
576
+ end
577
+
578
+ def reset!
579
+ clear_cache!
580
+ super
581
+ end
582
+
583
+ # Disconnects from the database.
584
+ def disconnect! #:nodoc:
585
+ clear_cache!
586
+ @connection.logoff rescue nil
587
+ end
588
+
589
+ # DATABASE STATEMENTS ======================================
590
+ #
591
+ # see: abstract/database_statements.rb
592
+
593
+ # Executes a SQL statement
594
+ def execute(sql, name = nil)
595
+ log(sql, name) { @connection.exec(sql) }
596
+ end
597
+
598
+ def substitute_at(column, index)
599
+ Arel.sql(":a#{index + 1}")
600
+ end
601
+
602
+ def clear_cache!
603
+ @statements.clear
604
+ end
605
+
606
+ def exec_query(sql, name = 'SQL', binds = [])
607
+ log(sql, name, binds) do
608
+ cursor = nil
609
+ cached = false
610
+ if binds.empty?
611
+ cursor = @connection.prepare(sql)
612
+ else
613
+ unless @statements.key? sql
614
+ @statements[sql] = @connection.prepare(sql)
615
+ end
616
+
617
+ cursor = @statements[sql]
618
+
619
+ binds.each_with_index do |bind, i|
620
+ col, val = bind
621
+ cursor.bind_param(i + 1, type_cast(val, col), col && col.type)
622
+ end
623
+
624
+ cached = true
625
+ end
626
+
627
+ cursor.exec
628
+
629
+ if name == 'EXPLAIN'
630
+ res = true
631
+ else
632
+ columns = cursor.get_col_names.map do |col_name|
633
+ @connection.oracle_downcase(col_name)
634
+ end
635
+ rows = []
636
+ fetch_options = {:get_lob_value => (name != 'Writable Large Object')}
637
+ while row = cursor.fetch(fetch_options)
638
+ rows << row
639
+ end
640
+ res = ActiveRecord::Result.new(columns, rows)
641
+ end
642
+
643
+ cursor.close unless cached
644
+ res
645
+ end
646
+ end
647
+
648
+ def supports_statement_cache?
649
+ true
650
+ end
651
+
652
+ def supports_explain?
653
+ true
654
+ end
655
+
656
+ def explain(arel, binds = [])
657
+ sql = "EXPLAIN PLAN FOR #{to_sql(arel)}"
658
+ return if sql =~ /FROM all_/
659
+ exec_query(sql, 'EXPLAIN', binds)
660
+ select_values("SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY)", 'EXPLAIN').join("\n")
661
+ end
662
+
663
+ # Returns an array of arrays containing the field values.
664
+ # Order is the same as that returned by #columns.
665
+ def select_rows(sql, name = nil)
666
+ # last parameter indicates to return also column list
667
+ result = columns = nil
668
+ log(sql, name) do
669
+ result, columns = @connection.select(sql, name, true)
670
+ end
671
+ result.map{ |v| columns.map{|c| v[c]} }
672
+ end
673
+
674
+ # Executes an INSERT statement and returns the new record's ID
675
+ def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
676
+ # if primary key value is already prefetched from sequence
677
+ # or if there is no primary key
678
+ if id_value || pk.nil?
679
+ execute(sql, name)
680
+ return id_value
681
+ end
682
+
683
+ sql_with_returning = sql + @connection.returning_clause(quote_column_name(pk))
684
+ log(sql, name) do
685
+ @connection.exec_with_returning(sql_with_returning)
686
+ end
687
+ end
688
+ protected :insert_sql
689
+
690
+ # New method in ActiveRecord 3.1
691
+ # Will add RETURNING clause in case of trigger generated primary keys
692
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
693
+ unless id_value || pk.nil? || (defined?(CompositePrimaryKeys) && pk.kind_of?(CompositePrimaryKeys::CompositeKeys))
694
+ sql = "#{sql} RETURNING #{quote_column_name(pk)} INTO :returning_id"
695
+ (binds = binds.dup) << [:returning_id, nil]
696
+ end
697
+ [sql, binds]
698
+ end
699
+
700
+ EXEC_INSERT_RESULT_COLUMNS = %w(returning_id) #:nodoc:
701
+
702
+ # New method in ActiveRecord 3.1
703
+ def exec_insert(sql, name, binds)
704
+ log(sql, name, binds) do
705
+ returning_id_index = nil
706
+ cursor = if @statements.key?(sql)
707
+ @statements[sql]
708
+ else
709
+ @statements[sql] = @connection.prepare(sql)
710
+ end
711
+
712
+ binds.each_with_index do |bind, i|
713
+ col, val = bind
714
+ if col == :returning_id
715
+ returning_id_index = i + 1
716
+ cursor.bind_returning_param(returning_id_index, Integer)
717
+ else
718
+ cursor.bind_param(i + 1, type_cast(val, col), col && col.type)
719
+ end
720
+ end
721
+
722
+ cursor.exec_update
723
+
724
+ rows = []
725
+ if returning_id_index
726
+ returning_id = cursor.get_returning_param(returning_id_index, Integer)
727
+ rows << [returning_id]
728
+ end
729
+ ActiveRecord::Result.new(EXEC_INSERT_RESULT_COLUMNS, rows)
730
+ end
731
+ end
732
+
733
+ # New method in ActiveRecord 3.1
734
+ def exec_update(sql, name, binds)
735
+ log(sql, name, binds) do
736
+ cached = false
737
+ if binds.empty?
738
+ cursor = @connection.prepare(sql)
739
+ else
740
+ cursor = if @statements.key?(sql)
741
+ @statements[sql]
742
+ else
743
+ @statements[sql] = @connection.prepare(sql)
744
+ end
745
+
746
+ binds.each_with_index do |bind, i|
747
+ col, val = bind
748
+ cursor.bind_param(i + 1, type_cast(val, col), col && col.type)
749
+ end
750
+ cached = true
751
+ end
752
+
753
+ res = cursor.exec_update
754
+ cursor.close unless cached
755
+ res
756
+ end
757
+ end
758
+
759
+ alias :exec_delete :exec_update
760
+
761
+ # use in set_sequence_name to avoid fetching primary key value from sequence
762
+ AUTOGENERATED_SEQUENCE_NAME = 'autogenerated'.freeze
763
+
764
+ # Returns the next sequence value from a sequence generator. Not generally
765
+ # called directly; used by ActiveRecord to get the next primary key value
766
+ # when inserting a new database record (see #prefetch_primary_key?).
767
+ def next_sequence_value(sequence_name)
768
+ # if sequence_name is set to :autogenerated then it means that primary key will be populated by trigger
769
+ return nil if sequence_name == AUTOGENERATED_SEQUENCE_NAME
770
+ # call directly connection method to avoid prepared statement which causes fetching of next sequence value twice
771
+ @connection.select_value("SELECT #{quote_table_name(sequence_name)}.NEXTVAL FROM dual")
772
+ end
773
+
774
+ def begin_db_transaction #:nodoc:
775
+ @connection.autocommit = false
776
+ end
777
+
778
+ def commit_db_transaction #:nodoc:
779
+ @connection.commit
780
+ ensure
781
+ @connection.autocommit = true
782
+ end
783
+
784
+ def rollback_db_transaction #:nodoc:
785
+ @connection.rollback
786
+ ensure
787
+ @connection.autocommit = true
788
+ end
789
+
790
+ def create_savepoint #:nodoc:
791
+ execute("SAVEPOINT #{current_savepoint_name}")
792
+ end
793
+
794
+ def rollback_to_savepoint #:nodoc:
795
+ execute("ROLLBACK TO #{current_savepoint_name}")
796
+ end
797
+
798
+ def release_savepoint #:nodoc:
799
+ # there is no RELEASE SAVEPOINT statement in Oracle
800
+ end
801
+
802
+ def add_limit_offset!(sql, options) #:nodoc:
803
+ # added to_i for limit and offset to protect from SQL injection
804
+ offset = (options[:offset] || 0).to_i
805
+ limit = options[:limit]
806
+ limit = limit.is_a?(String) && limit.blank? ? nil : limit && limit.to_i
807
+ if limit && offset > 0
808
+ sql.replace "SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM (#{sql}) raw_sql_ WHERE ROWNUM <= #{offset+limit}) WHERE raw_rnum_ > #{offset}"
809
+ elsif limit
810
+ sql.replace "SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}"
811
+ elsif offset > 0
812
+ sql.replace "SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM (#{sql}) raw_sql_) WHERE raw_rnum_ > #{offset}"
813
+ end
814
+ end
815
+
816
+ @@do_not_prefetch_primary_key = {}
817
+
818
+ # Returns true for Oracle adapter (since Oracle requires primary key
819
+ # values to be pre-fetched before insert). See also #next_sequence_value.
820
+ def prefetch_primary_key?(table_name = nil)
821
+ return true if table_name.nil?
822
+ table_name = table_name.to_s
823
+ do_not_prefetch = @@do_not_prefetch_primary_key[table_name]
824
+ if do_not_prefetch.nil?
825
+ owner, desc_table_name, db_link = @connection.describe(table_name)
826
+ @@do_not_prefetch_primary_key[table_name] = do_not_prefetch =
827
+ !has_primary_key?(table_name, owner, desc_table_name, db_link) ||
828
+ has_primary_key_trigger?(table_name, owner, desc_table_name, db_link)
829
+ end
830
+ !do_not_prefetch
831
+ end
832
+
833
+ # used just in tests to clear prefetch primary key flag for all tables
834
+ def clear_prefetch_primary_key #:nodoc:
835
+ @@do_not_prefetch_primary_key = {}
836
+ end
837
+
838
+ # Returns default sequence name for table.
839
+ # Will take all or first 26 characters of table name and append _seq suffix
840
+ def default_sequence_name(table_name, primary_key = nil)
841
+ # TODO: remove schema prefix if present before truncating
842
+ # truncate table name if necessary to fit in max length of identifier
843
+ "#{table_name.to_s[0,IDENTIFIER_MAX_LENGTH-4]}_seq"
844
+ end
845
+
846
+ # Inserts the given fixture into the table. Overridden to properly handle lobs.
847
+ def insert_fixture(fixture, table_name) #:nodoc:
848
+ super
849
+
850
+ if ActiveRecord::Base.pluralize_table_names
851
+ klass = table_name.singularize.camelize
852
+ else
853
+ klass = table_name.camelize
854
+ end
855
+
856
+ klass = klass.constantize rescue nil
857
+ if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
858
+ write_lobs(table_name, klass, fixture)
859
+ end
860
+ end
861
+
862
+ # Writes LOB values from attributes, as indicated by the LOB columns of klass.
863
+ def write_lobs(table_name, klass, attributes) #:nodoc:
864
+ # is class with composite primary key>
865
+ is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
866
+ if is_with_cpk
867
+ id = klass.primary_key.map {|pk| attributes[pk.to_s] }
868
+ else
869
+ id = quote(attributes[klass.primary_key])
870
+ end
871
+ klass.columns.select { |col| col.sql_type =~ /LOB$/i }.each do |col|
872
+ value = attributes[col.name]
873
+ # changed sequence of next two lines - should check if value is nil before converting to yaml
874
+ next if value.nil? || (value == '')
875
+ value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
876
+ uncached do
877
+ sql = is_with_cpk ? "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{klass.composite_where_clause(id)} FOR UPDATE" :
878
+ "SELECT #{quote_column_name(col.name)} FROM #{quote_table_name(table_name)} WHERE #{quote_column_name(klass.primary_key)} = #{id} FOR UPDATE"
879
+ unless lob_record = select_one(sql, 'Writable Large Object')
880
+ raise ActiveRecord::RecordNotFound, "statement #{sql} returned no rows"
881
+ end
882
+ lob = lob_record[col.name]
883
+ @connection.write_lob(lob, value.to_s, col.type == :binary)
884
+ end
885
+ end
886
+ end
887
+
888
+ # Current database name
889
+ def current_database
890
+ select_value("SELECT SYS_CONTEXT('userenv', 'db_name') FROM dual")
891
+ end
892
+
893
+ # Current database session user
894
+ def current_user
895
+ select_value("SELECT SYS_CONTEXT('userenv', 'session_user') FROM dual")
896
+ end
897
+
898
+ # Current database session schema
899
+ def current_schema
900
+ select_value("SELECT SYS_CONTEXT('userenv', 'current_schema') FROM dual")
901
+ end
902
+
903
+ # Default tablespace name of current user
904
+ def default_tablespace
905
+ select_value("SELECT LOWER(default_tablespace) FROM user_users WHERE username = SYS_CONTEXT('userenv', 'current_schema')")
906
+ end
907
+
908
+ def tables(name = nil) #:nodoc:
909
+ select_values(
910
+ "SELECT DECODE(table_name, UPPER(table_name), LOWER(table_name), table_name) FROM all_tables WHERE owner = SYS_CONTEXT('userenv', 'current_schema') AND secondary = 'N'",
911
+ name)
912
+ end
913
+
914
+ # Will return true if database object exists (to be able to use also views and synonyms for ActiveRecord models)
915
+ def table_exists?(table_name)
916
+ (owner, table_name, db_link) = @connection.describe(table_name)
917
+ true
918
+ rescue
919
+ false
920
+ end
921
+
922
+ def materialized_views #:nodoc:
923
+ select_values("SELECT LOWER(mview_name) FROM all_mviews WHERE owner = SYS_CONTEXT('userenv', 'current_schema')")
924
+ end
925
+
926
+ cattr_accessor :all_schema_indexes #:nodoc:
927
+
928
+ # This method selects all indexes at once, and caches them in a class variable.
929
+ # Subsequent index calls get them from the variable, without going to the DB.
930
+ def indexes(table_name, name = nil) #:nodoc:
931
+ (owner, table_name, db_link) = @connection.describe(table_name)
932
+ unless all_schema_indexes
933
+ default_tablespace_name = default_tablespace
934
+ result = select_all(<<-SQL.strip.gsub(/\s+/, ' '))
935
+ SELECT LOWER(i.table_name) AS table_name, LOWER(i.index_name) AS index_name, i.uniqueness,
936
+ i.index_type, i.ityp_owner, i.ityp_name, i.parameters,
937
+ LOWER(i.tablespace_name) AS tablespace_name,
938
+ LOWER(c.column_name) AS column_name, e.column_expression
939
+ FROM all_indexes#{db_link} i
940
+ JOIN all_ind_columns#{db_link} c ON c.index_name = i.index_name AND c.index_owner = i.owner
941
+ LEFT OUTER JOIN all_ind_expressions#{db_link} e ON e.index_name = i.index_name AND
942
+ e.index_owner = i.owner AND e.column_position = c.column_position
943
+ WHERE i.owner = '#{owner}'
944
+ AND i.table_owner = '#{owner}'
945
+ AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc
946
+ WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
947
+ ORDER BY i.index_name, c.column_position
948
+ SQL
949
+
950
+ current_index = nil
951
+ self.all_schema_indexes = []
952
+
953
+ result.each do |row|
954
+ # have to keep track of indexes because above query returns dups
955
+ # there is probably a better query we could figure out
956
+ if current_index != row['index_name']
957
+ statement_parameters = nil
958
+ if row['index_type'] == 'DOMAIN' && row['ityp_owner'] == 'CTXSYS' && row['ityp_name'] == 'CONTEXT'
959
+ procedure_name = default_datastore_procedure(row['index_name'])
960
+ source = select_values(<<-SQL).join
961
+ SELECT text
962
+ FROM all_source#{db_link}
963
+ WHERE owner = '#{owner}'
964
+ AND name = '#{procedure_name.upcase}'
965
+ ORDER BY line
966
+ SQL
967
+ if source =~ /-- add_context_index_parameters (.+)\n/
968
+ statement_parameters = $1
969
+ end
970
+ end
971
+ all_schema_indexes << OracleEnhancedIndexDefinition.new(row['table_name'], row['index_name'],
972
+ row['uniqueness'] == "UNIQUE", row['index_type'] == 'DOMAIN' ? "#{row['ityp_owner']}.#{row['ityp_name']}" : nil,
973
+ row['parameters'], statement_parameters,
974
+ row['tablespace_name'] == default_tablespace_name ? nil : row['tablespace_name'], [])
975
+ current_index = row['index_name']
976
+ end
977
+ all_schema_indexes.last.columns << (row['column_expression'] || row['column_name'].downcase)
978
+ end
979
+ end
980
+
981
+ # Return the indexes just for the requested table, since AR is structured that way
982
+ table_name = table_name.downcase
983
+ all_schema_indexes.select{|i| i.table == table_name}
984
+ end
985
+
986
+ @@ignore_table_columns = nil #:nodoc:
987
+
988
+ # set ignored columns for table
989
+ def ignore_table_columns(table_name, *args) #:nodoc:
990
+ @@ignore_table_columns ||= {}
991
+ @@ignore_table_columns[table_name] ||= []
992
+ @@ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
993
+ @@ignore_table_columns[table_name].uniq!
994
+ end
995
+
996
+ def ignored_table_columns(table_name) #:nodoc:
997
+ @@ignore_table_columns ||= {}
998
+ @@ignore_table_columns[table_name]
999
+ end
1000
+
1001
+ # used just in tests to clear ignored table columns
1002
+ def clear_ignored_table_columns #:nodoc:
1003
+ @@ignore_table_columns = nil
1004
+ end
1005
+
1006
+ @@table_column_type = nil #:nodoc:
1007
+
1008
+ # set explicit type for specified table columns
1009
+ def set_type_for_columns(table_name, column_type, *args) #:nodoc:
1010
+ @@table_column_type ||= {}
1011
+ @@table_column_type[table_name] ||= {}
1012
+ args.each do |col|
1013
+ @@table_column_type[table_name][col.to_s.downcase] = column_type
1014
+ end
1015
+ end
1016
+
1017
+ def get_type_for_column(table_name, column_name) #:nodoc:
1018
+ @@table_column_type && @@table_column_type[table_name] && @@table_column_type[table_name][column_name.to_s.downcase]
1019
+ end
1020
+
1021
+ # used just in tests to clear column data type definitions
1022
+ def clear_types_for_columns #:nodoc:
1023
+ @@table_column_type = nil
1024
+ end
1025
+
1026
+ # check if table has primary key trigger with _pkt suffix
1027
+ def has_primary_key_trigger?(table_name, owner = nil, desc_table_name = nil, db_link = nil)
1028
+ (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
1029
+
1030
+ trigger_name = default_trigger_name(table_name).upcase
1031
+ pkt_sql = <<-SQL
1032
+ SELECT trigger_name
1033
+ FROM all_triggers#{db_link}
1034
+ WHERE owner = '#{owner}'
1035
+ AND trigger_name = '#{trigger_name}'
1036
+ AND table_owner = '#{owner}'
1037
+ AND table_name = '#{desc_table_name}'
1038
+ AND status = 'ENABLED'
1039
+ SQL
1040
+ select_value(pkt_sql, 'Primary Key Trigger') ? true : false
1041
+ end
1042
+
1043
+ ##
1044
+ # :singleton-method:
1045
+ # Cache column description between requests.
1046
+ # Could be used in development environment to avoid selecting table columns from data dictionary tables for each request.
1047
+ # This can speed up request processing in development mode if development database is not on local computer.
1048
+ #
1049
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.cache_columns = true
1050
+ cattr_accessor :cache_columns
1051
+ self.cache_columns = false
1052
+
1053
+ def columns(table_name, name = nil) #:nodoc:
1054
+ if @@cache_columns
1055
+ @@columns_cache ||= {}
1056
+ @@columns_cache[table_name] ||= columns_without_cache(table_name, name)
1057
+ else
1058
+ columns_without_cache(table_name, name)
1059
+ end
1060
+ end
1061
+
1062
+ def columns_without_cache(table_name, name = nil) #:nodoc:
1063
+ table_name = table_name.to_s
1064
+ # get ignored_columns by original table name
1065
+ ignored_columns = ignored_table_columns(table_name)
1066
+
1067
+ (owner, desc_table_name, db_link) = @connection.describe(table_name)
1068
+
1069
+ # reset do_not_prefetch_primary_key cache for this table
1070
+ @@do_not_prefetch_primary_key[table_name] = nil
1071
+
1072
+ table_cols = <<-SQL.strip.gsub(/\s+/, ' ')
1073
+ SELECT column_name AS name, data_type AS sql_type, data_default, nullable, virtual_column, hidden_column,
1074
+ DECODE(data_type, 'NUMBER', data_precision,
1075
+ 'FLOAT', data_precision,
1076
+ 'VARCHAR2', DECODE(char_used, 'C', char_length, data_length),
1077
+ 'RAW', DECODE(char_used, 'C', char_length, data_length),
1078
+ 'CHAR', DECODE(char_used, 'C', char_length, data_length),
1079
+ NULL) AS limit,
1080
+ DECODE(data_type, 'NUMBER', data_scale, NULL) AS scale
1081
+ FROM all_tab_cols#{db_link}
1082
+ WHERE owner = '#{owner}'
1083
+ AND table_name = '#{desc_table_name}'
1084
+ AND hidden_column = 'NO'
1085
+ ORDER BY column_id
1086
+ SQL
1087
+
1088
+ # added deletion of ignored columns
1089
+ select_all(table_cols, name).delete_if do |row|
1090
+ ignored_columns && ignored_columns.include?(row['name'].downcase)
1091
+ end.map do |row|
1092
+ limit, scale = row['limit'], row['scale']
1093
+ if limit || scale
1094
+ row['sql_type'] += "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
1095
+ end
1096
+
1097
+ # clean up odd default spacing from Oracle
1098
+ if row['data_default']
1099
+ row['data_default'].sub!(/^(.*?)\s*$/, '\1')
1100
+
1101
+ # If a default contains a newline these cleanup regexes need to
1102
+ # match newlines.
1103
+ row['data_default'].sub!(/^'(.*)'$/m, '\1')
1104
+ row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
1105
+ end
1106
+
1107
+ OracleEnhancedColumn.new(oracle_downcase(row['name']),
1108
+ row['data_default'],
1109
+ row['sql_type'],
1110
+ row['nullable'] == 'Y',
1111
+ # pass table name for table specific column definitions
1112
+ table_name,
1113
+ # pass column type if specified in class definition
1114
+ get_type_for_column(table_name, oracle_downcase(row['name'])), row['virtual_column']=='YES')
1115
+ end
1116
+ end
1117
+
1118
+ # used just in tests to clear column cache
1119
+ def clear_columns_cache #:nodoc:
1120
+ @@columns_cache = nil
1121
+ @@pk_and_sequence_for_cache = nil
1122
+ end
1123
+
1124
+ # used in migrations to clear column cache for specified table
1125
+ def clear_table_columns_cache(table_name)
1126
+ if @@cache_columns
1127
+ @@columns_cache ||= {}
1128
+ @@columns_cache[table_name.to_s] = nil
1129
+ end
1130
+ end
1131
+
1132
+ ##
1133
+ # :singleton-method:
1134
+ # Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
1135
+ #
1136
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 1
1137
+ cattr_accessor :default_sequence_start_value
1138
+ self.default_sequence_start_value = 10000
1139
+
1140
+ # Find a table's primary key and sequence.
1141
+ # *Note*: Only primary key is implemented - sequence will be nil.
1142
+ def pk_and_sequence_for(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1143
+ if @@cache_columns
1144
+ @@pk_and_sequence_for_cache ||= {}
1145
+ if @@pk_and_sequence_for_cache.key?(table_name)
1146
+ @@pk_and_sequence_for_cache[table_name]
1147
+ else
1148
+ @@pk_and_sequence_for_cache[table_name] = pk_and_sequence_for_without_cache(table_name, owner, desc_table_name, db_link)
1149
+ end
1150
+ else
1151
+ pk_and_sequence_for_without_cache(table_name, owner, desc_table_name, db_link)
1152
+ end
1153
+ end
1154
+
1155
+ def pk_and_sequence_for_without_cache(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1156
+ (owner, desc_table_name, db_link) = @connection.describe(table_name) unless owner
1157
+
1158
+ # changed back from user_constraints to all_constraints for consistency
1159
+ pks = select_values(<<-SQL.strip.gsub(/\s+/, ' '), 'Primary Key')
1160
+ SELECT cc.column_name
1161
+ FROM all_constraints#{db_link} c, all_cons_columns#{db_link} cc
1162
+ WHERE c.owner = '#{owner}'
1163
+ AND c.table_name = '#{desc_table_name}'
1164
+ AND c.constraint_type = 'P'
1165
+ AND cc.owner = c.owner
1166
+ AND cc.constraint_name = c.constraint_name
1167
+ SQL
1168
+
1169
+ # only support single column keys
1170
+ pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
1171
+ end
1172
+
1173
+ # Returns just a table's primary key
1174
+ def primary_key(table_name)
1175
+ pk_and_sequence = pk_and_sequence_for(table_name)
1176
+ pk_and_sequence && pk_and_sequence.first
1177
+ end
1178
+
1179
+ def has_primary_key?(table_name, owner=nil, desc_table_name=nil, db_link=nil) #:nodoc:
1180
+ !pk_and_sequence_for(table_name, owner, desc_table_name, db_link).nil?
1181
+ end
1182
+
1183
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
1184
+ #
1185
+ # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
1186
+ # queries. However, with those columns included in the SELECT DISTINCT list, you
1187
+ # won't actually get a distinct list of the column you want (presuming the column
1188
+ # has duplicates with multiple values for the ordered-by columns. So we use the
1189
+ # FIRST_VALUE function to get a single (first) value for each column, effectively
1190
+ # making every row the same.
1191
+ #
1192
+ # distinct("posts.id", "posts.created_at desc")
1193
+ def distinct(columns, order_by) #:nodoc:
1194
+ return "DISTINCT #{columns}" if order_by.blank?
1195
+
1196
+ # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
1197
+ # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
1198
+ order_columns = if order_by.is_a?(String)
1199
+ order_by.split(',').map { |s| s.strip }.reject(&:blank?)
1200
+ else # in latest ActiveRecord versions order_by is already Array
1201
+ order_by
1202
+ end
1203
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
1204
+ # remove any ASC/DESC modifiers
1205
+ value = c =~ /^(.+)\s+(ASC|DESC)\s*$/i ? $1 : c
1206
+ "FIRST_VALUE(#{value}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
1207
+ end
1208
+ sql = "DISTINCT #{columns}, "
1209
+ sql << order_columns * ", "
1210
+ end
1211
+
1212
+ def temporary_table?(table_name) #:nodoc:
1213
+ select_value("SELECT temporary FROM user_tables WHERE table_name = '#{table_name.upcase}'") == 'Y'
1214
+ end
1215
+
1216
+ # ORDER BY clause for the passed order option.
1217
+ #
1218
+ # Uses column aliases as defined by #distinct.
1219
+ #
1220
+ # In Rails 3.x this method is moved to Arel
1221
+ def add_order_by_for_association_limiting!(sql, options) #:nodoc:
1222
+ return sql if options[:order].blank?
1223
+
1224
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
1225
+ order.map! {|s| $1 if s =~ / (.*)/}
1226
+ order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
1227
+
1228
+ sql << " ORDER BY #{order}"
1229
+ end
1230
+
1231
+ # construct additional wrapper subquery if select.offset is used to avoid generation of invalid subquery
1232
+ # ... IN ( SELECT * FROM ( SELECT raw_sql_.*, rownum raw_rnum_ FROM ( ... ) raw_sql_ ) WHERE raw_rnum_ > ... )
1233
+ def join_to_update(update, select) #:nodoc:
1234
+ if select.offset
1235
+ subsubselect = select.clone
1236
+ subsubselect.projections = [update.key]
1237
+
1238
+ subselect = Arel::SelectManager.new(select.engine)
1239
+ subselect.project Arel.sql(quote_column_name update.key.name)
1240
+ subselect.from subsubselect.as('alias_join_to_update')
1241
+
1242
+ update.where update.key.in(subselect)
1243
+ else
1244
+ super
1245
+ end
1246
+ end
1247
+
1248
+ protected
1249
+
1250
+ def translate_exception(exception, message) #:nodoc:
1251
+ case @connection.error_code(exception)
1252
+ when 1
1253
+ RecordNotUnique.new(message, exception)
1254
+ when 2291
1255
+ InvalidForeignKey.new(message, exception)
1256
+ else
1257
+ super
1258
+ end
1259
+ end
1260
+
1261
+ private
1262
+
1263
+ def select(sql, name = nil, binds = [])
1264
+ if ActiveRecord.const_defined?(:Result)
1265
+ exec_query(sql, name, binds).to_a
1266
+ else
1267
+ log(sql, name) do
1268
+ @connection.select(sql, name, false)
1269
+ end
1270
+ end
1271
+ end
1272
+
1273
+ def oracle_downcase(column_name)
1274
+ @connection.oracle_downcase(column_name)
1275
+ end
1276
+
1277
+ def compress_lines(string, join_with = "\n")
1278
+ string.split($/).map { |line| line.strip }.join(join_with)
1279
+ end
1280
+
1281
+ public
1282
+ # DBMS_OUTPUT =============================================
1283
+ #
1284
+ # PL/SQL in Oracle uses dbms_output for logging print statements
1285
+ # These methods stick that output into the Rails log so Ruby and PL/SQL
1286
+ # code can can be debugged together in a single application
1287
+
1288
+ # Maximum DBMS_OUTPUT buffer size
1289
+ DBMS_OUTPUT_BUFFER_SIZE = 10000 # can be 1-1000000
1290
+
1291
+ # Turn DBMS_Output logging on
1292
+ def enable_dbms_output
1293
+ set_dbms_output_plsql_connection
1294
+ @enable_dbms_output = true
1295
+ plsql(:dbms_output).sys.dbms_output.enable(DBMS_OUTPUT_BUFFER_SIZE)
1296
+ end
1297
+ # Turn DBMS_Output logging off
1298
+ def disable_dbms_output
1299
+ set_dbms_output_plsql_connection
1300
+ @enable_dbms_output = false
1301
+ plsql(:dbms_output).sys.dbms_output.disable
1302
+ end
1303
+ # Is DBMS_Output logging enabled?
1304
+ def dbms_output_enabled?
1305
+ @enable_dbms_output
1306
+ end
1307
+
1308
+ protected
1309
+ def log(sql, name, binds = nil) #:nodoc:
1310
+ if binds
1311
+ super sql, name, binds
1312
+ else
1313
+ super sql, name
1314
+ end
1315
+ ensure
1316
+ log_dbms_output if dbms_output_enabled?
1317
+ end
1318
+
1319
+ private
1320
+
1321
+ def set_dbms_output_plsql_connection
1322
+ raise OracleEnhancedConnectionException, "ruby-plsql gem is required for logging DBMS output" unless self.respond_to?(:plsql)
1323
+ # do not reset plsql connection if it is the same (as resetting will clear PL/SQL metadata cache)
1324
+ unless plsql(:dbms_output).connection && plsql(:dbms_output).connection.raw_connection == raw_connection
1325
+ plsql(:dbms_output).connection = raw_connection
1326
+ end
1327
+ end
1328
+
1329
+ def log_dbms_output
1330
+ while true do
1331
+ result = plsql(:dbms_output).sys.dbms_output.get_line(:line => '', :status => 0)
1332
+ break unless result[:status] == 0
1333
+ @logger.debug "DBMS_OUTPUT: #{result[:line]}" if @logger
1334
+ end
1335
+ end
1336
+
1337
+ end
1338
+ end
1339
+ end
1340
+
1341
+ # Added LOB writing callback for sessions stored in database
1342
+ # Otherwise it is not working as Session class is defined before OracleAdapter is loaded in Rails 2.0
1343
+ if defined?(CGI::Session::ActiveRecordStore::Session)
1344
+ if !CGI::Session::ActiveRecordStore::Session.respond_to?(:after_save_callback_chain) ||
1345
+ CGI::Session::ActiveRecordStore::Session.after_save_callback_chain.detect{|cb| cb.method == :enhanced_write_lobs}.nil?
1346
+ #:stopdoc:
1347
+ class CGI::Session::ActiveRecordStore::Session
1348
+ after_save :enhanced_write_lobs
1349
+ end
1350
+ #:startdoc:
1351
+ end
1352
+ end
1353
+
1354
+ # Implementation of standard schema definition statements and extensions for schema definition
1355
+ require 'active_record/connection_adapters/oracle_enhanced_schema_statements'
1356
+ require 'active_record/connection_adapters/oracle_enhanced_schema_statements_ext'
1357
+
1358
+ # Extensions for schema definition
1359
+ require 'active_record/connection_adapters/oracle_enhanced_schema_definitions'
1360
+
1361
+ # Extensions for context index definition
1362
+ require 'active_record/connection_adapters/oracle_enhanced_context_index'
1363
+
1364
+ # Load custom create, update, delete methods functionality
1365
+ require 'active_record/connection_adapters/oracle_enhanced_procedures'
1366
+
1367
+ # Load additional methods for composite_primary_keys support
1368
+ require 'active_record/connection_adapters/oracle_enhanced_cpk'
1369
+
1370
+ # Load patch for dirty tracking methods
1371
+ require 'active_record/connection_adapters/oracle_enhanced_dirty'
1372
+
1373
+ # Load rake tasks definitions
1374
+ begin
1375
+ require 'active_record/connection_adapters/oracle_enhanced_tasks'
1376
+ rescue LoadError
1377
+ end if defined?(Rails) || defined?(RAILS_ROOT)
1378
+
1379
+ # Patches and enhancements for schema dumper
1380
+ require 'active_record/connection_adapters/oracle_enhanced_schema_dumper'
1381
+
1382
+ # Implementation of structure dump
1383
+ require 'active_record/connection_adapters/oracle_enhanced_structure_dump'
1384
+
1385
+ # Add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1386
+ require 'active_record/connection_adapters/oracle_enhanced_core_ext'
1387
+
1388
+ require 'active_record/connection_adapters/oracle_enhanced_activerecord_patches'
1389
+
1390
+ require 'active_record/connection_adapters/oracle_enhanced_version'