saberma-saberma-activerecord-oracle_enhanced-adapter-nvarchar2 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 +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), NVARCHAR2(1) columns or NVARCHAR2 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)","NVARCHAR2(1)"].include?(field_type)
285
+ field_type =~ /^NVARCHAR2/ && (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 => "NVARCHAR2", :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 NVARCHAR2
319
+ :boolean => emulate_booleans_from_strings ?
320
+ { :name => "NVARCHAR2", :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
+ 'NVARCHAR2', 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
+