plukevdh-activerecord-oracle_enhanced-adapter 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. data/History.txt +111 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +26 -0
  4. data/README.rdoc +68 -0
  5. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  6. data/lib/active_record/connection_adapters/oracle_enhanced.rake +48 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1200 -0
  8. data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +71 -0
  9. data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +64 -0
  10. data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
  11. data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
  12. data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +358 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +368 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +150 -0
  15. data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
  16. data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
  17. data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +7 -0
  18. data/oracle-enhanced.gemspec +59 -0
  19. data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +659 -0
  20. data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +170 -0
  21. data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +40 -0
  22. data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +103 -0
  23. data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +951 -0
  24. data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +93 -0
  25. data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +27 -0
  26. data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +340 -0
  27. data/spec/spec.opts +6 -0
  28. data/spec/spec_helper.rb +94 -0
  29. metadata +94 -0
@@ -0,0 +1,111 @@
1
+ == 1.2.1 2009-06-07
2
+
3
+ * Enhancements
4
+ * caching of table indexes query which makes schema dump much faster
5
+ * Bug fixes:
6
+ * return Date (and not DateTime) values for :date column value before year 1970
7
+ * fixed after_create/update/destroy callbacks with plsql custom methods
8
+ * fixed creation of large integers in JRuby
9
+ * Made test tasks respect RAILS_ENV
10
+ * fixed support for composite primary keys for tables with LOBs
11
+
12
+ == 1.2.0 2009-03-22
13
+
14
+ * Enhancements
15
+ * support for JRuby and JDBC
16
+ * support for Ruby 1.9.1 and ruby-oci8 2.0
17
+ * support for Rails 2.3
18
+ * quoting of Oracle reserved words in table names and column names
19
+ * emulation of OracleAdapter (for ActiveRecord unit tests)
20
+ * Bug fixes:
21
+ * several bug fixes that were identified during running of ActiveRecord unit tests
22
+
23
+ == 1.1.9 2009-01-02
24
+
25
+ * Enhancements
26
+ * Added support for table and column comments in migrations
27
+ * Added support for specifying sequence start values
28
+ * Added :privilege option (e.g. :SYSDBA) to ActiveRecord::Base.establish_connection
29
+ * Bug fixes:
30
+ * Do not mark empty decimals, strings and texts (stored as NULL in database) as changed when reassigning them (starting from Rails 2.1)
31
+ * Create booleans as VARCHAR2(1) columns if emulate_booleans_from_strings is true
32
+
33
+ == 1.1.8 2008-10-10
34
+
35
+ * Bug fixes:
36
+ * Fixed storing of serialized LOB columns
37
+ * Prevent from SQL injection in :limit and :offset
38
+ * Order by LOB columns (by replacing column with function which returns first 100 characters of LOB)
39
+ * Sequence creation for tables with non-default primary key in create_table block
40
+ * Do count distinct workaround only when composite_primary_keys gem is used
41
+ (otherwise count distinct did not work with ActiveRecord 2.1.1)
42
+ * Fixed rake db:test:clone_structure task
43
+ (see http://rsim.lighthouseapp.com/projects/11468/tickets/11-rake-dbtestclone_structure-fails-in-117)
44
+ * Fixed bug when ActiveRecord::Base.allow_concurrency = true
45
+ (see http://dev.rubyonrails.org/ticket/11134)
46
+
47
+ == 1.1.7 2008-08-20
48
+
49
+ * Bug fixes:
50
+ * Fixed that adapter works without ruby-plsql gem (in this case just custom create/update/delete methods are not available)
51
+
52
+ == 1.1.6 2008-08-19
53
+
54
+ * Enhancements:
55
+ * Added support for set_date_columns and set_datetime_columns
56
+ * Added support for set_boolean_columns
57
+ * Added support for schema prefix in set_table_name (removed table name quoting)
58
+ * Added support for NVARCHAR2 column type
59
+ * Bug fixes:
60
+ * Do not call write_lobs callback when custom create or update methods are defined
61
+
62
+ == 1.1.5 2008-07-27
63
+
64
+ * Bug fixes:
65
+ * Fixed that write_lobs callback works with partial_updates enabled (added additional record lock before writing BLOB data to database)
66
+ * Enhancements:
67
+ * Changed SQL SELECT in indexes method so that it will execute faster on some large data dictionaries
68
+ * Support for other date and time formats when assigning string to :date or :datetime column
69
+
70
+ == 1.1.4 2008-07-14
71
+
72
+ * Enhancements:
73
+ * Date/Time quoting changes to support composite_primary_keys
74
+ * Added additional methods that are used by composite_primary_keys
75
+
76
+ == 1.1.3 2008-07-10
77
+
78
+ * Enhancements:
79
+ * Added support for custom create, update and delete methods when working with legacy databases where
80
+ PL/SQL API should be used for create, update and delete operations
81
+
82
+ == 1.1.2 2008-07-08
83
+
84
+ * Bug fixes:
85
+ * Fixed after_save callback addition for session store in ActiveRecord version 2.0.2
86
+ * Changed date column name recognition - now should match regex /(^|_)date(_|$)/i
87
+ (previously "updated_at" was recognized as :date column and not as :datetime)
88
+
89
+ == 1.1.1 2008-06-28
90
+
91
+ * Enhancements:
92
+ * Added ignore_table_columns option
93
+ * Added support for TIMESTAMP columns (without fractional seconds)
94
+ * NLS_DATE_FORMAT and NLS_TIMESTAMP_FORMAT independent DATE and TIMESTAMP columns support
95
+ * Bug fixes:
96
+ * Checks if CGI::Session::ActiveRecordStore::Session does not have enhanced_write_lobs callback before adding it
97
+ (Rails 2.0 does not add this callback, Rails 2.1 does)
98
+
99
+ == 1.1.0 2008-05-05
100
+
101
+ * Forked from original activerecord-oracle-adapter-1.0.0.9216
102
+ * Renamed oracle adapter to oracle_enhanced adapter
103
+ * Added "enhanced" to method and class definitions so that oracle_enhanced and original oracle adapter
104
+ could be used simultaniously
105
+ * Added Rails rake tasks as a copy from original oracle tasks
106
+ * Enhancements:
107
+ * Improved perfomance of schema dump methods when used on large data dictionaries
108
+ * Added LOB writing callback for sessions stored in database
109
+ * Added emulate_dates_by_column_name option
110
+ * Added emulate_integers_by_column_name option
111
+ * Added emulate_booleans_from_strings option
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,26 @@
1
+ History.txt
2
+ License.txt
3
+ README.rdoc
4
+ lib/active_record/connection_adapters/emulation/oracle_adapter.rb
5
+ lib/active_record/connection_adapters/oracle_enhanced.rake
6
+ lib/active_record/connection_adapters/oracle_enhanced_adapter.rb
7
+ lib/active_record/connection_adapters/oracle_enhanced_connection.rb
8
+ lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb
9
+ lib/active_record/connection_adapters/oracle_enhanced_cpk.rb
10
+ lib/active_record/connection_adapters/oracle_enhanced_dirty.rb
11
+ lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb
12
+ lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb
13
+ lib/active_record/connection_adapters/oracle_enhanced_procedures.rb
14
+ lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb
15
+ lib/active_record/connection_adapters/oracle_enhanced_tasks.rb
16
+ lib/active_record/connection_adapters/oracle_enhanced_version.rb
17
+ spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb
18
+ spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb
19
+ spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb
20
+ spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb
21
+ spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb
22
+ spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb
23
+ spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb
24
+ spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb
25
+ spec/spec.opts
26
+ spec/spec_helper.rb
@@ -0,0 +1,68 @@
1
+ = Activerecord Oracle enhanced adapter
2
+
3
+ * http://github.com/rsim/oracle-enhanced
4
+
5
+ == DESCRIPTION:
6
+
7
+ Oracle "enhanced" ActiveRecord adapter contains useful additional methods for working with new and legacy Oracle databases
8
+ from Rails which are extracted from current real projects' monkey patches of original Oracle adapter.
9
+
10
+ See http://github.com/rsim/oracle-enhanced/wikis for usage information.
11
+
12
+ For questions and feature discussion please use http://groups.google.com/group/oracle-enhanced
13
+
14
+ Blog posts about oracle-enahnced can be found at http://blog.rayapps.com/category/oracle-enhanced
15
+
16
+ Bugs and enhancement requests can be reported at http://rsim.lighthouseapp.com/projects/11468-oracle-enhanced
17
+
18
+ == REQUIREMENTS:
19
+
20
+ * Works (has been tested) with ActiveRecord version 2.0, 2.1, 2.2 and 2.3 (these are the same as Rails versions)
21
+ * Can be used on the following Ruby platforms:
22
+ * MRI - requires ruby-oci8 1.x or 2.x library to connect to Oracle
23
+ * Ruby/YARV 1.9.1 - requires ruby-oci8 2.x library to connect to Oracle
24
+ unicode_utils gem is recommended for Unicode aware string upcase and downcase
25
+ * JRuby - uses JDBC driver ojdbc14.jar to connect to Oracle (should be in JRUBY_HOME/lib or in Java class path)
26
+ * Requires ruby-plsql gem to support custom create, update and delete methods (but can be used without ruby-plsql if this functionality is not needed)
27
+
28
+ == INSTALL:
29
+
30
+ * [sudo] gem install activerecord-oracle_enhanced-adapter
31
+
32
+ == CONTRIBUTORS:
33
+
34
+ * Raimonds Simanovskis
35
+ * Jorge Dias
36
+ * James Wylder
37
+ * Rob Christie
38
+ * Nate Wieger
39
+ * Edgars Beigarts
40
+ * Lachlan Laycock
41
+ * toddwf
42
+ * Anton Jenkins
43
+ * Dave Smylie
44
+
45
+ == LICENSE:
46
+
47
+ (The MIT License)
48
+
49
+ Copyright (c) 2009 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
50
+
51
+ Permission is hereby granted, free of charge, to any person obtaining
52
+ a copy of this software and associated documentation files (the
53
+ 'Software'), to deal in the Software without restriction, including
54
+ without limitation the rights to use, copy, modify, merge, publish,
55
+ distribute, sublicense, and/or sell copies of the Software, and to
56
+ permit persons to whom the Software is furnished to do so, subject to
57
+ the following conditions:
58
+
59
+ The above copyright notice and this permission notice shall be
60
+ included in all copies or substantial portions of the Software.
61
+
62
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
63
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
64
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
65
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
66
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
67
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
68
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,5 @@
1
+ class ActiveRecord::ConnectionAdapters::OracleAdapter < ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter #:nodoc:
2
+ def adapter_name
3
+ 'Oracle'
4
+ end
5
+ end
@@ -0,0 +1,48 @@
1
+ # RSI: implementation idea taken from JDBC adapter
2
+ def redefine_task(*args, &block)
3
+ task_name = Hash === args.first ? args.first.keys[0] : args.first
4
+ existing_task = Rake.application.lookup task_name
5
+ if existing_task
6
+ class << existing_task; public :instance_variable_set; end
7
+ existing_task.instance_variable_set "@prerequisites", FileList[]
8
+ existing_task.instance_variable_set "@actions", []
9
+ end
10
+ task(*args, &block)
11
+ end
12
+
13
+ namespace :db do
14
+
15
+ namespace :structure do
16
+ redefine_task :dump => :environment do
17
+ abcs = ActiveRecord::Base.configurations
18
+ ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
19
+ File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
20
+ if ActiveRecord::Base.connection.supports_migrations?
21
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information }
22
+ end
23
+ if abcs[RAILS_ENV]['structure_dump'] == "db_stored_code"
24
+ File.open("db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.structure_dump_db_stored_code }
25
+ end
26
+
27
+ end
28
+ end
29
+
30
+ namespace :test do
31
+ redefine_task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
32
+ abcs = ActiveRecord::Base.configurations
33
+ ActiveRecord::Base.establish_connection(:test)
34
+ IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |ddl|
35
+ ActiveRecord::Base.connection.execute(ddl.chop)
36
+ end
37
+ end
38
+
39
+ redefine_task :purge => :environment do
40
+ abcs = ActiveRecord::Base.configurations
41
+ ActiveRecord::Base.establish_connection(:test)
42
+ ActiveRecord::Base.connection.structure_drop.split("\n\n").each do |ddl|
43
+ ActiveRecord::Base.connection.execute(ddl.chop)
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,1200 @@
1
+ # oracle_enhanced_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g, 11g
2
+ #
3
+ # Authors or original oracle_adapter: Graham Jenkins, Michael Schoen
4
+ #
5
+ # Current maintainer: Raimonds Simanovskis (http://blog.rayapps.com)
6
+ #
7
+ #########################################################################
8
+ #
9
+ # See History.txt for changes added to original oracle_adapter.rb
10
+ #
11
+ #########################################################################
12
+ #
13
+ # From original oracle_adapter.rb:
14
+ #
15
+ # Implementation notes:
16
+ # 1. Redefines (safely) a method in ActiveRecord to make it possible to
17
+ # implement an autonumbering solution for Oracle.
18
+ # 2. The OCI8 driver is patched to properly handle values for LONG and
19
+ # TIMESTAMP columns. The driver-author has indicated that a future
20
+ # release of the driver will obviate this patch.
21
+ # 3. LOB support is implemented through an after_save callback.
22
+ # 4. Oracle does not offer native LIMIT and OFFSET options; this
23
+ # functionality is mimiced through the use of nested selects.
24
+ # See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
25
+ #
26
+ # Do what you want with this code, at your own peril, but if any
27
+ # significant portion of my code remains then please acknowledge my
28
+ # contribution.
29
+ # portions Copyright 2005 Graham Jenkins
30
+
31
+ require 'active_record/connection_adapters/abstract_adapter'
32
+
33
+ require 'active_record/connection_adapters/oracle_enhanced_connection'
34
+
35
+ module ActiveRecord
36
+ class Base
37
+ # Establishes a connection to the database that's used by all Active Record objects.
38
+ def self.oracle_enhanced_connection(config) #:nodoc:
39
+ if config[:emulate_oracle_adapter] == true
40
+ # allows the enhanced adapter to look like the OracleAdapter. Useful to pick up
41
+ # conditionals in the rails activerecord test suite
42
+ require 'active_record/connection_adapters/emulation/oracle_adapter'
43
+ ConnectionAdapters::OracleAdapter.new(
44
+ ConnectionAdapters::OracleEnhancedConnection.create(config), logger)
45
+ else
46
+ ConnectionAdapters::OracleEnhancedAdapter.new(
47
+ ConnectionAdapters::OracleEnhancedConnection.create(config), logger)
48
+ end
49
+ end
50
+
51
+ # Specify table columns which should be ignored by ActiveRecord.
52
+ def self.ignore_table_columns(*args)
53
+ connection.ignore_table_columns(table_name,*args)
54
+ end
55
+
56
+ # Specify which table columns should be typecasted to Date (without time).
57
+ def self.set_date_columns(*args)
58
+ connection.set_type_for_columns(table_name,:date,*args)
59
+ end
60
+
61
+ # Specify which table columns should be typecasted to Time (or DateTime).
62
+ def self.set_datetime_columns(*args)
63
+ connection.set_type_for_columns(table_name,:datetime,*args)
64
+ end
65
+
66
+ # Specify which table columns should be typecasted to boolean values +true+ or +false+.
67
+ def self.set_boolean_columns(*args)
68
+ connection.set_type_for_columns(table_name,:boolean,*args)
69
+ end
70
+
71
+ # After setting large objects to empty, select the OCI8::LOB
72
+ # and write back the data.
73
+ after_save :enhanced_write_lobs
74
+ def enhanced_write_lobs #:nodoc:
75
+ if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
76
+ !(self.class.custom_create_method || self.class.custom_update_method)
77
+ connection.write_lobs(self.class.table_name, self.class, attributes)
78
+ end
79
+ end
80
+ private :enhanced_write_lobs
81
+
82
+ class << self
83
+ # patch ORDER BY to work with LOBs
84
+ def add_order_with_lobs!(sql, order, scope = :auto)
85
+ if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter)
86
+ order = connection.lob_order_by_expression(self, order) if order
87
+
88
+ orig_scope = scope
89
+ scope = scope(:find) if :auto == scope
90
+ if scope
91
+ new_scope_order = connection.lob_order_by_expression(self, scope[:order])
92
+ if new_scope_order != scope[:order]
93
+ scope = scope.merge(:order => new_scope_order)
94
+ else
95
+ scope = orig_scope
96
+ end
97
+ end
98
+ end
99
+ add_order_without_lobs!(sql, order, scope = :auto)
100
+ end
101
+ private :add_order_with_lobs!
102
+ #:stopdoc:
103
+ alias_method :add_order_without_lobs!, :add_order!
104
+ alias_method :add_order!, :add_order_with_lobs!
105
+ #:startdoc:
106
+ end
107
+
108
+ # Get table comment from schema definition.
109
+ def self.table_comment
110
+ connection.table_comment(self.table_name)
111
+ end
112
+ end
113
+
114
+
115
+ module ConnectionAdapters #:nodoc:
116
+ class OracleEnhancedColumn < Column
117
+
118
+ attr_reader :table_name, :forced_column_type #:nodoc:
119
+
120
+ def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil) #:nodoc:
121
+ @table_name = table_name
122
+ @forced_column_type = forced_column_type
123
+ super(name, default, sql_type, null)
124
+ end
125
+
126
+ def type_cast(value) #:nodoc:
127
+ return guess_date_or_time(value) if type == :datetime && OracleEnhancedAdapter.emulate_dates
128
+ super
129
+ end
130
+
131
+ # convert something to a boolean
132
+ # added y as boolean value
133
+ def self.value_to_boolean(value) #:nodoc:
134
+ if value == true || value == false
135
+ value
136
+ elsif value.is_a?(String) && value.blank?
137
+ nil
138
+ else
139
+ %w(true t 1 y +).include?(value.to_s.downcase)
140
+ end
141
+ end
142
+
143
+ # convert Time or DateTime value to Date for :date columns
144
+ def self.string_to_date(string) #:nodoc:
145
+ return string.to_date if string.is_a?(Time) || string.is_a?(DateTime)
146
+ super
147
+ end
148
+
149
+ # convert Date value to Time for :datetime columns
150
+ def self.string_to_time(string) #:nodoc:
151
+ return string.to_time if string.is_a?(Date) && !OracleEnhancedAdapter.emulate_dates
152
+ super
153
+ end
154
+
155
+ # Get column comment from schema definition.
156
+ # Will work only if using default ActiveRecord connection.
157
+ def comment
158
+ ActiveRecord::Base.connection.column_comment(@table_name, name)
159
+ end
160
+
161
+ private
162
+ def simplified_type(field_type)
163
+ return :boolean if OracleEnhancedAdapter.emulate_booleans && field_type == 'NUMBER(1)'
164
+ return :boolean if OracleEnhancedAdapter.emulate_booleans_from_strings &&
165
+ (forced_column_type == :boolean ||
166
+ OracleEnhancedAdapter.is_boolean_column?(name, field_type, table_name))
167
+
168
+ case field_type
169
+ when /date/i
170
+ forced_column_type ||
171
+ (:date if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name)) ||
172
+ :datetime
173
+ when /timestamp/i then :timestamp
174
+ when /time/i then :datetime
175
+ when /decimal|numeric|number/i
176
+ return :integer if extract_scale(field_type) == 0
177
+ # if column name is ID or ends with _ID
178
+ return :integer if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name)
179
+ :decimal
180
+ else super
181
+ end
182
+ end
183
+
184
+ def guess_date_or_time(value)
185
+ value.respond_to?(:hour) && (value.hour == 0 and value.min == 0 and value.sec == 0) ?
186
+ Date.new(value.year, value.month, value.day) : value
187
+ end
188
+
189
+ class <<self
190
+ protected
191
+
192
+ def fallback_string_to_date(string) #:nodoc:
193
+ if OracleEnhancedAdapter.string_to_date_format || OracleEnhancedAdapter.string_to_time_format
194
+ return (string_to_date_or_time_using_format(string).to_date rescue super)
195
+ end
196
+ super
197
+ end
198
+
199
+ def fallback_string_to_time(string) #:nodoc:
200
+ if OracleEnhancedAdapter.string_to_time_format || OracleEnhancedAdapter.string_to_date_format
201
+ return (string_to_date_or_time_using_format(string).to_time rescue super)
202
+ end
203
+ super
204
+ end
205
+
206
+ def string_to_date_or_time_using_format(string) #:nodoc:
207
+ if OracleEnhancedAdapter.string_to_time_format && dt=Date._strptime(string, OracleEnhancedAdapter.string_to_time_format)
208
+ return Time.mktime(*dt.values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :wday))
209
+ end
210
+ DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format)
211
+ end
212
+
213
+ end
214
+ end
215
+
216
+
217
+ # Oracle enhanced adapter will work with both
218
+ # Ruby 1.8/1.9 ruby-oci8 gem (which provides interface to Oracle OCI client)
219
+ # or with JRuby and Oracle JDBC driver.
220
+ #
221
+ # It should work with Oracle 9i, 10g and 11g databases.
222
+ # Limited set of functionality should work on Oracle 8i as well but several features
223
+ # rely on newer functionality in Oracle database.
224
+ #
225
+ # Usage notes:
226
+ # * Key generation assumes a "${table_name}_seq" sequence is available
227
+ # for all tables; the sequence name can be changed using
228
+ # ActiveRecord::Base.set_sequence_name. When using Migrations, these
229
+ # sequences are created automatically.
230
+ # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
231
+ # Consequently some hacks are employed to map data back to Date or Time
232
+ # in Ruby. Timezones and sub-second precision on timestamps are
233
+ # not supported.
234
+ # * Default values that are functions (such as "SYSDATE") are not
235
+ # supported. This is a restriction of the way ActiveRecord supports
236
+ # default values.
237
+ #
238
+ # Required parameters:
239
+ #
240
+ # * <tt>:username</tt>
241
+ # * <tt>:password</tt>
242
+ # * <tt>:database</tt> - either TNS alias or connection string for OCI client or database name in JDBC connection string
243
+ #
244
+ # Optional parameters:
245
+ #
246
+ # * <tt>:host</tt> - host name for JDBC connection, defaults to "localhost"
247
+ # * <tt>:port</tt> - port number for JDBC connection, defaults to 1521
248
+ # * <tt>:privilege</tt> - set "SYSDBA" if you want to connect with this privilege
249
+ # * <tt>:allow_concurrency</tt> - set to "true" if non-blocking mode should be enabled (just for OCI client)
250
+ # * <tt>:prefetch_rows</tt> - how many rows should be fetched at one time to increase performance, defaults to 100
251
+ # * <tt>:cursor_sharing</tt> - cursor sharing mode to minimize amount of unique statements, defaults to "similar"
252
+ # * <tt>:nls_length_semantics</tt> - semantics of size of VARCHAR2 and CHAR columns, defaults to "CHAR"
253
+ # (meaning that size specifies number of characters and not bytes)
254
+ class OracleEnhancedAdapter < AbstractAdapter
255
+
256
+ ##
257
+ # :singleton-method:
258
+ # By default, the OracleEnhancedAdapter will consider all columns of type <tt>NUMBER(1)</tt>
259
+ # as boolean. If you wish to disable this emulation you can add the following line
260
+ # to your initializer file:
261
+ #
262
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans = false
263
+ cattr_accessor :emulate_booleans
264
+ self.emulate_booleans = true
265
+
266
+ ##
267
+ # :singleton-method:
268
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
269
+ # to Time or DateTime (if value is out of Time value range) value.
270
+ # If you wish that DATE values with hour, minutes and seconds equal to 0 are typecasted
271
+ # to Date then you can add the following line to your initializer file:
272
+ #
273
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates = true
274
+ #
275
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
276
+ # that Date columns are explicily defined with +set_date_columns+ method.
277
+ cattr_accessor :emulate_dates
278
+ self.emulate_dates = false
279
+
280
+ ##
281
+ # :singleton-method:
282
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>DATE</tt>
283
+ # to Time or DateTime (if value is out of Time value range) value.
284
+ # If you wish that DATE columns with "date" in their name (e.g. "creation_date") are typecasted
285
+ # to Date then you can add the following line to your initializer file:
286
+ #
287
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_dates_by_column_name = true
288
+ #
289
+ # As this option can have side effects when unnecessary typecasting is done it is recommended
290
+ # that Date columns are explicily defined with +set_date_columns+ method.
291
+ cattr_accessor :emulate_dates_by_column_name
292
+ self.emulate_dates_by_column_name = false
293
+
294
+ # Check column name to identify if it is Date (and not Time) column.
295
+ # Is used if +emulate_dates_by_column_name+ option is set to +true+.
296
+ # Override this method definition in initializer file if different Date column recognition is needed.
297
+ def self.is_date_column?(name, table_name = nil)
298
+ name =~ /(^|_)date(_|$)/i
299
+ end
300
+
301
+ # instance method uses at first check if column type defined at class level
302
+ def is_date_column?(name, table_name = nil) #:nodoc:
303
+ case get_type_for_column(table_name, name)
304
+ when nil
305
+ self.class.is_date_column?(name, table_name)
306
+ when :date
307
+ true
308
+ else
309
+ false
310
+ end
311
+ end
312
+
313
+ ##
314
+ # :singleton-method:
315
+ # By default, the OracleEnhancedAdapter will typecast all columns of type <tt>NUMBER</tt>
316
+ # (without precision or scale) to Float or BigDecimal value.
317
+ # If you wish that NUMBER columns with name "id" or that end with "_id" are typecasted
318
+ # to Integer then you can add the following line to your initializer file:
319
+ #
320
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_integers_by_column_name = true
321
+ cattr_accessor :emulate_integers_by_column_name
322
+ self.emulate_integers_by_column_name = false
323
+
324
+ # Check column name to identify if it is Integer (and not Float or BigDecimal) column.
325
+ # Is used if +emulate_integers_by_column_name+ option is set to +true+.
326
+ # Override this method definition in initializer file if different Integer column recognition is needed.
327
+ def self.is_integer_column?(name, table_name = nil)
328
+ name =~ /((^|_)id|_(pk|fk))$/i
329
+ end
330
+
331
+ ##
332
+ # :singleton-method:
333
+ # If you wish that CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
334
+ # are typecasted to booleans then you can add the following line to your initializer file:
335
+ #
336
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.emulate_booleans_from_strings = true
337
+ cattr_accessor :emulate_booleans_from_strings
338
+ self.emulate_booleans_from_strings = false
339
+
340
+ # Check column name to identify if it is boolean (and not String) column.
341
+ # Is used if +emulate_booleans_from_strings+ option is set to +true+.
342
+ # Override this method definition in initializer file if different boolean column recognition is needed.
343
+ def self.is_boolean_column?(name, field_type, table_name = nil)
344
+ return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type)
345
+ field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i)
346
+ end
347
+
348
+ # How boolean value should be quoted to String.
349
+ # Used if +emulate_booleans_from_strings+ option is set to +true+.
350
+ def self.boolean_to_string(bool)
351
+ bool ? "Y" : "N"
352
+ end
353
+
354
+ # Specify non-default date format that should be used when assigning string values to :date columns, e.g.:
355
+ #
356
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_date_format = “%d.%m.%Y”
357
+ cattr_accessor :string_to_date_format
358
+ self.string_to_date_format = nil
359
+
360
+ # Specify non-default time format that should be used when assigning string values to :datetime columns, e.g.:
361
+ #
362
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.string_to_time_format = “%d.%m.%Y %H:%M:%S”
363
+ cattr_accessor :string_to_time_format
364
+ self.string_to_time_format = nil
365
+
366
+ def initialize(connection, logger = nil) #:nodoc:
367
+ super
368
+ @quoted_column_names, @quoted_table_names = {}, {}
369
+ end
370
+
371
+ def adapter_name #:nodoc:
372
+ 'OracleEnhanced'
373
+ end
374
+
375
+ def supports_migrations? #:nodoc:
376
+ true
377
+ end
378
+
379
+ def native_database_types #:nodoc:
380
+ {
381
+ :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
382
+ :string => { :name => "VARCHAR2", :limit => 255 },
383
+ :text => { :name => "CLOB" },
384
+ :integer => { :name => "NUMBER", :limit => 38 },
385
+ :float => { :name => "NUMBER" },
386
+ :decimal => { :name => "DECIMAL" },
387
+ :datetime => { :name => "DATE" },
388
+ # changed to native TIMESTAMP type
389
+ # :timestamp => { :name => "DATE" },
390
+ :timestamp => { :name => "TIMESTAMP" },
391
+ :time => { :name => "DATE" },
392
+ :date => { :name => "DATE" },
393
+ :binary => { :name => "BLOB" },
394
+ # if emulate_booleans_from_strings then store booleans in VARCHAR2
395
+ :boolean => emulate_booleans_from_strings ?
396
+ { :name => "VARCHAR2", :limit => 1 } : { :name => "NUMBER", :limit => 1 }
397
+ }
398
+ end
399
+
400
+ # maximum length of Oracle identifiers
401
+ IDENTIFIER_MAX_LENGTH = 30
402
+
403
+ def table_alias_length #:nodoc:
404
+ IDENTIFIER_MAX_LENGTH
405
+ end
406
+
407
+ # QUOTING ==================================================
408
+ #
409
+ # see: abstract/quoting.rb
410
+
411
+ def quote_column_name(name) #:nodoc:
412
+ # camelCase column names need to be quoted; not that anyone using Oracle
413
+ # would really do this, but handling this case means we pass the test...
414
+ @quoted_column_names[name] = name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : quote_oracle_reserved_words(name)
415
+ end
416
+
417
+ # unescaped table name should start with letter and
418
+ # contain letters, digits, _, $ or #
419
+ # can be prefixed with schema name
420
+ # CamelCase table names should be quoted
421
+ def self.valid_table_name?(name) #:nodoc:
422
+ name = name.to_s
423
+ name =~ /^([A-Za-z_0-9]+\.)?[a-z][a-z_0-9\$#]*$/ ||
424
+ name =~ /^([A-Za-z_0-9]+\.)?[A-Z][A-Z_0-9\$#]*$/ ? true : false
425
+ end
426
+
427
+ def quote_table_name(name) #:nodoc:
428
+ # abstract_adapter calls quote_column_name from quote_table_name, so prevent that
429
+ @quoted_table_names[name] ||= if self.class.valid_table_name?(name)
430
+ name
431
+ else
432
+ "\"#{name}\""
433
+ end
434
+ end
435
+
436
+ def quote_string(s) #:nodoc:
437
+ s.gsub(/'/, "''")
438
+ end
439
+
440
+ def quote(value, column = nil) #:nodoc:
441
+ if value && column
442
+ case column.type
443
+ when :text, :binary
444
+ %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
445
+ # NLS_DATE_FORMAT independent TIMESTAMP support
446
+ when :timestamp
447
+ quote_timestamp_with_to_timestamp(value)
448
+ # NLS_DATE_FORMAT independent DATE support
449
+ when :date, :time, :datetime
450
+ quote_date_with_to_date(value)
451
+ else
452
+ super
453
+ end
454
+ elsif value.acts_like?(:date)
455
+ quote_date_with_to_date(value)
456
+ elsif value.acts_like?(:time)
457
+ value.to_i == value.to_f ? quote_date_with_to_date(value) : quote_timestamp_with_to_timestamp(value)
458
+ else
459
+ super
460
+ end
461
+ end
462
+
463
+ def quoted_true #:nodoc:
464
+ return "'#{self.class.boolean_to_string(true)}'" if emulate_booleans_from_strings
465
+ "1"
466
+ end
467
+
468
+ def quoted_false #:nodoc:
469
+ return "'#{self.class.boolean_to_string(false)}'" if emulate_booleans_from_strings
470
+ "0"
471
+ end
472
+
473
+ def quote_date_with_to_date(value) #:nodoc:
474
+ # should support that composite_primary_keys gem will pass date as string
475
+ value = quoted_date(value) if value.acts_like?(:date) || value.acts_like?(:time)
476
+ "TO_DATE('#{value}','YYYY-MM-DD HH24:MI:SS')"
477
+ end
478
+
479
+ def quote_timestamp_with_to_timestamp(value) #:nodoc:
480
+ # add up to 9 digits of fractional seconds to inserted time
481
+ value = "#{quoted_date(value)}.#{("%.6f"%value.to_f).split('.')[1]}" if value.acts_like?(:time)
482
+ "TO_TIMESTAMP('#{value}','YYYY-MM-DD HH24:MI:SS.FF6')"
483
+ end
484
+
485
+ # CONNECTION MANAGEMENT ====================================
486
+ #
487
+
488
+ # If SQL statement fails due to lost connection then reconnect
489
+ # and retry SQL statement if autocommit mode is enabled.
490
+ # By default this functionality is disabled.
491
+ attr_reader :auto_retry #:nodoc:
492
+ @auto_retry = false
493
+
494
+ def auto_retry=(value) #:nodoc:
495
+ @auto_retry = value
496
+ @connection.auto_retry = value if @connection
497
+ end
498
+
499
+ # return raw OCI8 or JDBC connection
500
+ def raw_connection
501
+ @connection.raw_connection
502
+ end
503
+
504
+ # Returns true if the connection is active.
505
+ def active? #:nodoc:
506
+ # Pings the connection to check if it's still good. Note that an
507
+ # #active? method is also available, but that simply returns the
508
+ # last known state, which isn't good enough if the connection has
509
+ # gone stale since the last use.
510
+ @connection.ping
511
+ rescue OracleEnhancedConnectionException
512
+ false
513
+ end
514
+
515
+ # Reconnects to the database.
516
+ def reconnect! #:nodoc:
517
+ @connection.reset!
518
+ rescue OracleEnhancedConnectionException => e
519
+ @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" if @logger
520
+ end
521
+
522
+ # Disconnects from the database.
523
+ def disconnect! #:nodoc:
524
+ @connection.logoff rescue nil
525
+ end
526
+
527
+
528
+ # DATABASE STATEMENTS ======================================
529
+ #
530
+ # see: abstract/database_statements.rb
531
+
532
+ # Executes a SQL statement
533
+ def execute(sql, name = nil)
534
+ log(sql, name) { @connection.exec sql }
535
+ end
536
+
537
+ # Returns an array of arrays containing the field values.
538
+ # Order is the same as that returned by #columns.
539
+ def select_rows(sql, name = nil)
540
+ # last parameter indicates to return also column list
541
+ result, columns = select(sql, name, true)
542
+ result.map{ |v| columns.map{|c| v[c]} }
543
+ end
544
+
545
+ # Returns the next sequence value from a sequence generator. Not generally
546
+ # called directly; used by ActiveRecord to get the next primary key value
547
+ # when inserting a new database record (see #prefetch_primary_key?).
548
+ def next_sequence_value(sequence_name)
549
+ select_one("select #{sequence_name}.nextval id from dual")['id']
550
+ end
551
+
552
+ def begin_db_transaction #:nodoc:
553
+ @connection.autocommit = false
554
+ end
555
+
556
+ def commit_db_transaction #:nodoc:
557
+ @connection.commit
558
+ ensure
559
+ @connection.autocommit = true
560
+ end
561
+
562
+ def rollback_db_transaction #:nodoc:
563
+ @connection.rollback
564
+ ensure
565
+ @connection.autocommit = true
566
+ end
567
+
568
+ def add_limit_offset!(sql, options) #:nodoc:
569
+ # added to_i for limit and offset to protect from SQL injection
570
+ offset = (options[:offset] || 0).to_i
571
+
572
+ if limit = options[:limit]
573
+ limit = limit.to_i
574
+ sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
575
+ elsif offset > 0
576
+ sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
577
+ end
578
+ end
579
+
580
+ # Returns true for Oracle adapter (since Oracle requires primary key
581
+ # values to be pre-fetched before insert). See also #next_sequence_value.
582
+ def prefetch_primary_key?(table_name = nil)
583
+ true
584
+ end
585
+
586
+ def default_sequence_name(table, column) #:nodoc:
587
+ quote_table_name("#{table}_seq")
588
+ end
589
+
590
+
591
+ # Inserts the given fixture into the table. Overridden to properly handle lobs.
592
+ def insert_fixture(fixture, table_name) #:nodoc:
593
+ super
594
+
595
+ klass = fixture.class_name.constantize rescue nil
596
+ if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
597
+ write_lobs(table_name, klass, fixture)
598
+ end
599
+ end
600
+
601
+ # Writes LOB values from attributes, as indicated by the LOB columns of klass.
602
+ def write_lobs(table_name, klass, attributes) #:nodoc:
603
+ # is class with composite primary key>
604
+ is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
605
+ if is_with_cpk
606
+ id = klass.primary_key.map {|pk| attributes[pk.to_s] }
607
+ else
608
+ id = quote(attributes[klass.primary_key])
609
+ end
610
+ klass.columns.select { |col| col.sql_type =~ /LOB$/i }.each do |col|
611
+ value = attributes[col.name]
612
+ # changed sequence of next two lines - should check if value is nil before converting to yaml
613
+ next if value.nil? || (value == '')
614
+ value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
615
+ uncached do
616
+ if is_with_cpk
617
+ lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.composite_where_clause(id)} FOR UPDATE",
618
+ 'Writable Large Object')[col.name]
619
+ else
620
+ lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE",
621
+ 'Writable Large Object')[col.name]
622
+ end
623
+ @connection.write_lob(lob, value.to_s, col.type == :binary)
624
+ end
625
+ end
626
+ end
627
+
628
+ # change LOB column for ORDER BY clause
629
+ # just first 100 characters are taken for ordering
630
+ def lob_order_by_expression(klass, order) #:nodoc:
631
+ return order if order.nil?
632
+ changed = false
633
+ new_order = order.to_s.strip.split(/, */).map do |order_by_col|
634
+ column_name, asc_desc = order_by_col.split(/ +/)
635
+ if column = klass.columns.detect { |col| col.name == column_name && col.sql_type =~ /LOB$/i}
636
+ changed = true
637
+ "DBMS_LOB.SUBSTR(#{column_name},100,1) #{asc_desc}"
638
+ else
639
+ order_by_col
640
+ end
641
+ end.join(', ')
642
+ changed ? new_order : order
643
+ end
644
+
645
+ # SCHEMA STATEMENTS ========================================
646
+ #
647
+ # see: abstract/schema_statements.rb
648
+
649
+ def current_database #:nodoc:
650
+ select_one("select sys_context('userenv','db_name') db from dual")["db"]
651
+ end
652
+
653
+ def tables(name = nil) #:nodoc:
654
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
655
+ select_all("select decode(table_name,upper(table_name),lower(table_name),table_name) name from all_tables where owner = sys_context('userenv','session_user')").map {|t| t['name']}
656
+ end
657
+
658
+ cattr_accessor :all_schema_indexes #:nodoc:
659
+
660
+ # This method selects all indexes at once, and caches them in a class variable.
661
+ # Subsequent index calls get them from the variable, without going to the DB.
662
+ def indexes(table_name, name = nil) #:nodoc:
663
+ (owner, table_name) = @connection.describe(table_name)
664
+ unless all_schema_indexes
665
+ result = select_all(<<-SQL)
666
+ SELECT lower(i.table_name) as table_name, lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
667
+ FROM all_indexes i, all_ind_columns c
668
+ WHERE i.owner = '#{owner}'
669
+ AND i.table_owner = '#{owner}'
670
+ AND c.index_name = i.index_name
671
+ AND c.index_owner = i.owner
672
+ AND NOT EXISTS (SELECT uc.index_name FROM all_constraints uc WHERE uc.index_name = i.index_name AND uc.owner = i.owner AND uc.constraint_type = 'P')
673
+ ORDER BY i.index_name, c.column_position
674
+ SQL
675
+
676
+ current_index = nil
677
+ self.all_schema_indexes = []
678
+
679
+ result.each do |row|
680
+ # have to keep track of indexes because above query returns dups
681
+ # there is probably a better query we could figure out
682
+ if current_index != row['index_name']
683
+ self.all_schema_indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE", [])
684
+ current_index = row['index_name']
685
+ end
686
+
687
+ self.all_schema_indexes.last.columns << row['column_name']
688
+ end
689
+ end
690
+
691
+ # Return the indexes just for the requested table, since AR is structured that way
692
+ table_name = table_name.downcase
693
+ all_schema_indexes.select{|i| i.table == table_name}
694
+ end
695
+
696
+ # set ignored columns for table
697
+ def ignore_table_columns(table_name, *args) #:nodoc:
698
+ @ignore_table_columns ||= {}
699
+ @ignore_table_columns[table_name] ||= []
700
+ @ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
701
+ @ignore_table_columns[table_name].uniq!
702
+ end
703
+
704
+ def ignored_table_columns(table_name) #:nodoc:
705
+ @ignore_table_columns ||= {}
706
+ @ignore_table_columns[table_name]
707
+ end
708
+
709
+ # set explicit type for specified table columns
710
+ def set_type_for_columns(table_name, column_type, *args) #:nodoc:
711
+ @table_column_type ||= {}
712
+ @table_column_type[table_name] ||= {}
713
+ args.each do |col|
714
+ @table_column_type[table_name][col.to_s.downcase] = column_type
715
+ end
716
+ end
717
+
718
+ def get_type_for_column(table_name, column_name) #:nodoc:
719
+ @table_column_type && @table_column_type[table_name] && @table_column_type[table_name][column_name.to_s.downcase]
720
+ end
721
+
722
+ def clear_types_for_columns #:nodoc:
723
+ @table_column_type = nil
724
+ end
725
+
726
+ def columns(table_name, name = nil) #:nodoc:
727
+ # get ignored_columns by original table name
728
+ ignored_columns = ignored_table_columns(table_name)
729
+
730
+ (owner, desc_table_name) = @connection.describe(table_name)
731
+
732
+ table_cols = <<-SQL
733
+ select column_name as name, data_type as sql_type, data_default, nullable,
734
+ decode(data_type, 'NUMBER', data_precision,
735
+ 'FLOAT', data_precision,
736
+ 'VARCHAR2', decode(char_used, 'C', char_length, data_length),
737
+ 'CHAR', decode(char_used, 'C', char_length, data_length),
738
+ null) as limit,
739
+ decode(data_type, 'NUMBER', data_scale, null) as scale
740
+ from all_tab_columns
741
+ where owner = '#{owner}'
742
+ and table_name = '#{desc_table_name}'
743
+ order by column_id
744
+ SQL
745
+
746
+ # added deletion of ignored columns
747
+ select_all(table_cols, name).delete_if do |row|
748
+ ignored_columns && ignored_columns.include?(row['name'].downcase)
749
+ end.map do |row|
750
+ limit, scale = row['limit'], row['scale']
751
+ if limit || scale
752
+ row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
753
+ end
754
+
755
+ # clean up odd default spacing from Oracle
756
+ if row['data_default']
757
+ row['data_default'].sub!(/^(.*?)\s*$/, '\1')
758
+ row['data_default'].sub!(/^'(.*)'$/, '\1')
759
+ row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
760
+ end
761
+
762
+ OracleEnhancedColumn.new(oracle_downcase(row['name']),
763
+ row['data_default'],
764
+ row['sql_type'],
765
+ row['nullable'] == 'Y',
766
+ # pass table name for table specific column definitions
767
+ table_name,
768
+ # pass column type if specified in class definition
769
+ get_type_for_column(table_name, oracle_downcase(row['name'])))
770
+ end
771
+ end
772
+
773
+ ##
774
+ # :singleton-method:
775
+ # Specify default sequence start with value (by default 10000 if not explicitly set), e.g.:
776
+ #
777
+ # ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.default_sequence_start_value = 1
778
+ cattr_accessor :default_sequence_start_value
779
+ self.default_sequence_start_value = 10000
780
+
781
+ # Additional options for +create_table+ method in migration files.
782
+ #
783
+ # You can specify individual starting value in table creation migration file, e.g.:
784
+ #
785
+ # create_table :users, :sequence_start_value => 100 do |t|
786
+ # # ...
787
+ # end
788
+ #
789
+ # You can also specify other sequence definition additional parameters, e.g.:
790
+ #
791
+ # create_table :users, :sequence_start_value => “100 NOCACHE INCREMENT BY 10” do |t|
792
+ # # ...
793
+ # end
794
+ #
795
+ # It is possible to add table and column comments in table creation migration files:
796
+ #
797
+ # create_table :employees, :comment => “Employees and contractors” do |t|
798
+ # t.string :first_name, :comment => “Given name”
799
+ # t.string :last_name, :comment => “Surname”
800
+ # end
801
+ def create_table(name, options = {}, &block)
802
+ create_sequence = options[:id] != false
803
+ column_comments = {}
804
+ super(name, options) do |t|
805
+ # store that primary key was defined in create_table block
806
+ unless create_sequence
807
+ class <<t
808
+ attr_accessor :create_sequence
809
+ def primary_key(*args)
810
+ self.create_sequence = true
811
+ super(*args)
812
+ end
813
+ end
814
+ end
815
+
816
+ # store column comments
817
+ class <<t
818
+ attr_accessor :column_comments
819
+ def column(name, type, options = {})
820
+ if options[:comment]
821
+ self.column_comments ||= {}
822
+ self.column_comments[name] = options[:comment]
823
+ end
824
+ super(name, type, options)
825
+ end
826
+ end
827
+
828
+ result = block.call(t) if block
829
+ create_sequence = create_sequence || t.create_sequence
830
+ column_comments = t.column_comments if t.column_comments
831
+ end
832
+
833
+ seq_name = options[:sequence_name] || quote_table_name("#{name}_seq")
834
+ seq_start_value = options[:sequence_start_value] || default_sequence_start_value
835
+ execute "CREATE SEQUENCE #{seq_name} START WITH #{seq_start_value}" if create_sequence
836
+
837
+ add_table_comment name, options[:comment]
838
+ column_comments.each do |column_name, comment|
839
+ add_comment name, column_name, comment
840
+ end
841
+
842
+ end
843
+
844
+ def rename_table(name, new_name) #:nodoc:
845
+ execute "RENAME #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
846
+ execute "RENAME #{quote_table_name("#{name}_seq")} TO #{quote_table_name("#{new_name}_seq")}" rescue nil
847
+ end
848
+
849
+ def drop_table(name, options = {}) #:nodoc:
850
+ super(name)
851
+ seq_name = options[:sequence_name] || quote_table_name("#{name}_seq")
852
+ execute "DROP SEQUENCE #{seq_name}" rescue nil
853
+ end
854
+
855
+ # clear cached indexes when adding new index
856
+ def add_index(table_name, column_name, options = {}) #:nodoc:
857
+ self.all_schema_indexes = nil
858
+ super
859
+ end
860
+
861
+ # clear cached indexes when removing index
862
+ def remove_index(table_name, options = {}) #:nodoc:
863
+ self.all_schema_indexes = nil
864
+ execute "DROP INDEX #{index_name(table_name, options)}"
865
+ end
866
+
867
+ # returned shortened index name if default is too large
868
+ def index_name(table_name, options) #:nodoc:
869
+ default_name = super(table_name, options)
870
+ return default_name if default_name.length <= IDENTIFIER_MAX_LENGTH
871
+
872
+ # remove 'index', 'on' and 'and' keywords
873
+ shortened_name = "i_#{table_name}_#{Array(options[:column]) * '_'}"
874
+
875
+ # leave just first three letters from each word
876
+ if shortened_name.length > IDENTIFIER_MAX_LENGTH
877
+ shortened_name = shortened_name.split('_').map{|w| w[0,3]}.join('_')
878
+ end
879
+
880
+ if shortened_name.length <= IDENTIFIER_MAX_LENGTH
881
+ @logger.warn "#{adapter_name} shortened index name #{default_name} to #{shortened_name}" if @logger
882
+ return shortened_name
883
+ else
884
+ raise ArgumentError, "#{adapter_name} cannot shorten index name #{default_name}, please use add_index with :name option"
885
+ end
886
+ end
887
+
888
+ def add_column(table_name, column_name, type, options = {}) #:nodoc:
889
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
890
+ options[:type] = type
891
+ add_column_options!(add_column_sql, options)
892
+ execute(add_column_sql)
893
+ end
894
+
895
+ def change_column_default(table_name, column_name, default) #:nodoc:
896
+ execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
897
+ end
898
+
899
+ def change_column_null(table_name, column_name, null, default = nil) #:nodoc:
900
+ column = column_for(table_name, column_name)
901
+
902
+ unless null || default.nil?
903
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
904
+ end
905
+
906
+ change_column table_name, column_name, column.sql_type, :null => null
907
+ end
908
+
909
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
910
+ column = column_for(table_name, column_name)
911
+
912
+ # remove :null option if its value is the same as current column definition
913
+ # otherwise Oracle will raise error
914
+ if options.has_key?(:null) && options[:null] == column.null
915
+ options[:null] = nil
916
+ end
917
+
918
+ change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
919
+ options[:type] = type
920
+ add_column_options!(change_column_sql, options)
921
+ execute(change_column_sql)
922
+ end
923
+
924
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
925
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
926
+ end
927
+
928
+ def remove_column(table_name, column_name) #:nodoc:
929
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
930
+ end
931
+
932
+ def add_comment(table_name, column_name, comment) #:nodoc:
933
+ return if comment.blank?
934
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
935
+ end
936
+
937
+ def add_table_comment(table_name, comment) #:nodoc:
938
+ return if comment.blank?
939
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
940
+ end
941
+
942
+ def table_comment(table_name) #:nodoc:
943
+ (owner, table_name) = @connection.describe(table_name)
944
+ select_value <<-SQL
945
+ SELECT comments FROM all_tab_comments
946
+ WHERE owner = '#{owner}'
947
+ AND table_name = '#{table_name}'
948
+ SQL
949
+ end
950
+
951
+ def column_comment(table_name, column_name) #:nodoc:
952
+ (owner, table_name) = @connection.describe(table_name)
953
+ select_value <<-SQL
954
+ SELECT comments FROM all_col_comments
955
+ WHERE owner = '#{owner}'
956
+ AND table_name = '#{table_name}'
957
+ AND column_name = '#{column_name.upcase}'
958
+ SQL
959
+ end
960
+
961
+ # Find a table's primary key and sequence.
962
+ # *Note*: Only primary key is implemented - sequence will be nil.
963
+ def pk_and_sequence_for(table_name) #:nodoc:
964
+ (owner, table_name) = @connection.describe(table_name)
965
+
966
+ # changed select from all_constraints to user_constraints - much faster in large data dictionaries
967
+ pks = select_values(<<-SQL, 'Primary Key')
968
+ select cc.column_name
969
+ from user_constraints c, user_cons_columns cc
970
+ where c.owner = '#{owner}'
971
+ and c.table_name = '#{table_name}'
972
+ and c.constraint_type = 'P'
973
+ and cc.owner = c.owner
974
+ and cc.constraint_name = c.constraint_name
975
+ SQL
976
+
977
+ # only support single column keys
978
+ pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
979
+ end
980
+
981
+ def structure_dump #:nodoc:
982
+ s = select_all("select sequence_name from user_sequences order by 1").inject("") do |structure, seq|
983
+ structure << "create sequence #{seq.to_a.first.last};\n\n"
984
+ end
985
+
986
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
987
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |structure, table|
988
+ ddl = "create table #{table.to_a.first.last} (\n "
989
+ cols = select_all(%Q{
990
+ select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
991
+ from user_tab_columns
992
+ where table_name = '#{table.to_a.first.last}'
993
+ order by column_id
994
+ }).map do |row|
995
+ col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
996
+ if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
997
+ col << "(#{row['data_precision'].to_i}"
998
+ col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
999
+ col << ')'
1000
+ elsif row['data_type'].include?('CHAR')
1001
+ length = row['char_used'] == 'C' ? row['char_length'].to_i : row['data_length'].to_i
1002
+ col << "(#{length})"
1003
+ end
1004
+ col << " default #{row['data_default']}" if !row['data_default'].nil?
1005
+ col << ' not null' if row['nullable'] == 'N'
1006
+ col
1007
+ end
1008
+ ddl << cols.join(",\n ")
1009
+ ddl << ");\n\n"
1010
+ structure << ddl
1011
+ end
1012
+ end
1013
+
1014
+ # Extract all stored procedures, packages, synonyms and views.
1015
+ def structure_dump_db_stored_code #:nodoc:
1016
+ structure = "\n"
1017
+ select_all("select distinct name, type
1018
+ from all_source
1019
+ where type in ('PROCEDURE', 'PACKAGE', 'PACKAGE BODY', 'FUNCTION')
1020
+ and owner = sys_context('userenv','session_user')").inject("\n\n") do |structure, source|
1021
+ ddl = "create or replace \n "
1022
+ lines = select_all(%Q{
1023
+ select text
1024
+ from all_source
1025
+ where name = '#{source['name']}'
1026
+ and type = '#{source['type']}'
1027
+ and owner = sys_context('userenv','session_user')
1028
+ order by line
1029
+ }).map do |row|
1030
+ ddl << row['text'] if row['text'].size > 1
1031
+ end
1032
+ ddl << ";"
1033
+ structure << ddl << "\n"
1034
+ end
1035
+
1036
+ # export views
1037
+ select_all("select view_name, text from user_views").inject(structure) do |structure, view|
1038
+ ddl = "create or replace view #{view['view_name']} AS\n "
1039
+ # any views with empty lines will cause OCI to barf when loading. remove blank lines =/
1040
+ ddl << view['text'].gsub(/^\n/, '')
1041
+ ddl << ";\n\n"
1042
+ structure << ddl
1043
+ end
1044
+
1045
+ # export synonyms
1046
+ select_all("select owner, synonym_name, table_name, table_owner
1047
+ from all_synonyms
1048
+ where table_owner = sys_context('userenv','session_user') ").inject(structure) do |structure, synonym|
1049
+ ddl = "create or replace #{synonym['owner'] == 'PUBLIC' ? 'PUBLIC' : '' } SYNONYM #{synonym['synonym_name']} for #{synonym['table_owner']}.#{synonym['table_name']};\n\n"
1050
+ structure << ddl;
1051
+ end
1052
+ end
1053
+
1054
+
1055
+ def structure_drop #:nodoc:
1056
+ s = select_all("select sequence_name from user_sequences order by 1").inject("") do |drop, seq|
1057
+ drop << "drop sequence #{seq.to_a.first.last};\n\n"
1058
+ end
1059
+
1060
+ # changed select from user_tables to all_tables - much faster in large data dictionaries
1061
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |drop, table|
1062
+ drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
1063
+ end
1064
+ end
1065
+
1066
+
1067
+ def add_column_options!(sql, options) #:nodoc:
1068
+ type = options[:type] || ((column = options[:column]) && column.type)
1069
+ type = type && type.to_sym
1070
+ # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
1071
+ if options_include_default?(options)
1072
+ if type == :text
1073
+ sql << " DEFAULT #{quote(options[:default])}"
1074
+ else
1075
+ # from abstract adapter
1076
+ sql << " DEFAULT #{quote(options[:default], options[:column])}"
1077
+ end
1078
+ end
1079
+ # must explicitly add NULL or NOT NULL to allow change_column to work on migrations
1080
+ if options[:null] == false
1081
+ sql << " NOT NULL"
1082
+ elsif options[:null] == true
1083
+ sql << " NULL" unless type == :primary_key
1084
+ end
1085
+ end
1086
+
1087
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
1088
+ #
1089
+ # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
1090
+ # queries. However, with those columns included in the SELECT DISTINCT list, you
1091
+ # won't actually get a distinct list of the column you want (presuming the column
1092
+ # has duplicates with multiple values for the ordered-by columns. So we use the
1093
+ # FIRST_VALUE function to get a single (first) value for each column, effectively
1094
+ # making every row the same.
1095
+ #
1096
+ # distinct("posts.id", "posts.created_at desc")
1097
+ def distinct(columns, order_by) #:nodoc:
1098
+ return "DISTINCT #{columns}" if order_by.blank?
1099
+
1100
+ # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
1101
+ # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
1102
+ order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
1103
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
1104
+ "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
1105
+ end
1106
+ sql = "DISTINCT #{columns}, "
1107
+ sql << order_columns * ", "
1108
+ end
1109
+
1110
+ # ORDER BY clause for the passed order option.
1111
+ #
1112
+ # Uses column aliases as defined by #distinct.
1113
+ def add_order_by_for_association_limiting!(sql, options) #:nodoc:
1114
+ return sql if options[:order].blank?
1115
+
1116
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
1117
+ order.map! {|s| $1 if s =~ / (.*)/}
1118
+ order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
1119
+
1120
+ sql << " ORDER BY #{order}"
1121
+ end
1122
+
1123
+ protected
1124
+
1125
+ def translate_exception(exception, message) #:nodoc:
1126
+ case @connection.error_code(exception)
1127
+ when 1
1128
+ RecordNotUnique.new(message, exception)
1129
+ when 2291
1130
+ InvalidForeignKey.new(message, exception)
1131
+ else
1132
+ super
1133
+ end
1134
+ end
1135
+
1136
+ private
1137
+
1138
+ def select(sql, name = nil, return_column_names = false)
1139
+ log(sql, name) do
1140
+ @connection.select(sql, name, return_column_names)
1141
+ end
1142
+ end
1143
+
1144
+ def oracle_downcase(column_name)
1145
+ @connection.oracle_downcase(column_name)
1146
+ end
1147
+
1148
+ def column_for(table_name, column_name)
1149
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
1150
+ raise "No such column: #{table_name}.#{column_name}"
1151
+ end
1152
+ column
1153
+ end
1154
+
1155
+ end
1156
+ end
1157
+ end
1158
+
1159
+ # Added LOB writing callback for sessions stored in database
1160
+ # Otherwise it is not working as Session class is defined before OracleAdapter is loaded in Rails 2.0
1161
+ if defined?(CGI::Session::ActiveRecordStore::Session)
1162
+ if !CGI::Session::ActiveRecordStore::Session.respond_to?(:after_save_callback_chain) ||
1163
+ CGI::Session::ActiveRecordStore::Session.after_save_callback_chain.detect{|cb| cb.method == :enhanced_write_lobs}.nil?
1164
+ #:stopdoc:
1165
+ class CGI::Session::ActiveRecordStore::Session
1166
+ after_save :enhanced_write_lobs
1167
+ end
1168
+ #:startdoc:
1169
+ end
1170
+ end
1171
+
1172
+ # Load custom create, update, delete methods functionality
1173
+ # rescue LoadError if ruby-plsql gem cannot be loaded
1174
+ begin
1175
+ require 'active_record/connection_adapters/oracle_enhanced_procedures'
1176
+ rescue LoadError
1177
+ if defined?(RAILS_DEFAULT_LOGGER)
1178
+ RAILS_DEFAULT_LOGGER.info "INFO: ActiveRecord oracle_enhanced adapter could not load ruby-plsql gem. "+
1179
+ "Custom create, update and delete methods will not be available."
1180
+ end
1181
+ end
1182
+
1183
+ # Load additional methods for composite_primary_keys support
1184
+ require 'active_record/connection_adapters/oracle_enhanced_cpk'
1185
+
1186
+ # Load patch for dirty tracking methods
1187
+ require 'active_record/connection_adapters/oracle_enhanced_dirty'
1188
+
1189
+ # Load rake tasks definitions
1190
+ begin
1191
+ require 'active_record/connection_adapters/oracle_enhanced_tasks'
1192
+ rescue LoadError
1193
+ end if defined?(RAILS_ROOT)
1194
+
1195
+ # Handles quoting of oracle reserved words
1196
+ require 'active_record/connection_adapters/oracle_enhanced_reserved_words'
1197
+
1198
+ # Add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1199
+ require 'active_record/connection_adapters/oracle_enhanced_core_ext'
1200
+