rwc9u-oracle-enhanced 1.1.9.4
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.
- data/History.txt +89 -0
- data/License.txt +20 -0
- data/Manifest.txt +23 -0
- data/README.txt +51 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +44 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1137 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +39 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +110 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb +126 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +11 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +11 -0
- data/oracle-enhanced.gemspec +35 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +535 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +32 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +826 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +85 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +21 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +272 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +42 -0
- metadata +88 -0
data/History.txt
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
== 1.1.9 2009-01-02
|
2
|
+
|
3
|
+
* Enhancements
|
4
|
+
* Added support for table and column comments in migrations
|
5
|
+
* Added support for specifying sequence start values
|
6
|
+
* Added :privilege option (e.g. :SYSDBA) to ActiveRecord::Base.establish_connection
|
7
|
+
* Bug fixes:
|
8
|
+
* Do not mark empty decimals, strings and texts (stored as NULL in database) as changed when reassigning them (starting from Rails 2.1)
|
9
|
+
* Create booleans as VARCHAR2(1) columns if emulate_booleans_from_strings is true
|
10
|
+
|
11
|
+
== 1.1.8 2008-10-10
|
12
|
+
|
13
|
+
* Bug fixes:
|
14
|
+
* Fixed storing of serialized LOB columns
|
15
|
+
* Prevent from SQL injection in :limit and :offset
|
16
|
+
* Order by LOB columns (by replacing column with function which returns first 100 characters of LOB)
|
17
|
+
* Sequence creation for tables with non-default primary key in create_table block
|
18
|
+
* Do count distinct workaround only when composite_primary_keys gem is used
|
19
|
+
(otherwise count distinct did not work with ActiveRecord 2.1.1)
|
20
|
+
* Fixed rake db:test:clone_structure task
|
21
|
+
(see http://rsim.lighthouseapp.com/projects/11468/tickets/11-rake-dbtestclone_structure-fails-in-117)
|
22
|
+
* Fixed bug when ActiveRecord::Base.allow_concurrency = true
|
23
|
+
(see http://dev.rubyonrails.org/ticket/11134)
|
24
|
+
|
25
|
+
== 1.1.7 2008-08-20
|
26
|
+
|
27
|
+
* Bug fixes:
|
28
|
+
* Fixed that adapter works without ruby-plsql gem (in this case just custom create/update/delete methods are not available)
|
29
|
+
|
30
|
+
== 1.1.6 2008-08-19
|
31
|
+
|
32
|
+
* Enhancements:
|
33
|
+
* Added support for set_date_columns and set_datetime_columns
|
34
|
+
* Added support for set_boolean_columns
|
35
|
+
* Added support for schema prefix in set_table_name (removed table name quoting)
|
36
|
+
* Added support for NVARCHAR2 column type
|
37
|
+
* Bug fixes:
|
38
|
+
* Do not call write_lobs callback when custom create or update methods are defined
|
39
|
+
|
40
|
+
== 1.1.5 2008-07-27
|
41
|
+
|
42
|
+
* Bug fixes:
|
43
|
+
* Fixed that write_lobs callback works with partial_updates enabled (added additional record lock before writing BLOB data to database)
|
44
|
+
* Enhancements:
|
45
|
+
* Changed SQL SELECT in indexes method so that it will execute faster on some large data dictionaries
|
46
|
+
* Support for other date and time formats when assigning string to :date or :datetime column
|
47
|
+
|
48
|
+
== 1.1.4 2008-07-14
|
49
|
+
|
50
|
+
* Enhancements:
|
51
|
+
* Date/Time quoting changes to support composite_primary_keys
|
52
|
+
* Added additional methods that are used by composite_primary_keys
|
53
|
+
|
54
|
+
== 1.1.3 2008-07-10
|
55
|
+
|
56
|
+
* Enhancements:
|
57
|
+
* Added support for custom create, update and delete methods when working with legacy databases where
|
58
|
+
PL/SQL API should be used for create, update and delete operations
|
59
|
+
|
60
|
+
== 1.1.2 2008-07-08
|
61
|
+
|
62
|
+
* Bug fixes:
|
63
|
+
* Fixed after_save callback addition for session store in ActiveRecord version 2.0.2
|
64
|
+
* Changed date column name recognition - now should match regex /(^|_)date(_|$)/i
|
65
|
+
(previously "updated_at" was recognized as :date column and not as :datetime)
|
66
|
+
|
67
|
+
== 1.1.1 2008-06-28
|
68
|
+
|
69
|
+
* Enhancements:
|
70
|
+
* Added ignore_table_columns option
|
71
|
+
* Added support for TIMESTAMP columns (without fractional seconds)
|
72
|
+
* NLS_DATE_FORMAT and NLS_TIMESTAMP_FORMAT independent DATE and TIMESTAMP columns support
|
73
|
+
* Bug fixes:
|
74
|
+
* Checks if CGI::Session::ActiveRecordStore::Session does not have enhanced_write_lobs callback before adding it
|
75
|
+
(Rails 2.0 does not add this callback, Rails 2.1 does)
|
76
|
+
|
77
|
+
== 1.1.0 2008-05-05
|
78
|
+
|
79
|
+
* Forked from original activerecord-oracle-adapter-1.0.0.9216
|
80
|
+
* Renamed oracle adapter to oracle_enhanced adapter
|
81
|
+
* Added "enhanced" to method and class definitions so that oracle_enhanced and original oracle adapter
|
82
|
+
could be used simultaniously
|
83
|
+
* Added Rails rake tasks as a copy from original oracle tasks
|
84
|
+
* Enhancements:
|
85
|
+
* Improved perfomance of schema dump methods when used on large data dictionaries
|
86
|
+
* Added LOB writing callback for sessions stored in database
|
87
|
+
* Added emulate_dates_by_column_name option
|
88
|
+
* Added emulate_integers_by_column_name option
|
89
|
+
* 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,23 @@
|
|
1
|
+
#oracle-enhanced.gemspec#
|
2
|
+
History.txt
|
3
|
+
License.txt
|
4
|
+
Manifest.txt
|
5
|
+
README.txt
|
6
|
+
lib/active_record/connection_adapters/emulation/oracle_adapter.rb
|
7
|
+
lib/active_record/connection_adapters/oracle_enhanced.rake
|
8
|
+
lib/active_record/connection_adapters/oracle_enhanced_adapter.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_procedures.rb
|
12
|
+
lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb
|
13
|
+
lib/active_record/connection_adapters/oracle_enhanced_tasks.rb
|
14
|
+
lib/active_record/connection_adapters/oracle_enhanced_version.rb
|
15
|
+
oracle-enhanced.gemspec
|
16
|
+
spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb
|
17
|
+
spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb
|
18
|
+
spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb
|
19
|
+
spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb
|
20
|
+
spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb
|
21
|
+
spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb
|
22
|
+
spec/spec.opts
|
23
|
+
spec/spec_helper.rb
|
data/README.txt
ADDED
@@ -0,0 +1,51 @@
|
|
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 and 2.2 (these are the same as Rails versions)
|
21
|
+
* Requires ruby-oci8 library to connect to Oracle
|
22
|
+
* 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)
|
23
|
+
|
24
|
+
== INSTALL:
|
25
|
+
|
26
|
+
* sudo gem install activerecord-oracle_enhanced-adapter
|
27
|
+
|
28
|
+
== LICENSE:
|
29
|
+
|
30
|
+
(The MIT License)
|
31
|
+
|
32
|
+
Copyright (c) 2009 Graham Jenkins, Michael Schoen, Raimonds Simanovskis
|
33
|
+
|
34
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
35
|
+
a copy of this software and associated documentation files (the
|
36
|
+
'Software'), to deal in the Software without restriction, including
|
37
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
38
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
39
|
+
permit persons to whom the Software is furnished to do so, subject to
|
40
|
+
the following conditions:
|
41
|
+
|
42
|
+
The above copyright notice and this permission notice shall be
|
43
|
+
included in all copies or substantial portions of the Software.
|
44
|
+
|
45
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
46
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
47
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
48
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
49
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
50
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
51
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -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,1137 @@
|
|
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
|
+
require 'delegate'
|
33
|
+
|
34
|
+
begin
|
35
|
+
require 'active_record/connection_adapters/oracle_enhanced_tasks'
|
36
|
+
rescue LoadError
|
37
|
+
end if defined?(RAILS_ROOT)
|
38
|
+
|
39
|
+
begin
|
40
|
+
require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8
|
41
|
+
|
42
|
+
# RSI: added mapping for TIMESTAMP / WITH TIME ZONE / LOCAL TIME ZONE types
|
43
|
+
# currently Ruby-OCI8 does not support fractional seconds for timestamps
|
44
|
+
OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP] = OCI8::BindType::OraDate
|
45
|
+
OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_TZ] = OCI8::BindType::OraDate
|
46
|
+
OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_LTZ] = OCI8::BindType::OraDate
|
47
|
+
|
48
|
+
module ActiveRecord
|
49
|
+
class Base
|
50
|
+
def self.oracle_enhanced_connection(config) #:nodoc:
|
51
|
+
# Use OCI8AutoRecover instead of normal OCI8 driver.
|
52
|
+
if config[:emulate_oracle_adapter] == true
|
53
|
+
# allows the enhanced adapter to look like the OracleAdapter. Useful to pick up
|
54
|
+
# conditionals in the rails activerecord test suite
|
55
|
+
require 'active_record/connection_adapters/emulation/oracle_adapter'
|
56
|
+
ConnectionAdapters::OracleAdapter.new OCI8EnhancedAutoRecover.new(config), logger
|
57
|
+
else
|
58
|
+
ConnectionAdapters::OracleEnhancedAdapter.new OCI8EnhancedAutoRecover.new(config), logger
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# RSI: specify table columns which should be ifnored
|
63
|
+
def self.ignore_table_columns(*args)
|
64
|
+
connection.ignore_table_columns(table_name,*args)
|
65
|
+
end
|
66
|
+
|
67
|
+
# RSI: specify which table columns should be treated as date (without time)
|
68
|
+
def self.set_date_columns(*args)
|
69
|
+
connection.set_type_for_columns(table_name,:date,*args)
|
70
|
+
end
|
71
|
+
|
72
|
+
# RSI: specify which table columns should be treated as datetime
|
73
|
+
def self.set_datetime_columns(*args)
|
74
|
+
connection.set_type_for_columns(table_name,:datetime,*args)
|
75
|
+
end
|
76
|
+
|
77
|
+
# RSI: specify which table columns should be treated as booleans
|
78
|
+
def self.set_boolean_columns(*args)
|
79
|
+
connection.set_type_for_columns(table_name,:boolean,*args)
|
80
|
+
end
|
81
|
+
|
82
|
+
# After setting large objects to empty, select the OCI8::LOB
|
83
|
+
# and write back the data.
|
84
|
+
after_save :enhanced_write_lobs
|
85
|
+
def enhanced_write_lobs #:nodoc:
|
86
|
+
if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter) &&
|
87
|
+
!(self.class.custom_create_method || self.class.custom_update_method)
|
88
|
+
connection.write_lobs(self.class.table_name, self.class, attributes)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
private :enhanced_write_lobs
|
92
|
+
|
93
|
+
class << self
|
94
|
+
# RSI: patch ORDER BY to work with LOBs
|
95
|
+
def add_order_with_lobs!(sql, order, scope = :auto)
|
96
|
+
if connection.is_a?(ConnectionAdapters::OracleEnhancedAdapter)
|
97
|
+
order = connection.lob_order_by_expression(self, order) if order
|
98
|
+
|
99
|
+
orig_scope = scope
|
100
|
+
scope = scope(:find) if :auto == scope
|
101
|
+
if scope
|
102
|
+
new_scope_order = connection.lob_order_by_expression(self, scope[:order])
|
103
|
+
if new_scope_order != scope[:order]
|
104
|
+
scope = scope.merge(:order => new_scope_order)
|
105
|
+
else
|
106
|
+
scope = orig_scope
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
add_order_without_lobs!(sql, order, scope = :auto)
|
111
|
+
end
|
112
|
+
private :add_order_with_lobs!
|
113
|
+
alias_method :add_order_without_lobs!, :add_order!
|
114
|
+
alias_method :add_order!, :add_order_with_lobs!
|
115
|
+
end
|
116
|
+
|
117
|
+
# RSI: get table comment from schema definition
|
118
|
+
def self.table_comment
|
119
|
+
self.connection.table_comment(self.table_name)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
module ConnectionAdapters #:nodoc:
|
125
|
+
class OracleEnhancedColumn < Column #:nodoc:
|
126
|
+
|
127
|
+
attr_reader :table_name, :forced_column_type
|
128
|
+
|
129
|
+
def initialize(name, default, sql_type = nil, null = true, table_name = nil, forced_column_type = nil)
|
130
|
+
@table_name = table_name
|
131
|
+
@forced_column_type = forced_column_type
|
132
|
+
super(name, default, sql_type, null)
|
133
|
+
end
|
134
|
+
|
135
|
+
def type_cast(value)
|
136
|
+
return guess_date_or_time(value) if type == :datetime && OracleEnhancedAdapter.emulate_dates
|
137
|
+
super
|
138
|
+
end
|
139
|
+
|
140
|
+
# convert something to a boolean
|
141
|
+
# RSI: added y as boolean value
|
142
|
+
def self.value_to_boolean(value)
|
143
|
+
if value == true || value == false
|
144
|
+
value
|
145
|
+
else
|
146
|
+
%w(true t 1 y +).include?(value.to_s.downcase)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# RSI: convert Time value to Date for :date columns
|
151
|
+
def self.string_to_date(string)
|
152
|
+
return string.to_date if string.is_a?(Time)
|
153
|
+
super
|
154
|
+
end
|
155
|
+
|
156
|
+
# RSI: convert Date value to Time for :datetime columns
|
157
|
+
def self.string_to_time(string)
|
158
|
+
return string.to_time if string.is_a?(Date) && !OracleEnhancedAdapter.emulate_dates
|
159
|
+
super
|
160
|
+
end
|
161
|
+
|
162
|
+
# RSI: get column comment from schema definition
|
163
|
+
# will work only if using default ActiveRecord connection
|
164
|
+
def comment
|
165
|
+
ActiveRecord::Base.connection.column_comment(@table_name, name)
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
def simplified_type(field_type)
|
170
|
+
return :boolean if OracleEnhancedAdapter.emulate_booleans && field_type == 'NUMBER(1)'
|
171
|
+
return :boolean if OracleEnhancedAdapter.emulate_booleans_from_strings &&
|
172
|
+
(forced_column_type == :boolean ||
|
173
|
+
OracleEnhancedAdapter.is_boolean_column?(name, field_type, table_name))
|
174
|
+
|
175
|
+
case field_type
|
176
|
+
when /date/i
|
177
|
+
forced_column_type ||
|
178
|
+
(:date if OracleEnhancedAdapter.emulate_dates_by_column_name && OracleEnhancedAdapter.is_date_column?(name, table_name)) ||
|
179
|
+
:datetime
|
180
|
+
when /timestamp/i then :timestamp
|
181
|
+
when /time/i then :datetime
|
182
|
+
when /decimal|numeric|number/i
|
183
|
+
return :integer if extract_scale(field_type) == 0
|
184
|
+
# RSI: if column name is ID or ends with _ID
|
185
|
+
return :integer if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(name, table_name)
|
186
|
+
:decimal
|
187
|
+
else super
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def guess_date_or_time(value)
|
192
|
+
value.respond_to?(:hour) && (value.hour == 0 and value.min == 0 and value.sec == 0) ?
|
193
|
+
Date.new(value.year, value.month, value.day) : value
|
194
|
+
end
|
195
|
+
|
196
|
+
class << self
|
197
|
+
protected
|
198
|
+
|
199
|
+
def fallback_string_to_date(string)
|
200
|
+
if OracleEnhancedAdapter.string_to_date_format || OracleEnhancedAdapter.string_to_time_format
|
201
|
+
return (string_to_date_or_time_using_format(string).to_date rescue super)
|
202
|
+
end
|
203
|
+
super
|
204
|
+
end
|
205
|
+
|
206
|
+
def fallback_string_to_time(string)
|
207
|
+
if OracleEnhancedAdapter.string_to_time_format || OracleEnhancedAdapter.string_to_date_format
|
208
|
+
return (string_to_date_or_time_using_format(string).to_time rescue super)
|
209
|
+
end
|
210
|
+
super
|
211
|
+
end
|
212
|
+
|
213
|
+
def string_to_date_or_time_using_format(string)
|
214
|
+
if OracleEnhancedAdapter.string_to_time_format && dt=Date._strptime(string, OracleEnhancedAdapter.string_to_time_format)
|
215
|
+
return Time.mktime(*dt.values_at(:year, :mon, :mday, :hour, :min, :sec, :zone, :wday))
|
216
|
+
end
|
217
|
+
DateTime.strptime(string, OracleEnhancedAdapter.string_to_date_format)
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
# This is an Oracle/OCI adapter for the ActiveRecord persistence
|
225
|
+
# framework. It relies upon the OCI8 driver, which works with Oracle 8i
|
226
|
+
# and above. Most recent development has been on Debian Linux against
|
227
|
+
# a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13.
|
228
|
+
# See: http://rubyforge.org/projects/ruby-oci8/
|
229
|
+
#
|
230
|
+
# Usage notes:
|
231
|
+
# * Key generation assumes a "${table_name}_seq" sequence is available
|
232
|
+
# for all tables; the sequence name can be changed using
|
233
|
+
# ActiveRecord::Base.set_sequence_name. When using Migrations, these
|
234
|
+
# sequences are created automatically.
|
235
|
+
# * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
|
236
|
+
# Consequently some hacks are employed to map data back to Date or Time
|
237
|
+
# in Ruby. If the column_name ends in _time it's created as a Ruby Time.
|
238
|
+
# Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else
|
239
|
+
# it's a Ruby Time. This is a bit nasty - but if you use Duck Typing
|
240
|
+
# you'll probably not care very much. In 9i and up it's tempting to
|
241
|
+
# map DATE to Date and TIMESTAMP to Time, but too many databases use
|
242
|
+
# DATE for both. Timezones and sub-second precision on timestamps are
|
243
|
+
# not supported.
|
244
|
+
# * Default values that are functions (such as "SYSDATE") are not
|
245
|
+
# supported. This is a restriction of the way ActiveRecord supports
|
246
|
+
# default values.
|
247
|
+
# * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which
|
248
|
+
# is supported in Oracle9i and later. You will need to use #finder_sql for
|
249
|
+
# has_and_belongs_to_many associations to run against Oracle8.
|
250
|
+
#
|
251
|
+
# Required parameters:
|
252
|
+
#
|
253
|
+
# * <tt>:username</tt>
|
254
|
+
# * <tt>:password</tt>
|
255
|
+
# * <tt>:database</tt>
|
256
|
+
class OracleEnhancedAdapter < AbstractAdapter
|
257
|
+
|
258
|
+
@@emulate_booleans = true
|
259
|
+
cattr_accessor :emulate_booleans
|
260
|
+
|
261
|
+
@@emulate_dates = false
|
262
|
+
cattr_accessor :emulate_dates
|
263
|
+
|
264
|
+
# RSI: set to true if columns with DATE in their name should be emulated as date
|
265
|
+
@@emulate_dates_by_column_name = false
|
266
|
+
cattr_accessor :emulate_dates_by_column_name
|
267
|
+
def self.is_date_column?(name, table_name = nil)
|
268
|
+
name =~ /(^|_)date(_|$)/i
|
269
|
+
end
|
270
|
+
# RSI: instance method uses at first check if column type defined at class level
|
271
|
+
def is_date_column?(name, table_name = nil)
|
272
|
+
case get_type_for_column(table_name, name)
|
273
|
+
when nil
|
274
|
+
self.class.is_date_column?(name, table_name)
|
275
|
+
when :date
|
276
|
+
true
|
277
|
+
else
|
278
|
+
false
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# RSI: set to true if NUMBER columns with ID at the end of their name should be emulated as integers
|
283
|
+
@@emulate_integers_by_column_name = false
|
284
|
+
cattr_accessor :emulate_integers_by_column_name
|
285
|
+
def self.is_integer_column?(name, table_name = nil)
|
286
|
+
name =~ /(^|_)id$/i
|
287
|
+
end
|
288
|
+
|
289
|
+
# RSI: set to true if CHAR(1), VARCHAR2(1) columns or VARCHAR2 columns with FLAG or YN at the end of their name
|
290
|
+
# should be emulated as booleans
|
291
|
+
@@emulate_booleans_from_strings = false
|
292
|
+
cattr_accessor :emulate_booleans_from_strings
|
293
|
+
def self.is_boolean_column?(name, field_type, table_name = nil)
|
294
|
+
return true if ["CHAR(1)","VARCHAR2(1)"].include?(field_type)
|
295
|
+
field_type =~ /^VARCHAR2/ && (name =~ /_flag$/i || name =~ /_yn$/i)
|
296
|
+
end
|
297
|
+
def self.boolean_to_string(bool)
|
298
|
+
bool ? "Y" : "N"
|
299
|
+
end
|
300
|
+
|
301
|
+
# RSI: use to set NLS specific date formats which will be used when assigning string to :date and :datetime columns
|
302
|
+
@@string_to_date_format = @@string_to_time_format = nil
|
303
|
+
cattr_accessor :string_to_date_format, :string_to_time_format
|
304
|
+
|
305
|
+
def adapter_name #:nodoc:
|
306
|
+
'OracleEnhanced'
|
307
|
+
end
|
308
|
+
|
309
|
+
def supports_migrations? #:nodoc:
|
310
|
+
true
|
311
|
+
end
|
312
|
+
|
313
|
+
def native_database_types #:nodoc:
|
314
|
+
{
|
315
|
+
:primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
|
316
|
+
:string => { :name => "VARCHAR2", :limit => 255 },
|
317
|
+
:text => { :name => "CLOB" },
|
318
|
+
:integer => { :name => "NUMBER", :limit => 38 },
|
319
|
+
:float => { :name => "NUMBER" },
|
320
|
+
:decimal => { :name => "DECIMAL" },
|
321
|
+
:datetime => { :name => "DATE" },
|
322
|
+
# RSI: changed to native TIMESTAMP type
|
323
|
+
# :timestamp => { :name => "DATE" },
|
324
|
+
:timestamp => { :name => "TIMESTAMP" },
|
325
|
+
:time => { :name => "DATE" },
|
326
|
+
:date => { :name => "DATE" },
|
327
|
+
:binary => { :name => "BLOB" },
|
328
|
+
# RSI: if emulate_booleans_from_strings then store booleans in VARCHAR2
|
329
|
+
:boolean => emulate_booleans_from_strings ?
|
330
|
+
{ :name => "VARCHAR2", :limit => 1 } : { :name => "NUMBER", :limit => 1 }
|
331
|
+
}
|
332
|
+
end
|
333
|
+
|
334
|
+
def table_alias_length
|
335
|
+
30
|
336
|
+
end
|
337
|
+
|
338
|
+
# Returns an array of arrays containing the field values.
|
339
|
+
# Order is the same as that returned by #columns.
|
340
|
+
def select_rows(sql, name = nil)
|
341
|
+
result = select(sql, name)
|
342
|
+
result.map{ |v| v.values}
|
343
|
+
end
|
344
|
+
|
345
|
+
# QUOTING ==================================================
|
346
|
+
#
|
347
|
+
# see: abstract/quoting.rb
|
348
|
+
|
349
|
+
# camelCase column names need to be quoted; not that anyone using Oracle
|
350
|
+
# would really do this, but handling this case means we pass the test...
|
351
|
+
def quote_column_name(name) #:nodoc:
|
352
|
+
name.to_s =~ /[A-Z]/ ? "\"#{name}\"" : quote_oracle_reserved_words(name)
|
353
|
+
end
|
354
|
+
|
355
|
+
# abstract_adapter calls quote_column_name from quote_table_name, so prevent that
|
356
|
+
# but still quote names that have non alphanumeric values
|
357
|
+
def quote_table_name(name)
|
358
|
+
if name.to_s =~ /^[A-Z_0-9\.]+$/i
|
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
|
+
# Returns true if the connection is active.
|
418
|
+
def active?
|
419
|
+
# Pings the connection to check if it's still good. Note that an
|
420
|
+
# #active? method is also available, but that simply returns the
|
421
|
+
# last known state, which isn't good enough if the connection has
|
422
|
+
# gone stale since the last use.
|
423
|
+
@connection.ping
|
424
|
+
rescue OCIException
|
425
|
+
false
|
426
|
+
end
|
427
|
+
|
428
|
+
# Reconnects to the database.
|
429
|
+
def reconnect!
|
430
|
+
@connection.reset!
|
431
|
+
rescue OCIException => e
|
432
|
+
@logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
|
433
|
+
end
|
434
|
+
|
435
|
+
# Disconnects from the database.
|
436
|
+
def disconnect!
|
437
|
+
@connection.logoff rescue nil
|
438
|
+
@connection.active = false
|
439
|
+
end
|
440
|
+
|
441
|
+
|
442
|
+
# DATABASE STATEMENTS ======================================
|
443
|
+
#
|
444
|
+
# see: abstract/database_statements.rb
|
445
|
+
|
446
|
+
def execute(sql, name = nil) #:nodoc:
|
447
|
+
log(sql, name) { @connection.exec sql }
|
448
|
+
end
|
449
|
+
|
450
|
+
# Returns the next sequence value from a sequence generator. Not generally
|
451
|
+
# called directly; used by ActiveRecord to get the next primary key value
|
452
|
+
# when inserting a new database record (see #prefetch_primary_key?).
|
453
|
+
def next_sequence_value(sequence_name)
|
454
|
+
id = 0
|
455
|
+
@connection.exec("select #{sequence_name}.nextval id from dual") { |r| id = r[0].to_i }
|
456
|
+
id
|
457
|
+
end
|
458
|
+
|
459
|
+
def begin_db_transaction #:nodoc:
|
460
|
+
@connection.autocommit = false
|
461
|
+
end
|
462
|
+
|
463
|
+
def commit_db_transaction #:nodoc:
|
464
|
+
@connection.commit
|
465
|
+
ensure
|
466
|
+
@connection.autocommit = true
|
467
|
+
end
|
468
|
+
|
469
|
+
def rollback_db_transaction #:nodoc:
|
470
|
+
@connection.rollback
|
471
|
+
ensure
|
472
|
+
@connection.autocommit = true
|
473
|
+
end
|
474
|
+
|
475
|
+
def add_limit_offset!(sql, options) #:nodoc:
|
476
|
+
# RSI: added to_i for limit and offset to protect from SQL injection
|
477
|
+
offset = (options[:offset] || 0).to_i
|
478
|
+
|
479
|
+
if limit = options[:limit]
|
480
|
+
limit = limit.to_i
|
481
|
+
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
|
482
|
+
elsif offset > 0
|
483
|
+
sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
# Returns true for Oracle adapter (since Oracle requires primary key
|
488
|
+
# values to be pre-fetched before insert). See also #next_sequence_value.
|
489
|
+
def prefetch_primary_key?(table_name = nil)
|
490
|
+
true
|
491
|
+
end
|
492
|
+
|
493
|
+
def default_sequence_name(table, column) #:nodoc:
|
494
|
+
quote_table_name("#{table}_seq")
|
495
|
+
end
|
496
|
+
|
497
|
+
|
498
|
+
# Inserts the given fixture into the table. Overridden to properly handle lobs.
|
499
|
+
def insert_fixture(fixture, table_name)
|
500
|
+
super
|
501
|
+
|
502
|
+
klass = fixture.class_name.constantize rescue nil
|
503
|
+
if klass.respond_to?(:ancestors) && klass.ancestors.include?(ActiveRecord::Base)
|
504
|
+
write_lobs(table_name, klass, fixture)
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
# Writes LOB values from attributes, as indicated by the LOB columns of klass.
|
509
|
+
def write_lobs(table_name, klass, attributes)
|
510
|
+
id = quote(attributes[klass.primary_key])
|
511
|
+
klass.columns.select { |col| col.sql_type =~ /LOB$/i }.each do |col|
|
512
|
+
value = attributes[col.name]
|
513
|
+
# RSI: changed sequence of next two lines - should check if value is nil before converting to yaml
|
514
|
+
next if value.nil? || (value == '')
|
515
|
+
value = value.to_yaml if col.text? && klass.serialized_attributes[col.name]
|
516
|
+
uncached do
|
517
|
+
lob = select_one("SELECT #{col.name} FROM #{table_name} WHERE #{klass.primary_key} = #{id} FOR UPDATE",
|
518
|
+
'Writable Large Object')[col.name]
|
519
|
+
lob.write value
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
# RSI: change LOB column for ORDER BY clause
|
525
|
+
# just first 100 characters are taken for ordering
|
526
|
+
def lob_order_by_expression(klass, order)
|
527
|
+
return order if order.nil?
|
528
|
+
changed = false
|
529
|
+
new_order = order.to_s.strip.split(/, */).map do |order_by_col|
|
530
|
+
column_name, asc_desc = order_by_col.split(/ +/)
|
531
|
+
if column = klass.columns.detect { |col| col.name == column_name && col.sql_type =~ /LOB$/i}
|
532
|
+
changed = true
|
533
|
+
"DBMS_LOB.SUBSTR(#{column_name},100,1) #{asc_desc}"
|
534
|
+
else
|
535
|
+
order_by_col
|
536
|
+
end
|
537
|
+
end.join(', ')
|
538
|
+
changed ? new_order : order
|
539
|
+
end
|
540
|
+
|
541
|
+
# SCHEMA STATEMENTS ========================================
|
542
|
+
#
|
543
|
+
# see: abstract/schema_statements.rb
|
544
|
+
|
545
|
+
def current_database #:nodoc:
|
546
|
+
select_one("select sys_context('userenv','db_name') db from dual")["db"]
|
547
|
+
end
|
548
|
+
|
549
|
+
# RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
|
550
|
+
def tables(name = nil) #:nodoc:
|
551
|
+
select_all("select lower(table_name) from all_tables where owner = sys_context('userenv','session_user')").inject([]) do | tabs, t |
|
552
|
+
tabs << t.to_a.first.last
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
def indexes(table_name, name = nil) #:nodoc:
|
557
|
+
result = select_all(<<-SQL, name)
|
558
|
+
SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
|
559
|
+
FROM all_indexes i, user_ind_columns c
|
560
|
+
WHERE i.table_name = '#{table_name.to_s.upcase}'
|
561
|
+
AND c.index_name = i.index_name
|
562
|
+
AND i.index_name NOT IN (SELECT uc.index_name FROM user_constraints uc WHERE uc.constraint_type = 'P')
|
563
|
+
AND i.owner = sys_context('userenv','session_user')
|
564
|
+
ORDER BY i.index_name, c.column_position
|
565
|
+
SQL
|
566
|
+
|
567
|
+
current_index = nil
|
568
|
+
indexes = []
|
569
|
+
|
570
|
+
result.each do |row|
|
571
|
+
if current_index != row['index_name']
|
572
|
+
indexes << IndexDefinition.new(table_name, row['index_name'], row['uniqueness'] == "UNIQUE", [])
|
573
|
+
current_index = row['index_name']
|
574
|
+
end
|
575
|
+
|
576
|
+
indexes.last.columns << row['column_name']
|
577
|
+
end
|
578
|
+
|
579
|
+
indexes
|
580
|
+
end
|
581
|
+
|
582
|
+
# RSI: set ignored columns for table
|
583
|
+
def ignore_table_columns(table_name, *args)
|
584
|
+
@ignore_table_columns ||= {}
|
585
|
+
@ignore_table_columns[table_name] ||= []
|
586
|
+
@ignore_table_columns[table_name] += args.map{|a| a.to_s.downcase}
|
587
|
+
@ignore_table_columns[table_name].uniq!
|
588
|
+
end
|
589
|
+
|
590
|
+
def ignored_table_columns(table_name)
|
591
|
+
@ignore_table_columns ||= {}
|
592
|
+
@ignore_table_columns[table_name]
|
593
|
+
end
|
594
|
+
|
595
|
+
# RSI: set explicit type for specified table columns
|
596
|
+
def set_type_for_columns(table_name, column_type, *args)
|
597
|
+
@table_column_type ||= {}
|
598
|
+
@table_column_type[table_name] ||= {}
|
599
|
+
args.each do |col|
|
600
|
+
@table_column_type[table_name][col.to_s.downcase] = column_type
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
def get_type_for_column(table_name, column_name)
|
605
|
+
result = @table_column_type && @table_column_type[table_name] && @table_column_type[table_name][column_name.to_s.downcase]
|
606
|
+
result
|
607
|
+
end
|
608
|
+
|
609
|
+
def clear_types_for_columns
|
610
|
+
@table_column_type = nil
|
611
|
+
end
|
612
|
+
|
613
|
+
def columns(table_name, name = nil) #:nodoc:
|
614
|
+
# RSI: get ignored_columns by original table name
|
615
|
+
ignored_columns = ignored_table_columns(table_name)
|
616
|
+
|
617
|
+
(owner, desc_table_name) = @connection.describe(quote_table_name(table_name))
|
618
|
+
|
619
|
+
table_cols = <<-SQL
|
620
|
+
select column_name as name, data_type as sql_type, data_default, nullable,
|
621
|
+
decode(data_type, 'NUMBER', data_precision,
|
622
|
+
'FLOAT', data_precision,
|
623
|
+
'VARCHAR2', data_length,
|
624
|
+
'CHAR', data_length,
|
625
|
+
null) as limit,
|
626
|
+
decode(data_type, 'NUMBER', data_scale, null) as scale
|
627
|
+
from all_tab_columns
|
628
|
+
where owner = '#{owner}'
|
629
|
+
and table_name = '#{desc_table_name}'
|
630
|
+
order by column_id
|
631
|
+
SQL
|
632
|
+
|
633
|
+
# RSI: added deletion of ignored columns
|
634
|
+
select_all(table_cols, name).delete_if do |row|
|
635
|
+
ignored_columns && ignored_columns.include?(row['name'].downcase)
|
636
|
+
end.map do |row|
|
637
|
+
limit, scale = row['limit'], row['scale']
|
638
|
+
if limit || scale
|
639
|
+
row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
|
640
|
+
end
|
641
|
+
|
642
|
+
# clean up odd default spacing from Oracle
|
643
|
+
if row['data_default']
|
644
|
+
row['data_default'].sub!(/^(.*?)\s*$/, '\1')
|
645
|
+
row['data_default'].sub!(/^'(.*)'$/, '\1')
|
646
|
+
row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
|
647
|
+
end
|
648
|
+
|
649
|
+
OracleEnhancedColumn.new(oracle_downcase(row['name']),
|
650
|
+
row['data_default'],
|
651
|
+
row['sql_type'],
|
652
|
+
row['nullable'] == 'Y',
|
653
|
+
# RSI: pass table name for table specific column definitions
|
654
|
+
table_name,
|
655
|
+
# RSI: pass column type if specified in class definition
|
656
|
+
get_type_for_column(table_name, oracle_downcase(row['name'])))
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
# RSI: default sequence start with value
|
661
|
+
@@default_sequence_start_value = 10000
|
662
|
+
cattr_accessor :default_sequence_start_value
|
663
|
+
|
664
|
+
def create_table(name, options = {}, &block) #:nodoc:
|
665
|
+
create_sequence = options[:id] != false
|
666
|
+
column_comments = {}
|
667
|
+
super(name, options) do |t|
|
668
|
+
# store that primary key was defined in create_table block
|
669
|
+
unless create_sequence
|
670
|
+
class << t
|
671
|
+
attr_accessor :create_sequence
|
672
|
+
def primary_key(*args)
|
673
|
+
self.create_sequence = true
|
674
|
+
super(*args)
|
675
|
+
end
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
# store column comments
|
680
|
+
class << t
|
681
|
+
attr_accessor :column_comments
|
682
|
+
def column(name, type, options = {})
|
683
|
+
if options[:comment]
|
684
|
+
self.column_comments ||= {}
|
685
|
+
self.column_comments[name] = options[:comment]
|
686
|
+
end
|
687
|
+
super(name, type, options)
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
result = block.call(t)
|
692
|
+
create_sequence = create_sequence || t.create_sequence
|
693
|
+
column_comments = t.column_comments if t.column_comments
|
694
|
+
end
|
695
|
+
|
696
|
+
seq_name = options[:sequence_name] || quote_table_name("#{name}_seq")
|
697
|
+
seq_start_value = options[:sequence_start_value] || default_sequence_start_value
|
698
|
+
execute "CREATE SEQUENCE #{seq_name} START WITH #{seq_start_value}" if create_sequence
|
699
|
+
|
700
|
+
add_table_comment name, options[:comment]
|
701
|
+
column_comments.each do |column_name, comment|
|
702
|
+
add_comment name, column_name, comment
|
703
|
+
end
|
704
|
+
|
705
|
+
end
|
706
|
+
|
707
|
+
def rename_table(name, new_name) #:nodoc:
|
708
|
+
execute "RENAME #{name} TO #{new_name}"
|
709
|
+
execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
|
710
|
+
end
|
711
|
+
|
712
|
+
def drop_table(name, options = {}) #:nodoc:
|
713
|
+
super(name)
|
714
|
+
seq_name = options[:sequence_name] || quote_table_name("#{name}_seq")
|
715
|
+
execute "DROP SEQUENCE #{seq_name}" rescue nil
|
716
|
+
end
|
717
|
+
|
718
|
+
def remove_index(table_name, options = {}) #:nodoc:
|
719
|
+
execute "DROP INDEX #{index_name(table_name, options)}"
|
720
|
+
end
|
721
|
+
|
722
|
+
def change_column_default(table_name, column_name, default) #:nodoc:
|
723
|
+
execute "ALTER TABLE #{table_name} MODIFY #{quote_column_name(column_name)} DEFAULT #{quote(default)}"
|
724
|
+
end
|
725
|
+
|
726
|
+
def change_column(table_name, column_name, type, options = {}) #:nodoc:
|
727
|
+
change_column_sql = "ALTER TABLE #{table_name} MODIFY #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
|
728
|
+
add_column_options!(change_column_sql, options)
|
729
|
+
execute(change_column_sql)
|
730
|
+
end
|
731
|
+
|
732
|
+
def rename_column(table_name, column_name, new_column_name) #:nodoc:
|
733
|
+
execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} to #{quote_column_name(new_column_name)}"
|
734
|
+
end
|
735
|
+
|
736
|
+
def remove_column(table_name, column_name) #:nodoc:
|
737
|
+
execute "ALTER TABLE #{table_name} DROP COLUMN #{quote_column_name(column_name)}"
|
738
|
+
end
|
739
|
+
|
740
|
+
# RSI: table and column comments
|
741
|
+
def add_comment(table_name, column_name, comment)
|
742
|
+
return if comment.blank?
|
743
|
+
execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{column_name} IS '#{comment}'"
|
744
|
+
end
|
745
|
+
|
746
|
+
def add_table_comment(table_name, comment)
|
747
|
+
return if comment.blank?
|
748
|
+
execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS '#{comment}'"
|
749
|
+
end
|
750
|
+
|
751
|
+
def table_comment(table_name)
|
752
|
+
(owner, table_name) = @connection.describe(quote_table_name(table_name))
|
753
|
+
select_value <<-SQL
|
754
|
+
SELECT comments FROM all_tab_comments
|
755
|
+
WHERE owner = '#{owner}'
|
756
|
+
AND table_name = '#{table_name}'
|
757
|
+
SQL
|
758
|
+
end
|
759
|
+
|
760
|
+
def column_comment(table_name, column_name)
|
761
|
+
(owner, table_name) = @connection.describe(quote_table_name(table_name))
|
762
|
+
select_value <<-SQL
|
763
|
+
SELECT comments FROM all_col_comments
|
764
|
+
WHERE owner = '#{owner}'
|
765
|
+
AND table_name = '#{table_name}'
|
766
|
+
AND column_name = '#{column_name.upcase}'
|
767
|
+
SQL
|
768
|
+
end
|
769
|
+
|
770
|
+
# Find a table's primary key and sequence.
|
771
|
+
# *Note*: Only primary key is implemented - sequence will be nil.
|
772
|
+
def pk_and_sequence_for(table_name)
|
773
|
+
(owner, table_name) = @connection.describe(quote_table_name(table_name))
|
774
|
+
|
775
|
+
# RSI: changed select from all_constraints to user_constraints - much faster in large data dictionaries
|
776
|
+
pks = select_values(<<-SQL, 'Primary Key')
|
777
|
+
select cc.column_name
|
778
|
+
from user_constraints c, all_cons_columns cc
|
779
|
+
where c.owner = '#{owner}'
|
780
|
+
and c.table_name = '#{table_name}'
|
781
|
+
and c.constraint_type = 'P'
|
782
|
+
and cc.owner = c.owner
|
783
|
+
and cc.constraint_name = c.constraint_name
|
784
|
+
SQL
|
785
|
+
|
786
|
+
# only support single column keys
|
787
|
+
pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
|
788
|
+
end
|
789
|
+
|
790
|
+
def structure_dump #:nodoc:
|
791
|
+
s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
|
792
|
+
structure << "create sequence #{seq.to_a.first.last};\n\n"
|
793
|
+
end
|
794
|
+
|
795
|
+
# RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
|
796
|
+
select_all("select table_name from all_tables where owner = sys_context('userenv','session_user')").inject(s) do |structure, table|
|
797
|
+
ddl = "create table #{table.to_a.first.last} (\n "
|
798
|
+
cols = select_all(%Q{
|
799
|
+
select column_name, data_type, data_length, char_used, char_length, data_precision, data_scale, data_default, nullable
|
800
|
+
from user_tab_columns
|
801
|
+
where table_name = '#{table.to_a.first.last}'
|
802
|
+
order by column_id
|
803
|
+
}).map do |row|
|
804
|
+
col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
|
805
|
+
if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
|
806
|
+
col << "(#{row['data_precision'].to_i}"
|
807
|
+
col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
|
808
|
+
col << ')'
|
809
|
+
elsif row['data_type'].include?('CHAR')
|
810
|
+
length = row['char_used'] == 'C' ? row['char_length'].to_i : row['data_length'].to_i
|
811
|
+
col << "(#{length})"
|
812
|
+
end
|
813
|
+
col << " default #{row['data_default']}" if !row['data_default'].nil?
|
814
|
+
col << ' not null' if row['nullable'] == 'N'
|
815
|
+
col
|
816
|
+
end
|
817
|
+
ddl << cols.join(",\n ")
|
818
|
+
ddl << ");\n\n"
|
819
|
+
structure << ddl
|
820
|
+
end
|
821
|
+
end
|
822
|
+
|
823
|
+
def structure_drop #:nodoc:
|
824
|
+
s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
|
825
|
+
drop << "drop sequence #{seq.to_a.first.last};\n\n"
|
826
|
+
end
|
827
|
+
|
828
|
+
# RSI: changed select from user_tables to all_tables - much faster in large data dictionaries
|
829
|
+
select_all("select table_name from all_tables where owner = sys_context('userenv','session_user')").inject(s) do |drop, table|
|
830
|
+
drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
|
831
|
+
end
|
832
|
+
end
|
833
|
+
|
834
|
+
def add_column_options!(sql, options) #:nodoc:
|
835
|
+
# handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
|
836
|
+
if options_include_default?(options) && (column = options[:column]) && column.type == :text
|
837
|
+
sql << " DEFAULT #{quote(options.delete(:default))}"
|
838
|
+
end
|
839
|
+
super
|
840
|
+
end
|
841
|
+
|
842
|
+
# SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
|
843
|
+
#
|
844
|
+
# Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
|
845
|
+
# queries. However, with those columns included in the SELECT DISTINCT list, you
|
846
|
+
# won't actually get a distinct list of the column you want (presuming the column
|
847
|
+
# has duplicates with multiple values for the ordered-by columns. So we use the
|
848
|
+
# FIRST_VALUE function to get a single (first) value for each column, effectively
|
849
|
+
# making every row the same.
|
850
|
+
#
|
851
|
+
# distinct("posts.id", "posts.created_at desc")
|
852
|
+
def distinct(columns, order_by)
|
853
|
+
return "DISTINCT #{columns}" if order_by.blank?
|
854
|
+
|
855
|
+
# construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
|
856
|
+
# FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
|
857
|
+
order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
|
858
|
+
order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
|
859
|
+
"FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
|
860
|
+
end
|
861
|
+
sql = "DISTINCT #{columns}, "
|
862
|
+
sql << order_columns * ", "
|
863
|
+
end
|
864
|
+
|
865
|
+
# ORDER BY clause for the passed order option.
|
866
|
+
#
|
867
|
+
# Uses column aliases as defined by #distinct.
|
868
|
+
def add_order_by_for_association_limiting!(sql, options)
|
869
|
+
return sql if options[:order].blank?
|
870
|
+
|
871
|
+
order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
|
872
|
+
order.map! {|s| $1 if s =~ / (.*)/}
|
873
|
+
order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')
|
874
|
+
|
875
|
+
sql << " ORDER BY #{order}"
|
876
|
+
end
|
877
|
+
|
878
|
+
private
|
879
|
+
|
880
|
+
def select(sql, name = nil)
|
881
|
+
cursor = execute(sql, name)
|
882
|
+
cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
|
883
|
+
rows = []
|
884
|
+
|
885
|
+
while row = cursor.fetch
|
886
|
+
hash = Hash.new
|
887
|
+
|
888
|
+
cols.each_with_index do |col, i|
|
889
|
+
hash[col] =
|
890
|
+
case row[i]
|
891
|
+
when OCI8::LOB
|
892
|
+
name == 'Writable Large Object' ? row[i]: row[i].read
|
893
|
+
when OraDate
|
894
|
+
d = row[i]
|
895
|
+
# RSI: added emulate_dates_by_column_name functionality
|
896
|
+
# if emulate_dates_by_column_name && self.class.is_date_column?(col)
|
897
|
+
# d.to_date
|
898
|
+
# elsif
|
899
|
+
if emulate_dates && (d.hour == 0 && d.minute == 0 && d.second == 0)
|
900
|
+
d.to_date
|
901
|
+
else
|
902
|
+
# see string_to_time; Time overflowing to DateTime, respecting the default timezone
|
903
|
+
time_array = [d.year, d.month, d.day, d.hour, d.minute, d.second]
|
904
|
+
begin
|
905
|
+
Time.send(Base.default_timezone, *time_array)
|
906
|
+
rescue
|
907
|
+
zone_offset = if Base.default_timezone == :local then DateTime.now.offset else 0 end
|
908
|
+
# Append zero calendar reform start to account for dates skipped by calendar reform
|
909
|
+
DateTime.new(*time_array[0..5] << zone_offset << 0) rescue nil
|
910
|
+
end
|
911
|
+
end
|
912
|
+
# RSI: added emulate_integers_by_column_name functionality
|
913
|
+
when Float
|
914
|
+
n = row[i]
|
915
|
+
if emulate_integers_by_column_name && self.class.is_integer_column?(col)
|
916
|
+
n.to_i
|
917
|
+
else
|
918
|
+
n
|
919
|
+
end
|
920
|
+
else row[i]
|
921
|
+
end unless col == 'raw_rnum_'
|
922
|
+
end
|
923
|
+
|
924
|
+
rows << hash
|
925
|
+
end
|
926
|
+
|
927
|
+
rows
|
928
|
+
ensure
|
929
|
+
cursor.close if cursor
|
930
|
+
end
|
931
|
+
|
932
|
+
# Oracle column names by default are case-insensitive, but treated as upcase;
|
933
|
+
# for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
|
934
|
+
# their column names when creating Oracle tables, which makes then case-sensitive.
|
935
|
+
# I don't know anybody who does this, but we'll handle the theoretical case of a
|
936
|
+
# camelCase column name. I imagine other dbs handle this different, since there's a
|
937
|
+
# unit test that's currently failing test_oci.
|
938
|
+
def oracle_downcase(column_name)
|
939
|
+
column_name =~ /[a-z]/ ? column_name : column_name.downcase
|
940
|
+
end
|
941
|
+
|
942
|
+
end
|
943
|
+
end
|
944
|
+
end
|
945
|
+
|
946
|
+
|
947
|
+
class OCI8 #:nodoc:
|
948
|
+
|
949
|
+
# This OCI8 patch may not longer be required with the upcoming
|
950
|
+
# release of version 0.2.
|
951
|
+
class Cursor #:nodoc:
|
952
|
+
alias :enhanced_define_a_column_pre_ar :define_a_column
|
953
|
+
def define_a_column(i)
|
954
|
+
case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
|
955
|
+
when 8; @stmt.defineByPos(i, String, 65535) # Read LONG values
|
956
|
+
when 187; @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
|
957
|
+
when 108
|
958
|
+
if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
|
959
|
+
@stmt.defineByPos(i, String, 65535)
|
960
|
+
else
|
961
|
+
raise 'unsupported datatype'
|
962
|
+
end
|
963
|
+
else enhanced_define_a_column_pre_ar i
|
964
|
+
end
|
965
|
+
end
|
966
|
+
end
|
967
|
+
|
968
|
+
# missing constant from oci8 < 0.1.14
|
969
|
+
OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
|
970
|
+
|
971
|
+
# Uses the describeAny OCI call to find the target owner and table_name
|
972
|
+
# indicated by +name+, parsing through synonynms as necessary. Returns
|
973
|
+
# an array of [owner, table_name].
|
974
|
+
def describe(name)
|
975
|
+
@desc ||= @@env.alloc(OCIDescribe)
|
976
|
+
@desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
|
977
|
+
do_ocicall(@ctx) { @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) } rescue raise %Q{"DESC #{name}" failed; does it exist?}
|
978
|
+
info = @desc.attrGet(OCI_ATTR_PARAM)
|
979
|
+
|
980
|
+
case info.attrGet(OCI_ATTR_PTYPE)
|
981
|
+
when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
|
982
|
+
owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
|
983
|
+
table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
|
984
|
+
[owner, table_name]
|
985
|
+
when OCI_PTYPE_SYN
|
986
|
+
schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
|
987
|
+
name = info.attrGet(OCI_ATTR_NAME)
|
988
|
+
describe(schema + '.' + name)
|
989
|
+
else raise %Q{"DESC #{name}" failed; not a table or view.}
|
990
|
+
end
|
991
|
+
end
|
992
|
+
|
993
|
+
end
|
994
|
+
|
995
|
+
|
996
|
+
# The OracleConnectionFactory factors out the code necessary to connect and
|
997
|
+
# configure an Oracle/OCI connection.
|
998
|
+
class OracleEnhancedConnectionFactory #:nodoc:
|
999
|
+
def new_connection(username, password, database, async, prefetch_rows, cursor_sharing, privilege)
|
1000
|
+
conn = OCI8.new username, password, database, privilege
|
1001
|
+
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
1002
|
+
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
|
1003
|
+
conn.autocommit = true
|
1004
|
+
conn.non_blocking = true if async
|
1005
|
+
conn.prefetch_rows = prefetch_rows
|
1006
|
+
conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
|
1007
|
+
conn
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
|
1012
|
+
# The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
|
1013
|
+
# reset functionality. If a call to #exec fails, and autocommit is turned on
|
1014
|
+
# (ie., we're not in the middle of a longer transaction), it will
|
1015
|
+
# automatically reconnect and try again. If autocommit is turned off,
|
1016
|
+
# this would be dangerous (as the earlier part of the implied transaction
|
1017
|
+
# may have failed silently if the connection died) -- so instead the
|
1018
|
+
# connection is marked as dead, to be reconnected on it's next use.
|
1019
|
+
class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
|
1020
|
+
attr_accessor :active
|
1021
|
+
alias :active? :active
|
1022
|
+
|
1023
|
+
cattr_accessor :auto_retry
|
1024
|
+
class << self
|
1025
|
+
alias :auto_retry? :auto_retry
|
1026
|
+
end
|
1027
|
+
@@auto_retry = false
|
1028
|
+
|
1029
|
+
def initialize(config, factory = OracleEnhancedConnectionFactory.new)
|
1030
|
+
@active = true
|
1031
|
+
@username, @password, @database, = config[:username].to_s, config[:password].to_s, config[:database].to_s
|
1032
|
+
@async = config[:allow_concurrency]
|
1033
|
+
@prefetch_rows = config[:prefetch_rows] || 100
|
1034
|
+
@cursor_sharing = config[:cursor_sharing] || 'similar'
|
1035
|
+
@factory = factory
|
1036
|
+
@privilege = config[:privilege]
|
1037
|
+
@connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing, @privilege
|
1038
|
+
super @connection
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
# Checks connection, returns true if active. Note that ping actively
|
1042
|
+
# checks the connection, while #active? simply returns the last
|
1043
|
+
# known state.
|
1044
|
+
def ping
|
1045
|
+
@connection.exec("select 1 from dual") { |r| nil }
|
1046
|
+
@active = true
|
1047
|
+
rescue
|
1048
|
+
@active = false
|
1049
|
+
raise
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
# Resets connection, by logging off and creating a new connection.
|
1053
|
+
def reset!
|
1054
|
+
logoff rescue nil
|
1055
|
+
begin
|
1056
|
+
@connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing, @privilege
|
1057
|
+
__setobj__ @connection
|
1058
|
+
@active = true
|
1059
|
+
rescue
|
1060
|
+
@active = false
|
1061
|
+
raise
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
# ORA-00028: your session has been killed
|
1066
|
+
# ORA-01012: not logged on
|
1067
|
+
# ORA-03113: end-of-file on communication channel
|
1068
|
+
# ORA-03114: not connected to ORACLE
|
1069
|
+
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]
|
1070
|
+
|
1071
|
+
# Adds auto-recovery functionality.
|
1072
|
+
#
|
1073
|
+
# See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
|
1074
|
+
def exec(sql, *bindvars, &block)
|
1075
|
+
should_retry = self.class.auto_retry? && autocommit?
|
1076
|
+
|
1077
|
+
begin
|
1078
|
+
@connection.exec(sql, *bindvars, &block)
|
1079
|
+
rescue OCIException => e
|
1080
|
+
raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
|
1081
|
+
@active = false
|
1082
|
+
raise unless should_retry
|
1083
|
+
should_retry = false
|
1084
|
+
reset! rescue nil
|
1085
|
+
retry
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
rescue LoadError
|
1092
|
+
# OCI8 driver is unavailable.
|
1093
|
+
if defined?(RAILS_DEFAULT_LOGGER)
|
1094
|
+
RAILS_DEFAULT_LOGGER.error "ERROR: ActiveRecord oracle_enhanced adapter could not load ruby-oci8 library. "+
|
1095
|
+
"Please install ruby-oci8 library or gem."
|
1096
|
+
end
|
1097
|
+
module ActiveRecord # :nodoc:
|
1098
|
+
class Base
|
1099
|
+
@@oracle_error_message = "Oracle/OCI libraries could not be loaded: #{$!.to_s}"
|
1100
|
+
def self.oracle_enhanced_connection(config) # :nodoc:
|
1101
|
+
# Set up a reasonable error message
|
1102
|
+
raise LoadError, @@oracle_error_message
|
1103
|
+
end
|
1104
|
+
end
|
1105
|
+
end
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
# RSI: Added LOB writing callback for sessions stored in database
|
1109
|
+
# Otherwise it is not working as Session class is defined before OracleAdapter is loaded in Rails 2.0
|
1110
|
+
if defined?(CGI::Session::ActiveRecordStore::Session)
|
1111
|
+
if !CGI::Session::ActiveRecordStore::Session.respond_to?(:after_save_callback_chain) ||
|
1112
|
+
CGI::Session::ActiveRecordStore::Session.after_save_callback_chain.detect{|cb| cb.method == :enhanced_write_lobs}.nil?
|
1113
|
+
class CGI::Session::ActiveRecordStore::Session
|
1114
|
+
after_save :enhanced_write_lobs
|
1115
|
+
end
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
# RSI: load custom create, update, delete methods functionality
|
1120
|
+
# rescue LoadError if ruby-plsql gem cannot be loaded
|
1121
|
+
begin
|
1122
|
+
require 'active_record/connection_adapters/oracle_enhanced_procedures'
|
1123
|
+
rescue LoadError
|
1124
|
+
if defined?(RAILS_DEFAULT_LOGGER)
|
1125
|
+
RAILS_DEFAULT_LOGGER.info "INFO: ActiveRecord oracle_enhanced adapter could not load ruby-plsql gem. "+
|
1126
|
+
"Custom create, update and delete methods will not be available."
|
1127
|
+
end
|
1128
|
+
end
|
1129
|
+
|
1130
|
+
# RSI: load additional methods for composite_primary_keys support
|
1131
|
+
require 'active_record/connection_adapters/oracle_enhanced_cpk'
|
1132
|
+
|
1133
|
+
# RSI: load patch for dirty tracking methods
|
1134
|
+
require 'active_record/connection_adapters/oracle_enhanced_dirty'
|
1135
|
+
|
1136
|
+
# handles quoting of oracle reserved words
|
1137
|
+
require 'active_record/connection_adapters/oracle_enhanced_reserved_words'
|