saberma-activerecord-oracle_enhanced-adapter-nvarchar2 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +66 -0
  5. data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
  6. data/lib/active_record/connection_adapters/oracle_enhanced.rake +44 -0
  7. data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1017 -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 +341 -0
  13. data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +351 -0
  14. data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +124 -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 +590 -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 +943 -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
data/History.txt ADDED
@@ -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
data/License.txt ADDED
@@ -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.
data/Manifest.txt ADDED
@@ -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
data/README.rdoc ADDED
@@ -0,0 +1,66 @@
1
+ = activerecord-oracle_enhanced-adapter
2
+
3
+ * http://rubyforge.org/projects/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 more 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 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
+
43
+ == LICENSE:
44
+
45
+ (The MIT License)
46
+
47
+ Copyright (c) 2009 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
48
+
49
+ Permission is hereby granted, free of charge, to any person obtaining
50
+ a copy of this software and associated documentation files (the
51
+ 'Software'), to deal in the Software without restriction, including
52
+ without limitation the rights to use, copy, modify, merge, publish,
53
+ distribute, sublicense, and/or sell copies of the Software, and to
54
+ permit persons to whom the Software is furnished to do so, subject to
55
+ the following conditions:
56
+
57
+ The above copyright notice and this permission notice shall be
58
+ included in all copies or substantial portions of the Software.
59
+
60
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
61
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
62
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
63
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
64
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
65
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
66
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,5 @@
1
+ class ActiveRecord::ConnectionAdapters::OracleAdapter < ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter
2
+ def adapter_name
3
+ 'Oracle'
4
+ end
5
+ end
@@ -0,0 +1,44 @@
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
+ end
24
+ end
25
+
26
+ namespace :test do
27
+ redefine_task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
28
+ abcs = ActiveRecord::Base.configurations
29
+ ActiveRecord::Base.establish_connection(:test)
30
+ IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |ddl|
31
+ ActiveRecord::Base.connection.execute(ddl.chop)
32
+ end
33
+ end
34
+
35
+ redefine_task :purge => :environment do
36
+ abcs = ActiveRecord::Base.configurations
37
+ ActiveRecord::Base.establish_connection(:test)
38
+ ActiveRecord::Base.connection.structure_drop.split("\n\n").each do |ddl|
39
+ ActiveRecord::Base.connection.execute(ddl.chop)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,1017 @@
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
+ def self.oracle_enhanced_connection(config)
38
+ if config[:emulate_oracle_adapter] == true
39
+ # allows the enhanced adapter to look like the OracleAdapter. Useful to pick up
40
+ # conditionals in the rails activerecord test suite
41
+ require 'active_record/connection_adapters/emulation/oracle_adapter'
42
+ ConnectionAdapters::OracleAdapter.new(
43
+ ConnectionAdapters::OracleEnhancedConnection.create(config), logger)
44
+ else
45
+ ConnectionAdapters::OracleEnhancedAdapter.new(
46
+ ConnectionAdapters::OracleEnhancedConnection.create(config), logger)
47
+ end
48
+ end
49
+
50
+ # RSI: specify table columns which should be ifnored
51
+ def self.ignore_table_columns(*args)
52
+ connection.ignore_table_columns(table_name,*args)
53
+ end
54
+
55
+ # RSI: specify which table columns should be treated as date (without time)
56
+ def self.set_date_columns(*args)
57
+ connection.set_type_for_columns(table_name,:date,*args)
58
+ end
59
+
60
+ # RSI: specify which table columns should be treated as datetime
61
+ def self.set_datetime_columns(*args)
62
+ connection.set_type_for_columns(table_name,:datetime,*args)
63
+ end
64
+
65
+ # RSI: specify which table columns should be treated as booleans
66
+ def self.set_boolean_columns(*args)
67
+ connection.set_type_for_columns(table_name,:boolean,*args)
68
+ end
69
+
70
+ # After setting large objects to empty, select the OCI8::LOB
71
+ # and write back the data.
72
+ after_save :enhanced_write_lobs
73
+ def enhanced_write_lobs #:nodoc:
74
+ if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
75
+ !(self.class.custom_create_method || self.class.custom_update_method)
76
+ connection.write_lobs(self.class.table_name, self.class, attributes)
77
+ end
78
+ end
79
+ private :enhanced_write_lobs
80
+
81
+ class << self
82
+ # RSI: patch ORDER BY to work with LOBs
83
+ def add_order_with_lobs!(sql, order, scope = :auto)
84
+ if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter)
85
+ order = connection.lob_order_by_expression(self, order) if order
86
+
87
+ orig_scope = scope
88
+ scope = scope(:find) if :auto == scope
89
+ if scope
90
+ new_scope_order = connection.lob_order_by_expression(self, scope[:order])
91
+ if new_scope_order != scope[:order]
92
+ scope = scope.merge(:order => new_scope_order)
93
+ else
94
+ scope = orig_scope
95
+ end
96
+ end
97
+ end
98
+ add_order_without_lobs!(sql, order, scope = :auto)
99
+ end
100
+ private :add_order_with_lobs!
101
+ alias_method :add_order_without_lobs!, :add_order!
102
+ alias_method :add_order!, :add_order_with_lobs!
103
+ end
104
+
105
+ # RSI: get table comment from schema definition
106
+ def self.table_comment
107
+ connection.table_comment(self.table_name)
108
+ end
109
+ end
110
+
111
+
112
+ module ConnectionAdapters #:nodoc:
113
+ class OracleEnhancedColumn < Column #:nodoc:
114
+
115
+ attr_reader :table_name, :forced_column_type
116
+
117
+ def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil)
118
+ @table_name = table_name
119
+ @forced_column_type = forced_column_type
120
+ super(name, default, sql_type, null)
121
+ end
122
+
123
+ def type_cast(value)
124
+ return guess_date_or_time(value) if type == :datetime && OracleEnhancedAdapter.emulate_dates
125
+ super
126
+ end
127
+
128
+ # convert something to a boolean
129
+ # RSI: added y as boolean value
130
+ def self.value_to_boolean(value)
131
+ if value == true || value == false
132
+ value
133
+ elsif value.is_a?(String) && value.blank?
134
+ nil
135
+ else
136
+ %w(true t 1 y +).include?(value.to_s.downcase)
137
+ end
138
+ end
139
+
140
+ # RSI: convert Time or DateTime value to Date for :date columns
141
+ def self.string_to_date(string)
142
+ return string.to_date if string.is_a?(Time) || string.is_a?(DateTime)
143
+ super
144
+ end
145
+
146
+ # RSI: convert Date value to Time for :datetime columns
147
+ def self.string_to_time(string)
148
+ return string.to_time if string.is_a?(Date) && !OracleEnhancedAdapter.emulate_dates
149
+ super
150
+ end
151
+
152
+ # RSI: get column comment from schema definition
153
+ # will work only if using default ActiveRecord connection
154
+ def comment
155
+ ActiveRecord::Base.connection.column_comment(@table_name, name)
156
+ end
157
+
158
+ private
159
+ def simplified_type(field_type)
160
+ return :boolean if OracleEnhancedAdapter.emulate_booleans && field_type == 'NUMBER(1)'
161
+ return :boolean if OracleEnhancedAdapter.emulate_booleans_from_strings &&
162
+ (forced_column_type == :boolean ||
163
+ OracleEnhancedAdapter.is_boolean_column?(name, field_type, table_name))
164
+
165
+ case field_type
166
+ when /date/i
167
+ forced_column_type ||
168
+ (:date if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name)) ||
169
+ :datetime
170
+ when /timestamp/i then :timestamp
171
+ when /time/i then :datetime
172
+ when /decimal|numeric|number/i
173
+ return :integer if extract_scale(field_type) == 0
174
+ # RSI: if column name is ID or ends with _ID
175
+ return :integer if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name)
176
+ :decimal
177
+ else super
178
+ end
179
+ end
180
+
181
+ def guess_date_or_time(value)
182
+ value.respond_to?(:hour) && (value.hour == 0 and value.min == 0 and value.sec == 0) ?
183
+ Date.new(value.year, value.month, value.day) : value
184
+ end
185
+
186
+ class <<self
187
+ protected
188
+
189
+ def fallback_string_to_date(string)
190
+ if OracleEnhancedAdapter.string_to_date_format || OracleEnhancedAdapter.string_to_time_format
191
+ return (string_to_date_or_time_using_format(string).to_date rescue super)
192
+ end
193
+ super
194
+ end
195
+
196
+ def fallback_string_to_time(string)
197
+ if OracleEnhancedAdapter.string_to_time_format || OracleEnhancedAdapter.string_to_date_format
198
+ return (string_to_date_or_time_using_format(string).to_time rescue super)
199
+ end
200
+ super
201
+ end
202
+
203
+ def string_to_date_or_time_using_format(string)
204
+ if OracleEnhancedAdapter.string_to_time_format && dt=Date._strptime(string, OracleEnhancedAdapter.string_to_time_format)
205
+ return Time.mktime(*dt.values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :wday))
206
+ end
207
+ DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format)
208
+ end
209
+
210
+ end
211
+ end
212
+
213
+
214
+ # This is an Oracle/OCI adapter for the ActiveRecord persistence
215
+ # framework. It relies upon the OCI8 driver, which works with Oracle 8i
216
+ # and above. Most recent development has been on Debian Linux against
217
+ # a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13.
218
+ # See: http://rubyforge.org/projects/ruby-oci8/
219
+ #
220
+ # Usage notes:
221
+ # * Key generation assumes a "${table_name}_seq" sequence is available
222
+ # for all tables; the sequence name can be changed using
223
+ # ActiveRecord::Base.set_sequence_name. When using Migrations, these
224
+ # sequences are created automatically.
225
+ # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
226
+ # Consequently some hacks are employed to map data back to Date or Time
227
+ # in Ruby. If the column_name ends in _time it's created as a Ruby Time.
228
+ # Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else
229
+ # it's a Ruby Time. This is a bit nasty - but if you use Duck Typing
230
+ # you'll probably not care very much. In 9i and up it's tempting to
231
+ # map DATE to Date and TIMESTAMP to Time, but too many databases use
232
+ # DATE for both. Timezones and sub-second precision on timestamps are
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
+ # * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which
238
+ # is supported in Oracle9i and later. You will need to use #finder_sql for
239
+ # has_and_belongs_to_many associations to run against Oracle8.
240
+ #
241
+ # Required parameters:
242
+ #
243
+ # * <tt>:username</tt>
244
+ # * <tt>:password</tt>
245
+ # * <tt>:database</tt>
246
+ class OracleEnhancedAdapter < AbstractAdapter
247
+
248
+ @@emulate_booleans = true
249
+ cattr_accessor :emulate_booleans
250
+
251
+ @@emulate_dates = false
252
+ cattr_accessor :emulate_dates
253
+
254
+ # RSI: set to true if columns with DATE in their name should be emulated as date
255
+ @@emulate_dates_by_column_name = false
256
+ cattr_accessor :emulate_dates_by_column_name
257
+ def self.is_date_column?(name, table_name = nil)
258
+ name =~ /(^|_)date(_|$)/i
259
+ end
260
+ # RSI: instance method uses at first check if column type defined at class level
261
+ def is_date_column?(name, table_name = nil)
262
+ case get_type_for_column(table_name, name)
263
+ when nil
264
+ self.class.is_date_column?(name, table_name)
265
+ when :date
266
+ true
267
+ else
268
+ false
269
+ end
270
+ end
271
+
272
+ # RSI: set to true if NUMBER columns with ID at the end of their name should be emulated as integers
273
+ @@emulate_integers_by_column_name = false
274
+ cattr_accessor :emulate_integers_by_column_name
275
+ def self.is_integer_column?(name, table_name = nil)
276
+ name =~ /(^|_)id$/i
277
+ end
278
+
279
+ # RSI: set to true if CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
280
+ # should be emulated as booleans
281
+ @@emulate_booleans_from_strings = false
282
+ cattr_accessor :emulate_booleans_from_strings
283
+ def self.is_boolean_column?(name, field_type, table_name = nil)
284
+ return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type)
285
+ field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i)
286
+ end
287
+ def self.boolean_to_string(bool)
288
+ bool ? "Y" : "N"
289
+ end
290
+
291
+ # RSI: use to set NLS specific date formats which will be used when assigning string to :date and :datetime columns
292
+ @@string_to_date_format = @@string_to_time_format = nil
293
+ cattr_accessor :string_to_date_format, :string_to_time_format
294
+
295
+ def adapter_name #:nodoc:
296
+ 'OracleEnhanced'
297
+ end
298
+
299
+ def supports_migrations? #:nodoc:
300
+ true
301
+ end
302
+
303
+ def native_database_types #:nodoc:
304
+ {
305
+ :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
306
+ :string => { :name => "VARCHAR2", :limit => 255 },
307
+ :text => { :name => "CLOB" },
308
+ :integer => { :name => "NUMBER", :limit => 38 },
309
+ :float => { :name => "NUMBER" },
310
+ :decimal => { :name => "DECIMAL" },
311
+ :datetime => { :name => "DATE" },
312
+ # RSI: changed to native TIMESTAMP type
313
+ # :timestamp => { :name => "DATE" },
314
+ :timestamp => { :name => "TIMESTAMP" },
315
+ :time => { :name => "DATE" },
316
+ :date => { :name => "DATE" },
317
+ :binary => { :name => "BLOB" },
318
+ # RSI: if emulate_booleans_from_strings then store booleans in VARCHAR2
319
+ :boolean => emulate_booleans_from_strings ?
320
+ { :name => "VARCHAR2", :limit => 1 } : { :name => "NUMBER", :limit => 1 }
321
+ }
322
+ end
323
+
324
+ def table_alias_length
325
+ 30
326
+ end
327
+
328
+ # Returns an array of arrays containing the field values.
329
+ # Order is the same as that returned by #columns.
330
+ def select_rows(sql, name = nil)
331
+ # last parameter indicates to return also column list
332
+ result, columns = select(sql, name, true)
333
+ result.map{ |v| columns.map{|c| v[c]} }
334
+ end
335
+
336
+ # QUOTING ==================================================
337
+ #
338
+ # see: abstract/quoting.rb
339
+
340
+ # camelCase column names need to be quoted; not that anyone using Oracle
341
+ # would really do this, but handling this case means we pass the test...
342
+ def quote_column_name(name) #:nodoc:
343
+ name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : quote_oracle_reserved_words(name)
344
+ end
345
+
346
+ # unescaped table name should start with letter and
347
+ # contain letters, digits, _, $ or #
348
+ # can be prefixed with schema name
349
+ # CamelCase table names should be quoted
350
+ def self.valid_table_name?(name)
351
+ name = name.to_s
352
+ name =~ /^([A-Za-z_0-9]+\.)?[a-z][a-z_0-9\$#]*$/ ||
353
+ name =~ /^([A-Za-z_0-9]+\.)?[A-Z][A-Z_0-9\$#]*$/ ? true : false
354
+ end
355
+
356
+ # abstract_adapter calls quote_column_name from quote_table_name, so prevent that
357
+ def quote_table_name(name)
358
+ if self.class.valid_table_name?(name)
359
+ name
360
+ else
361
+ "\"#{name}\""
362
+ end
363
+ end
364
+
365
+ def quote_string(s) #:nodoc:
366
+ s.gsub(/'/, "''")
367
+ end
368
+
369
+ def quote(value, column = nil) #:nodoc:
370
+ if value && column
371
+ case column.type
372
+ when :text, :binary
373
+ %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
374
+ # RSI: TIMESTAMP support
375
+ when :timestamp
376
+ quote_timestamp_with_to_timestamp(value)
377
+ # RSI: NLS_DATE_FORMAT independent DATE support
378
+ when :date, :time, :datetime
379
+ quote_date_with_to_date(value)
380
+ else
381
+ super
382
+ end
383
+ elsif value.acts_like?(:date)
384
+ quote_date_with_to_date(value)
385
+ elsif value.acts_like?(:time)
386
+ value.to_i == value.to_f ? quote_date_with_to_date(value) : quote_timestamp_with_to_timestamp(value)
387
+ else
388
+ super
389
+ end
390
+ end
391
+
392
+ def quoted_true
393
+ return "'#{self.class.boolean_to_string(true)}'" if emulate_booleans_from_strings
394
+ "1"
395
+ end
396
+
397
+ def quoted_false
398
+ return "'#{self.class.boolean_to_string(false)}'" if emulate_booleans_from_strings
399
+ "0"
400
+ end
401
+
402
+ # RSI: should support that composite_primary_keys gem will pass date as string
403
+ def quote_date_with_to_date(value)
404
+ value = value.to_s(:db) if value.acts_like?(:date) || value.acts_like?(:time)
405
+ "TO_DATE('#{value}','YYYY-MM-DD HH24:MI:SS')"
406
+ end
407
+
408
+ def quote_timestamp_with_to_timestamp(value)
409
+ # add up to 9 digits of fractional seconds to inserted time
410
+ value = "#{value.to_s(:db)}.#{("%.6f"%value.to_f).split('.')[1]}" if value.acts_like?(:time)
411
+ "TO_TIMESTAMP('#{value}','YYYY-MM-DD HH24:MI:SS.FF6')"
412
+ end
413
+
414
+ # CONNECTION MANAGEMENT ====================================
415
+ #
416
+
417
+ # If SQL statement fails due to lost connection then reconnect
418
+ # and retry SQL statement if autocommit mode is enabled.
419
+ # By default this functionality is disabled.
420
+ @auto_retry = false
421
+ attr_reader :auto_retry
422
+ def auto_retry=(value)
423
+ @auto_retry = value
424
+ @connection.auto_retry = value if @connection
425
+ end
426
+
427
+ def raw_connection
428
+ @connection.raw_connection
429
+ end
430
+
431
+ # Returns true if the connection is active.
432
+ def active?
433
+ # Pings the connection to check if it's still good. Note that an
434
+ # #active? method is also available, but that simply returns the
435
+ # last known state, which isn't good enough if the connection has
436
+ # gone stale since the last use.
437
+ @connection.ping
438
+ rescue OracleEnhancedConnectionException
439
+ false
440
+ end
441
+
442
+ # Reconnects to the database.
443
+ def reconnect!
444
+ @connection.reset!
445
+ rescue OracleEnhancedConnectionException => e
446
+ @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
447
+ end
448
+
449
+ # Disconnects from the database.
450
+ def disconnect!
451
+ @connection.logoff rescue nil
452
+ end
453
+
454
+
455
+ # DATABASE STATEMENTS ======================================
456
+ #
457
+ # see: abstract/database_statements.rb
458
+
459
+ def execute(sql, name = nil) #:nodoc:
460
+ log(sql, name) { @connection.exec sql }
461
+ end
462
+
463
+ # Returns the next sequence value from a sequence generator. Not generally
464
+ # called directly; used by ActiveRecord to get the next primary key value
465
+ # when inserting a new database record (see #prefetch_primary_key?).
466
+ def next_sequence_value(sequence_name)
467
+ select_one("select #{sequence_name}.nextval id from dual")['id']
468
+ end
469
+
470
+ def begin_db_transaction #:nodoc:
471
+ @connection.autocommit = false
472
+ end
473
+
474
+ def commit_db_transaction #:nodoc:
475
+ @connection.commit
476
+ ensure
477
+ @connection.autocommit = true
478
+ end
479
+
480
+ def rollback_db_transaction #:nodoc:
481
+ @connection.rollback
482
+ ensure
483
+ @connection.autocommit = true
484
+ end
485
+
486
+ def add_limit_offset!(sql, options) #:nodoc:
487
+ # RSI: added to_i for limit and offset to protect from SQL injection
488
+ offset = (options[:offset] || 0).to_i
489
+
490
+ if limit = options[:limit]
491
+ limit = limit.to_i
492
+ sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
493
+ elsif offset > 0
494
+ sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
495
+ end
496
+ end
497
+
498
+ # Returns true for Oracle adapter (since Oracle requires primary key
499
+ # values to be pre-fetched before insert). See also #next_sequence_value.
500
+ def prefetch_primary_key?(table_name = nil)
501
+ true
502
+ end
503
+
504
+ def default_sequence_name(table, column) #:nodoc:
505
+ quote_table_name("#{table}_seq")
506
+ end
507
+
508
+
509
+ # Inserts the given fixture into the table. Overridden to properly handle lobs.
510
+ def insert_fixture(fixture, table_name)
511
+ super
512
+
513
+ klass = fixture.class_name.constantize rescue nil
514
+ if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
515
+ write_lobs(table_name, klass, fixture)
516
+ end
517
+ end
518
+
519
+ # Writes LOB values from attributes, as indicated by the LOB columns of klass.
520
+ def write_lobs(table_name, klass, attributes)
521
+ # is class with composite primary key>
522
+ is_with_cpk = klass.respond_to?(:composite?) && klass.composite?
523
+ if is_with_cpk
524
+ id = klass.primary_key.map {|pk| attributes[pk.to_s] }
525
+ else
526
+ id = quote(attributes[klass.primary_key])
527
+ end
528
+ klass.columns.select { |col| col.sql_type =~ /LOB$/i }.each do |col|
529
+ value = attributes[col.name]
530
+ # RSI: changed sequence of next two lines - should check if value is nil before converting to yaml
531
+ next if value.nil? || (value == '')
532
+ value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
533
+ uncached do
534
+ if is_with_cpk
535
+ lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.composite_where_clause(id)} FOR UPDATE",
536
+ 'Writable Large Object')[col.name]
537
+ else
538
+ lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE",
539
+ 'Writable Large Object')[col.name]
540
+ end
541
+ @connection.write_lob(lob, value, col.type == :binary)
542
+ end
543
+ end
544
+ end
545
+
546
+ # RSI: change LOB column for ORDER BY clause
547
+ # just first 100 characters are taken for ordering
548
+ def lob_order_by_expression(klass, order)
549
+ return order if order.nil?
550
+ changed = false
551
+ new_order = order.to_s.strip.split(/, */).map do |order_by_col|
552
+ column_name, asc_desc = order_by_col.split(/ +/)
553
+ if column = klass.columns.detect { |col| col.name == column_name && col.sql_type =~ /LOB$/i}
554
+ changed = true
555
+ "DBMS_LOB.SUBSTR(#{column_name},100,1) #{asc_desc}"
556
+ else
557
+ order_by_col
558
+ end
559
+ end.join(', ')
560
+ changed ? new_order : order
561
+ end
562
+
563
+ # SCHEMA STATEMENTS ========================================
564
+ #
565
+ # see: abstract/schema_statements.rb
566
+
567
+ def current_database #:nodoc:
568
+ select_one("select sys_context('userenv','db_name') db from dual")["db"]
569
+ end
570
+
571
+ # RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
572
+ def tables(name = nil) #:nodoc:
573
+ select_all("select decode(table_name,upper(table_name),lower(table_name),table_name) name from all_tables where owner = sys_context('userenv','session_user')").map {|t| t['name']}
574
+ end
575
+
576
+ cattr_accessor :all_schema_indexes
577
+
578
+ # This method selects all indexes at once, and caches them in a class variable.
579
+ # Subsequent index calls get them from the variable, without going to the DB.
580
+ def indexes(table_name, name = nil)
581
+ (owner, table_name) = @connection.describe(table_name)
582
+ unless all_schema_indexes
583
+ result = select_all(<<-SQL)
584
+ SELECT lower(i.table_name) as table_name, lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
585
+ FROM all_indexes i, all_ind_columns c
586
+ WHERE i.owner = '#{owner}'
587
+ AND i.table_owner = '#{owner}'
588
+ AND c.index_name = i.index_name
589
+ AND c.index_owner = i.owner
590
+ 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')
591
+ ORDER BY i.index_name, c.column_position
592
+ SQL
593
+
594
+ current_index = nil
595
+ self.all_schema_indexes = []
596
+
597
+ result.each do |row|
598
+ # have to keep track of indexes because above query returns dups
599
+ # there is probably a better query we could figure out
600
+ if current_index != row['index_name']
601
+ self.all_schema_indexes << ::ActiveRecord::ConnectionAdapters::IndexDefinition.new(row['table_name'], row['index_name'], row['uniqueness'] == "UNIQUE", [])
602
+ current_index = row['index_name']
603
+ end
604
+
605
+ self.all_schema_indexes.last.columns << row['column_name']
606
+ end
607
+ end
608
+
609
+ # Return the indexes just for the requested table, since AR is structured that way
610
+ table_name = table_name.downcase
611
+ all_schema_indexes.select{|i| i.table == table_name}
612
+ end
613
+
614
+ # RSI: set ignored columns for table
615
+ def ignore_table_columns(table_name, *args)
616
+ @ignore_table_columns ||= {}
617
+ @ignore_table_columns[table_name] ||= []
618
+ @ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
619
+ @ignore_table_columns[table_name].uniq!
620
+ end
621
+
622
+ def ignored_table_columns(table_name)
623
+ @ignore_table_columns ||= {}
624
+ @ignore_table_columns[table_name]
625
+ end
626
+
627
+ # RSI: set explicit type for specified table columns
628
+ def set_type_for_columns(table_name, column_type, *args)
629
+ @table_column_type ||= {}
630
+ @table_column_type[table_name] ||= {}
631
+ args.each do |col|
632
+ @table_column_type[table_name][col.to_s.downcase] = column_type
633
+ end
634
+ end
635
+
636
+ def get_type_for_column(table_name, column_name)
637
+ @table_column_type && @table_column_type[table_name] && @table_column_type[table_name][column_name.to_s.downcase]
638
+ end
639
+
640
+ def clear_types_for_columns
641
+ @table_column_type = nil
642
+ end
643
+
644
+ def columns(table_name, name = nil) #:nodoc:
645
+ # RSI: get ignored_columns by original table name
646
+ ignored_columns = ignored_table_columns(table_name)
647
+
648
+ (owner, desc_table_name) = @connection.describe(table_name)
649
+
650
+ table_cols = <<-SQL
651
+ select column_name as name, data_type as sql_type, data_default, nullable,
652
+ decode(data_type, 'NUMBER', data_precision,
653
+ 'FLOAT', data_precision,
654
+ 'VARCHAR2', data_length,
655
+ 'CHAR', data_length,
656
+ null) as limit,
657
+ decode(data_type, 'NUMBER', data_scale, null) as scale
658
+ from all_tab_columns
659
+ where owner = '#{owner}'
660
+ and table_name = '#{desc_table_name}'
661
+ order by column_id
662
+ SQL
663
+
664
+ # RSI: added deletion of ignored columns
665
+ select_all(table_cols, name).delete_if do |row|
666
+ ignored_columns && ignored_columns.include?(row['name'].downcase)
667
+ end.map do |row|
668
+ limit, scale = row['limit'], row['scale']
669
+ if limit || scale
670
+ row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
671
+ end
672
+
673
+ # clean up odd default spacing from Oracle
674
+ if row['data_default']
675
+ row['data_default'].sub!(/^(.*?)\s*$/, '\1')
676
+ row['data_default'].sub!(/^'(.*)'$/, '\1')
677
+ row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
678
+ end
679
+
680
+ OracleEnhancedColumn.new(oracle_downcase(row['name']),
681
+ row['data_default'],
682
+ row['sql_type'],
683
+ row['nullable'] == 'Y',
684
+ # RSI: pass table name for table specific column definitions
685
+ table_name,
686
+ # RSI: pass column type if specified in class definition
687
+ get_type_for_column(table_name, oracle_downcase(row['name'])))
688
+ end
689
+ end
690
+
691
+ # RSI: default sequence start with value
692
+ @@default_sequence_start_value = 10000
693
+ cattr_accessor :default_sequence_start_value
694
+
695
+ def create_table(name, options = {}, &block) #:nodoc:
696
+ create_sequence = options[:id] != false
697
+ column_comments = {}
698
+ super(name, options) do |t|
699
+ # store that primary key was defined in create_table block
700
+ unless create_sequence
701
+ class <<t
702
+ attr_accessor :create_sequence
703
+ def primary_key(*args)
704
+ self.create_sequence = true
705
+ super(*args)
706
+ end
707
+ end
708
+ end
709
+
710
+ # store column comments
711
+ class <<t
712
+ attr_accessor :column_comments
713
+ def column(name, type, options = {})
714
+ if options[:comment]
715
+ self.column_comments ||= {}
716
+ self.column_comments[name] = options[:comment]
717
+ end
718
+ super(name, type, options)
719
+ end
720
+ end
721
+
722
+ result = block.call(t)
723
+ create_sequence = create_sequence || t.create_sequence
724
+ column_comments = t.column_comments if t.column_comments
725
+ end
726
+
727
+ seq_name = options[:sequence_name] || quote_table_name("#{name}_seq")
728
+ seq_start_value = options[:sequence_start_value] || default_sequence_start_value
729
+ execute "CREATE SEQUENCE #{seq_name} START WITH #{seq_start_value}" if create_sequence
730
+
731
+ add_table_comment name, options[:comment]
732
+ column_comments.each do |column_name, comment|
733
+ add_comment name, column_name, comment
734
+ end
735
+
736
+ end
737
+
738
+ def rename_table(name, new_name) #:nodoc:
739
+ execute "RENAME #{quote_table_name(name)} TO #{quote_table_name(new_name)}"
740
+ execute "RENAME #{quote_table_name("#{name}_seq")} TO #{quote_table_name("#{new_name}_seq")}" rescue nil
741
+ end
742
+
743
+ def drop_table(name, options = {}) #:nodoc:
744
+ super(name)
745
+ seq_name = options[:sequence_name] || quote_table_name("#{name}_seq")
746
+ execute "DROP SEQUENCE #{seq_name}" rescue nil
747
+ end
748
+
749
+ # clear cached indexes when adding new index
750
+ def add_index(table_name, column_name, options = {})
751
+ self.all_schema_indexes = nil
752
+ super
753
+ end
754
+
755
+ # clear cached indexes when removing index
756
+ def remove_index(table_name, options = {}) #:nodoc:
757
+ self.all_schema_indexes = nil
758
+ execute "DROP INDEX #{index_name(table_name, options)}"
759
+ end
760
+
761
+ def add_column(table_name, column_name, type, options = {})
762
+ add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
763
+ options[:type] = type
764
+ add_column_options!(add_column_sql, options)
765
+ execute(add_column_sql)
766
+ end
767
+
768
+ def change_column_default(table_name, column_name, default) #:nodoc:
769
+ execute "ALTER TABLE #{quote_table_name(table_name)} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
770
+ end
771
+
772
+ def change_column_null(table_name, column_name, null, default = nil)
773
+ column = column_for(table_name, column_name)
774
+
775
+ unless null || default.nil?
776
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
777
+ end
778
+
779
+ change_column table_name, column_name, column.sql_type, :null => null
780
+ end
781
+
782
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
783
+ column = column_for(table_name, column_name)
784
+
785
+ # remove :null option if its value is the same as current column definition
786
+ # otherwise Oracle will raise error
787
+ if options.has_key?(:null) && options[:null] == column.null
788
+ options[:null] = nil
789
+ end
790
+
791
+ 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])}"
792
+ options[:type] = type
793
+ add_column_options!(change_column_sql, options)
794
+ execute(change_column_sql)
795
+ end
796
+
797
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
798
+ execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
799
+ end
800
+
801
+ def remove_column(table_name, column_name) #:nodoc:
802
+ execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
803
+ end
804
+
805
+ # RSI: table and column comments
806
+ def add_comment(table_name, column_name, comment)
807
+ return if comment.blank?
808
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
809
+ end
810
+
811
+ def add_table_comment(table_name, comment)
812
+ return if comment.blank?
813
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
814
+ end
815
+
816
+ def table_comment(table_name)
817
+ (owner, table_name) = @connection.describe(table_name)
818
+ select_value <<-SQL
819
+ SELECT comments FROM all_tab_comments
820
+ WHERE owner = '#{owner}'
821
+ AND table_name = '#{table_name}'
822
+ SQL
823
+ end
824
+
825
+ def column_comment(table_name, column_name)
826
+ (owner, table_name) = @connection.describe(table_name)
827
+ select_value <<-SQL
828
+ SELECT comments FROM all_col_comments
829
+ WHERE owner = '#{owner}'
830
+ AND table_name = '#{table_name}'
831
+ AND column_name = '#{column_name.upcase}'
832
+ SQL
833
+ end
834
+
835
+ # Find a table's primary key and sequence.
836
+ # *Note*: Only primary key is implemented - sequence will be nil.
837
+ def pk_and_sequence_for(table_name)
838
+ (owner, table_name) = @connection.describe(table_name)
839
+
840
+ # RSI: changed select from all_constraints to user_constraints - much faster in large data dictionaries
841
+ pks = select_values(<<-SQL, 'Primary Key')
842
+ select cc.column_name
843
+ from user_constraints c, user_cons_columns cc
844
+ where c.owner = '#{owner}'
845
+ and c.table_name = '#{table_name}'
846
+ and c.constraint_type = 'P'
847
+ and cc.owner = c.owner
848
+ and cc.constraint_name = c.constraint_name
849
+ SQL
850
+
851
+ # only support single column keys
852
+ pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
853
+ end
854
+
855
+ def structure_dump #:nodoc:
856
+ s = select_all("select sequence_name from user_sequences order by 1").inject("") do |structure, seq|
857
+ structure << "create sequence #{seq.to_a.first.last};\n\n"
858
+ end
859
+
860
+ # RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
861
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |structure, table|
862
+ ddl = "create table #{table.to_a.first.last} (\n "
863
+ cols = select_all(%Q{
864
+ select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
865
+ from user_tab_columns
866
+ where table_name = '#{table.to_a.first.last}'
867
+ order by column_id
868
+ }).map do |row|
869
+ col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
870
+ if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
871
+ col << "(#{row['data_precision'].to_i}"
872
+ col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
873
+ col << ')'
874
+ elsif row['data_type'].include?('CHAR')
875
+ length = row['char_used'] == 'C' ? row['char_length'].to_i : row['data_length'].to_i
876
+ col << "(#{length})"
877
+ end
878
+ col << " default #{row['data_default']}" if !row['data_default'].nil?
879
+ col << ' not null' if row['nullable'] == 'N'
880
+ col
881
+ end
882
+ ddl << cols.join(",\n ")
883
+ ddl << ");\n\n"
884
+ structure << ddl
885
+ end
886
+ end
887
+
888
+ def structure_drop #:nodoc:
889
+ s = select_all("select sequence_name from user_sequences order by 1").inject("") do |drop, seq|
890
+ drop << "drop sequence #{seq.to_a.first.last};\n\n"
891
+ end
892
+
893
+ # RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
894
+ select_all("select table_name from all_tables where owner = sys_context('userenv','session_user') order by 1").inject(s) do |drop, table|
895
+ drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
896
+ end
897
+ end
898
+
899
+ def add_column_options!(sql, options) #:nodoc:
900
+ type = options[:type] || ((column = options[:column]) && column.type)
901
+ type = type && type.to_sym
902
+ # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
903
+ if options_include_default?(options)
904
+ if type == :text
905
+ sql << " DEFAULT #{quote(options[:default])}"
906
+ else
907
+ # from abstract adapter
908
+ sql << " DEFAULT #{quote(options[:default], options[:column])}"
909
+ end
910
+ end
911
+ # must explicitly add NULL or NOT NULL to allow change_column to work on migrations
912
+ if options[:null] == false
913
+ sql << " NOT NULL"
914
+ elsif options[:null] == true
915
+ sql << " NULL" unless type == :primary_key
916
+ end
917
+ end
918
+
919
+ # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
920
+ #
921
+ # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
922
+ # queries. However, with those columns included in the SELECT DISTINCT list, you
923
+ # won't actually get a distinct list of the column you want (presuming the column
924
+ # has duplicates with multiple values for the ordered-by columns. So we use the
925
+ # FIRST_VALUE function to get a single (first) value for each column, effectively
926
+ # making every row the same.
927
+ #
928
+ # distinct("posts.id", "posts.created_at desc")
929
+ def distinct(columns, order_by)
930
+ return "DISTINCT #{columns}" if order_by.blank?
931
+
932
+ # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
933
+ # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
934
+ order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
935
+ order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
936
+ "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
937
+ end
938
+ sql = "DISTINCT #{columns}, "
939
+ sql << order_columns * ", "
940
+ end
941
+
942
+ # ORDER BY clause for the passed order option.
943
+ #
944
+ # Uses column aliases as defined by #distinct.
945
+ def add_order_by_for_association_limiting!(sql, options)
946
+ return sql if options[:order].blank?
947
+
948
+ order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
949
+ order.map! {|s| $1 if s =~ / (.*)/}
950
+ order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
951
+
952
+ sql << " ORDER BY #{order}"
953
+ end
954
+
955
+ private
956
+
957
+ def select(sql, name = nil, return_column_names = false)
958
+ log(sql, name) do
959
+ @connection.select(sql, name, return_column_names)
960
+ end
961
+ end
962
+
963
+ def oracle_downcase(column_name)
964
+ @connection.oracle_downcase(column_name)
965
+ end
966
+
967
+ def column_for(table_name, column_name)
968
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
969
+ raise "No such column: #{table_name}.#{column_name}"
970
+ end
971
+ column
972
+ end
973
+
974
+ end
975
+ end
976
+ end
977
+
978
+ # RSI: Added LOB writing callback for sessions stored in database
979
+ # Otherwise it is not working as Session class is defined before OracleAdapter is loaded in Rails 2.0
980
+ if defined?(CGI::Session::ActiveRecordStore::Session)
981
+ if !CGI::Session::ActiveRecordStore::Session.respond_to?(:after_save_callback_chain) ||
982
+ CGI::Session::ActiveRecordStore::Session.after_save_callback_chain.detect{|cb| cb.method == :enhanced_write_lobs}.nil?
983
+ class CGI::Session::ActiveRecordStore::Session
984
+ after_save :enhanced_write_lobs
985
+ end
986
+ end
987
+ end
988
+
989
+ # RSI: load custom create, update, delete methods functionality
990
+ # rescue LoadError if ruby-plsql gem cannot be loaded
991
+ begin
992
+ require 'active_record/connection_adapters/oracle_enhanced_procedures'
993
+ rescue LoadError
994
+ if defined?(RAILS_DEFAULT_LOGGER)
995
+ RAILS_DEFAULT_LOGGER.info "INFO: ActiveRecord oracle_enhanced adapter could not load ruby-plsql gem. "+
996
+ "Custom create, update and delete methods will not be available."
997
+ end
998
+ end
999
+
1000
+ # RSI: load additional methods for composite_primary_keys support
1001
+ require 'active_record/connection_adapters/oracle_enhanced_cpk'
1002
+
1003
+ # RSI: load patch for dirty tracking methods
1004
+ require 'active_record/connection_adapters/oracle_enhanced_dirty'
1005
+
1006
+ # RSI: load rake tasks definitions
1007
+ begin
1008
+ require 'active_record/connection_adapters/oracle_enhanced_tasks'
1009
+ rescue LoadError
1010
+ end if defined?(RAILS_ROOT)
1011
+
1012
+ # handles quoting of oracle reserved words
1013
+ require 'active_record/connection_adapters/oracle_enhanced_reserved_words'
1014
+
1015
+ # add BigDecimal#to_d, Fixnum#to_d and Bignum#to_d methods if not already present
1016
+ require 'active_record/connection_adapters/oracle_enhanced_core_ext'
1017
+