ctreatma-activerecord-oracle_enhanced-adapter 1.4.1.1

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