activerecord-oracle_enhanced-adapter 1.2.1 → 1.2.2
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 +34 -0
- data/README.rdoc +10 -5
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +4 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +534 -170
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +53 -3
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +10 -10
- data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +3 -3
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +3 -3
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +86 -58
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +105 -68
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +27 -1
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +164 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +122 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +224 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +2 -2
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +230 -455
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +37 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +1 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +6 -2
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +21 -4
- data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +63 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +1 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +1 -3
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +1 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +255 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_spec.rb +720 -0
- data/spec/spec_helper.rb +38 -7
- metadata +13 -15
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module ConnectionAdapters
|
3
3
|
# interface independent methods
|
4
|
-
class OracleEnhancedConnection
|
4
|
+
class OracleEnhancedConnection #:nodoc:
|
5
5
|
|
6
6
|
def self.create(config)
|
7
7
|
case ORACLE_ENHANCED_CONNECTION
|
@@ -23,9 +23,60 @@ module ActiveRecord
|
|
23
23
|
# camelCase column name. I imagine other dbs handle this different, since there's a
|
24
24
|
# unit test that's currently failing test_oci.
|
25
25
|
def oracle_downcase(column_name)
|
26
|
+
return nil if column_name.nil?
|
26
27
|
column_name =~ /[a-z]/ ? column_name : column_name.downcase
|
27
28
|
end
|
28
29
|
|
30
|
+
# Used always by JDBC connection as well by OCI connection when describing tables over database link
|
31
|
+
def describe(name)
|
32
|
+
name = name.to_s
|
33
|
+
if name.include?('@')
|
34
|
+
name, db_link = name.split('@')
|
35
|
+
default_owner = select_value("SELECT username FROM all_db_links WHERE db_link = '#{db_link.upcase}'")
|
36
|
+
db_link = "@#{db_link}"
|
37
|
+
else
|
38
|
+
db_link = nil
|
39
|
+
default_owner = @owner
|
40
|
+
end
|
41
|
+
real_name = OracleEnhancedAdapter.valid_table_name?(name) ? name.upcase : name
|
42
|
+
if real_name.include?('.')
|
43
|
+
table_owner, table_name = real_name.split('.')
|
44
|
+
else
|
45
|
+
table_owner, table_name = default_owner, real_name
|
46
|
+
end
|
47
|
+
sql = <<-SQL
|
48
|
+
SELECT owner, table_name, 'TABLE' name_type
|
49
|
+
FROM all_tables#{db_link}
|
50
|
+
WHERE owner = '#{table_owner}'
|
51
|
+
AND table_name = '#{table_name}'
|
52
|
+
UNION ALL
|
53
|
+
SELECT owner, view_name table_name, 'VIEW' name_type
|
54
|
+
FROM all_views#{db_link}
|
55
|
+
WHERE owner = '#{table_owner}'
|
56
|
+
AND view_name = '#{table_name}'
|
57
|
+
UNION ALL
|
58
|
+
SELECT table_owner, DECODE(db_link, NULL, table_name, table_name||'@'||db_link), 'SYNONYM' name_type
|
59
|
+
FROM all_synonyms#{db_link}
|
60
|
+
WHERE owner = '#{table_owner}'
|
61
|
+
AND synonym_name = '#{table_name}'
|
62
|
+
UNION ALL
|
63
|
+
SELECT table_owner, DECODE(db_link, NULL, table_name, table_name||'@'||db_link), 'SYNONYM' name_type
|
64
|
+
FROM all_synonyms#{db_link}
|
65
|
+
WHERE owner = 'PUBLIC'
|
66
|
+
AND synonym_name = '#{real_name}'
|
67
|
+
SQL
|
68
|
+
if result = select_one(sql)
|
69
|
+
case result['name_type']
|
70
|
+
when 'SYNONYM'
|
71
|
+
describe("#{result['owner'] && "#{result['owner']}."}#{result['table_name']}#{db_link}")
|
72
|
+
else
|
73
|
+
db_link ? [result['owner'], result['table_name'], db_link] : [result['owner'], result['table_name']]
|
74
|
+
end
|
75
|
+
else
|
76
|
+
raise OracleEnhancedConnectionException, %Q{"DESC #{name}" failed; does it exist?}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
29
80
|
private
|
30
81
|
|
31
82
|
# Returns a record hash with the column names as keys and column values
|
@@ -48,11 +99,10 @@ module ActiveRecord
|
|
48
99
|
result = select(sql)
|
49
100
|
result.map { |r| r.values.first }
|
50
101
|
end
|
51
|
-
|
52
102
|
|
53
103
|
end
|
54
104
|
|
55
|
-
class OracleEnhancedConnectionException < StandardError
|
105
|
+
class OracleEnhancedConnectionException < StandardError #:nodoc:
|
56
106
|
end
|
57
107
|
|
58
108
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require "bigdecimal"
|
2
2
|
unless BigDecimal.instance_methods.include?("to_d")
|
3
3
|
BigDecimal.class_eval do
|
4
|
-
def to_d
|
4
|
+
def to_d #:nodoc:
|
5
5
|
self
|
6
6
|
end
|
7
7
|
end
|
@@ -9,7 +9,7 @@ end
|
|
9
9
|
|
10
10
|
unless Bignum.instance_methods.include?("to_d")
|
11
11
|
Bignum.class_eval do
|
12
|
-
def to_d
|
12
|
+
def to_d #:nodoc:
|
13
13
|
BigDecimal.new(self.to_s)
|
14
14
|
end
|
15
15
|
end
|
@@ -17,7 +17,7 @@ end
|
|
17
17
|
|
18
18
|
unless Fixnum.instance_methods.include?("to_d")
|
19
19
|
Fixnum.class_eval do
|
20
|
-
def to_d
|
20
|
+
def to_d #:nodoc:
|
21
21
|
BigDecimal.new(self.to_s)
|
22
22
|
end
|
23
23
|
end
|
@@ -30,22 +30,22 @@ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' && RUBY_VERSION >= '1.9'
|
|
30
30
|
require "unicode_utils/upcase"
|
31
31
|
require "unicode_utils/downcase"
|
32
32
|
|
33
|
-
module ActiveRecord
|
34
|
-
module ConnectionAdapters
|
35
|
-
module OracleEnhancedUnicodeString
|
36
|
-
def upcase
|
33
|
+
module ActiveRecord #:nodoc:
|
34
|
+
module ConnectionAdapters #:nodoc:
|
35
|
+
module OracleEnhancedUnicodeString #:nodoc:
|
36
|
+
def upcase #:nodoc:
|
37
37
|
UnicodeUtils.upcase(self)
|
38
38
|
end
|
39
39
|
|
40
|
-
def downcase
|
40
|
+
def downcase #:nodoc:
|
41
41
|
UnicodeUtils.downcase(self)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
class String
|
48
|
-
def mb_chars
|
47
|
+
class String #:nodoc:
|
48
|
+
def mb_chars #:nodoc:
|
49
49
|
self.extend(ActiveRecord::ConnectionAdapters::OracleEnhancedUnicodeString)
|
50
50
|
self
|
51
51
|
end
|
@@ -2,13 +2,13 @@ module ActiveRecord #:nodoc:
|
|
2
2
|
module ConnectionAdapters #:nodoc:
|
3
3
|
module OracleEnhancedCpk #:nodoc:
|
4
4
|
|
5
|
-
# This mightn't be in Core, but count(distinct x,y) doesn't work for me
|
6
|
-
#
|
5
|
+
# This mightn't be in Core, but count(distinct x,y) doesn't work for me.
|
6
|
+
# Return that not supported if composite_primary_keys gem is required.
|
7
7
|
def supports_count_distinct? #:nodoc:
|
8
8
|
@supports_count_distinct ||= ! defined?(CompositePrimaryKeys)
|
9
9
|
end
|
10
10
|
|
11
|
-
def concat(*columns)
|
11
|
+
def concat(*columns) #:nodoc:
|
12
12
|
"(#{columns.join('||')})"
|
13
13
|
end
|
14
14
|
|
@@ -2,19 +2,19 @@ module ActiveRecord #:nodoc:
|
|
2
2
|
module ConnectionAdapters #:nodoc:
|
3
3
|
module OracleEnhancedDirty #:nodoc:
|
4
4
|
|
5
|
-
module InstanceMethods
|
5
|
+
module InstanceMethods #:nodoc:
|
6
6
|
private
|
7
7
|
|
8
8
|
def field_changed?(attr, old, value)
|
9
9
|
if column = column_for_attribute(attr)
|
10
|
-
#
|
10
|
+
# Added also :decimal type
|
11
11
|
if (column.type == :integer || column.type == :decimal) && column.null && (old.nil? || old == 0) && value.blank?
|
12
12
|
# For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
|
13
13
|
# Hence we don't record it as a change if the value changes from nil to ''.
|
14
14
|
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
|
15
15
|
# be typecast back to 0 (''.to_i => 0)
|
16
16
|
value = nil
|
17
|
-
#
|
17
|
+
# Oracle stores empty string '' or empty text (CLOB) as NULL
|
18
18
|
# therefore need to convert empty string value to nil if old value is nil
|
19
19
|
elsif (column.type == :string || column.type == :text) && column.null && old.nil?
|
20
20
|
value = nil if value == ''
|
@@ -1,20 +1,28 @@
|
|
1
1
|
begin
|
2
2
|
require "java"
|
3
3
|
require "jruby"
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
# ojdbc14.jar file should be in JRUBY_HOME/lib or should be in ENV['PATH'] or load path
|
6
6
|
|
7
7
|
ojdbc_jar = "ojdbc14.jar"
|
8
|
-
|
9
|
-
|
8
|
+
|
9
|
+
unless ENV_JAVA['java.class.path'] =~ Regexp.new(ojdbc_jar)
|
10
|
+
# Adds JRuby classloader to current thread classloader - as a result ojdbc14.jar should not be in $JRUBY_HOME/lib
|
11
|
+
# not necessary anymore for JRuby 1.3
|
12
|
+
# java.lang.Thread.currentThread.setContextClassLoader(JRuby.runtime.jruby_class_loader)
|
13
|
+
|
14
|
+
if ojdbc_jar_path = ENV["PATH"].split(/[:;]/).concat($LOAD_PATH).find{|d| File.exists?(File.join(d,ojdbc_jar))}
|
15
|
+
require File.join(ojdbc_jar_path,ojdbc_jar)
|
16
|
+
end
|
10
17
|
end
|
11
|
-
|
12
|
-
# import java.sql.Connection
|
13
|
-
# import java.sql.SQLException
|
14
|
-
# import java.sql.Types
|
15
|
-
# import java.sql.DriverManager
|
18
|
+
|
16
19
|
java.sql.DriverManager.registerDriver Java::oracle.jdbc.driver.OracleDriver.new
|
17
20
|
|
21
|
+
# set tns_admin property from TNS_ADMIN environment variable
|
22
|
+
if !java.lang.System.get_property("oracle.net.tns_admin") && ENV["TNS_ADMIN"]
|
23
|
+
java.lang.System.set_property("oracle.net.tns_admin", ENV["TNS_ADMIN"])
|
24
|
+
end
|
25
|
+
|
18
26
|
rescue LoadError, NameError
|
19
27
|
# JDBC driver is unavailable.
|
20
28
|
error_message = "ERROR: ActiveRecord oracle_enhanced adapter could not load Oracle JDBC driver. "+
|
@@ -32,7 +40,7 @@ module ActiveRecord
|
|
32
40
|
module ConnectionAdapters
|
33
41
|
|
34
42
|
# JDBC database interface for JRuby
|
35
|
-
class OracleEnhancedJDBCConnection < OracleEnhancedConnection
|
43
|
+
class OracleEnhancedJDBCConnection < OracleEnhancedConnection #:nodoc:
|
36
44
|
|
37
45
|
attr_accessor :active
|
38
46
|
alias :active? :active
|
@@ -52,10 +60,17 @@ module ActiveRecord
|
|
52
60
|
privilege = config[:privilege] && config[:privilege].to_s
|
53
61
|
host, port = config[:host], config[:port]
|
54
62
|
|
55
|
-
|
63
|
+
# connection using TNS alias
|
64
|
+
if database && !host && !config[:url] && ENV['TNS_ADMIN']
|
65
|
+
url = "jdbc:oracle:thin:@#{database || 'XE'}"
|
66
|
+
else
|
67
|
+
url = config[:url] || "jdbc:oracle:thin:@#{host || 'localhost'}:#{port || 1521}:#{database || 'XE'}"
|
68
|
+
end
|
56
69
|
|
57
70
|
prefetch_rows = config[:prefetch_rows] || 100
|
58
|
-
cursor_sharing = config[:cursor_sharing] || '
|
71
|
+
cursor_sharing = config[:cursor_sharing] || 'force'
|
72
|
+
# by default VARCHAR2 column size will be interpreted as max number of characters (and not bytes)
|
73
|
+
nls_length_semantics = config[:nls_length_semantics] || 'CHAR'
|
59
74
|
|
60
75
|
properties = java.util.Properties.new
|
61
76
|
properties.put("user", username)
|
@@ -65,8 +80,9 @@ module ActiveRecord
|
|
65
80
|
|
66
81
|
@raw_connection = java.sql.DriverManager.getConnection(url, properties)
|
67
82
|
exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
68
|
-
exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'}
|
69
|
-
exec "alter session set cursor_sharing = #{cursor_sharing}"
|
83
|
+
exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'}
|
84
|
+
exec "alter session set cursor_sharing = #{cursor_sharing}"
|
85
|
+
exec "alter session set nls_length_semantics = '#{nls_length_semantics}'"
|
70
86
|
self.autocommit = true
|
71
87
|
|
72
88
|
# Set session time zone to current time zone
|
@@ -159,16 +175,64 @@ module ActiveRecord
|
|
159
175
|
end
|
160
176
|
|
161
177
|
def exec_no_retry(sql)
|
162
|
-
cs = @raw_connection.prepareCall(sql)
|
163
178
|
case sql
|
164
|
-
when /\A\s*UPDATE
|
165
|
-
|
179
|
+
when /\A\s*(UPDATE|INSERT|DELETE)/i
|
180
|
+
s = @raw_connection.prepareStatement(sql)
|
181
|
+
s.executeUpdate
|
182
|
+
# it is safer for CREATE and DROP statements not to use PreparedStatement
|
183
|
+
# as it does not allow creation of triggers with :NEW in their definition
|
184
|
+
when /\A\s*(CREATE|DROP)/i
|
185
|
+
s = @raw_connection.createStatement()
|
186
|
+
s.execute(sql)
|
187
|
+
true
|
166
188
|
else
|
167
|
-
|
189
|
+
s = @raw_connection.prepareStatement(sql)
|
190
|
+
s.execute
|
168
191
|
true
|
169
192
|
end
|
170
193
|
ensure
|
171
|
-
|
194
|
+
s.close rescue nil
|
195
|
+
end
|
196
|
+
|
197
|
+
def returning_clause(quoted_pk)
|
198
|
+
" RETURNING #{quoted_pk} INTO ?"
|
199
|
+
end
|
200
|
+
|
201
|
+
# execute sql with RETURNING ... INTO :insert_id
|
202
|
+
# and return :insert_id value
|
203
|
+
def exec_with_returning(sql)
|
204
|
+
with_retry do
|
205
|
+
begin
|
206
|
+
# it will always be INSERT statement
|
207
|
+
|
208
|
+
# TODO: need to investigate why PreparedStatement is giving strange exception "Protocol violation"
|
209
|
+
# s = @raw_connection.prepareStatement(sql)
|
210
|
+
# s.registerReturnParameter(1, ::Java::oracle.jdbc.OracleTypes::NUMBER)
|
211
|
+
# count = s.executeUpdate
|
212
|
+
# if count > 0
|
213
|
+
# rs = s.getReturnResultSet
|
214
|
+
# if rs.next
|
215
|
+
# # Assuming that primary key will not be larger as long max value
|
216
|
+
# insert_id = rs.getLong(1)
|
217
|
+
# rs.wasNull ? nil : insert_id
|
218
|
+
# else
|
219
|
+
# nil
|
220
|
+
# end
|
221
|
+
# else
|
222
|
+
# nil
|
223
|
+
# end
|
224
|
+
|
225
|
+
# Workaround with CallableStatement
|
226
|
+
s = @raw_connection.prepareCall("BEGIN #{sql}; END;")
|
227
|
+
s.registerOutParameter(1, java.sql.Types::BIGINT)
|
228
|
+
s.execute
|
229
|
+
insert_id = s.getLong(1)
|
230
|
+
s.wasNull ? nil : insert_id
|
231
|
+
ensure
|
232
|
+
# rs.close rescue nil
|
233
|
+
s.close rescue nil
|
234
|
+
end
|
235
|
+
end
|
172
236
|
end
|
173
237
|
|
174
238
|
def select(sql, name = nil, return_column_names = false)
|
@@ -220,46 +284,10 @@ module ActiveRecord
|
|
220
284
|
end
|
221
285
|
end
|
222
286
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
table_owner, table_name = real_name.split('.')
|
227
|
-
else
|
228
|
-
table_owner, table_name = @owner, real_name
|
229
|
-
end
|
230
|
-
sql = <<-SQL
|
231
|
-
SELECT owner, table_name, 'TABLE' name_type
|
232
|
-
FROM all_tables
|
233
|
-
WHERE owner = '#{table_owner}'
|
234
|
-
AND table_name = '#{table_name}'
|
235
|
-
UNION ALL
|
236
|
-
SELECT owner, view_name table_name, 'VIEW' name_type
|
237
|
-
FROM all_views
|
238
|
-
WHERE owner = '#{table_owner}'
|
239
|
-
AND view_name = '#{table_name}'
|
240
|
-
UNION ALL
|
241
|
-
SELECT table_owner, table_name, 'SYNONYM' name_type
|
242
|
-
FROM all_synonyms
|
243
|
-
WHERE owner = '#{table_owner}'
|
244
|
-
AND synonym_name = '#{table_name}'
|
245
|
-
UNION ALL
|
246
|
-
SELECT table_owner, table_name, 'SYNONYM' name_type
|
247
|
-
FROM all_synonyms
|
248
|
-
WHERE owner = 'PUBLIC'
|
249
|
-
AND synonym_name = '#{real_name}'
|
250
|
-
SQL
|
251
|
-
if result = select_one(sql)
|
252
|
-
case result['name_type']
|
253
|
-
when 'SYNONYM'
|
254
|
-
describe("#{result['owner']}.#{result['table_name']}")
|
255
|
-
else
|
256
|
-
[result['owner'], result['table_name']]
|
257
|
-
end
|
258
|
-
else
|
259
|
-
raise OracleEnhancedConnectionException, %Q{"DESC #{name}" failed; does it exist?}
|
260
|
-
end
|
287
|
+
# Return NativeException / java.sql.SQLException error code
|
288
|
+
def error_code(exception)
|
289
|
+
exception.cause.getErrorCode
|
261
290
|
end
|
262
|
-
|
263
291
|
|
264
292
|
private
|
265
293
|
|
@@ -3,7 +3,7 @@ require 'delegate'
|
|
3
3
|
begin
|
4
4
|
require 'oci8' unless self.class.const_defined? :OCI8
|
5
5
|
|
6
|
-
#
|
6
|
+
# added mapping for TIMESTAMP / WITH TIME ZONE / LOCAL TIME ZONE types
|
7
7
|
# currently Ruby-OCI8 does not support fractional seconds for timestamps
|
8
8
|
OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP] = OCI8::BindType::OraDate
|
9
9
|
OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_TZ] = OCI8::BindType::OraDate
|
@@ -24,10 +24,12 @@ module ActiveRecord
|
|
24
24
|
module ConnectionAdapters
|
25
25
|
|
26
26
|
# OCI database interface for MRI
|
27
|
-
class OracleEnhancedOCIConnection < OracleEnhancedConnection
|
27
|
+
class OracleEnhancedOCIConnection < OracleEnhancedConnection #:nodoc:
|
28
28
|
|
29
29
|
def initialize(config)
|
30
30
|
@raw_connection = OCI8EnhancedAutoRecover.new(config, OracleEnhancedOCIFactory)
|
31
|
+
# default schema owner
|
32
|
+
@owner = config[:username].to_s.upcase
|
31
33
|
end
|
32
34
|
|
33
35
|
def auto_retry
|
@@ -81,6 +83,21 @@ module ActiveRecord
|
|
81
83
|
def exec(sql, *bindvars, &block)
|
82
84
|
@raw_connection.exec(sql, *bindvars, &block)
|
83
85
|
end
|
86
|
+
|
87
|
+
def returning_clause(quoted_pk)
|
88
|
+
" RETURNING #{quoted_pk} INTO :insert_id"
|
89
|
+
end
|
90
|
+
|
91
|
+
# execute sql with RETURNING ... INTO :insert_id
|
92
|
+
# and return :insert_id value
|
93
|
+
def exec_with_returning(sql)
|
94
|
+
cursor = @raw_connection.parse(sql)
|
95
|
+
cursor.bind_param(':insert_id', nil, Integer)
|
96
|
+
cursor.exec
|
97
|
+
cursor[':insert_id']
|
98
|
+
ensure
|
99
|
+
cursor.close rescue nil
|
100
|
+
end
|
84
101
|
|
85
102
|
def select(sql, name = nil, return_column_names = false)
|
86
103
|
cursor = @raw_connection.exec(sql)
|
@@ -100,59 +117,7 @@ module ActiveRecord
|
|
100
117
|
hash = column_hash.dup
|
101
118
|
|
102
119
|
cols.each_with_index do |col, i|
|
103
|
-
hash[col] =
|
104
|
-
case v = row[i]
|
105
|
-
# RSI: added emulate_integers_by_column_name functionality
|
106
|
-
when Float
|
107
|
-
# if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(col)
|
108
|
-
# v.to_i
|
109
|
-
# else
|
110
|
-
# v
|
111
|
-
# end
|
112
|
-
v == (v_to_i = v.to_i) ? v_to_i : v
|
113
|
-
# ruby-oci8 2.0 returns OraNumber - convert it to Integer or BigDecimal
|
114
|
-
when OraNumber
|
115
|
-
v == (v_to_i = v.to_i) ? v_to_i : BigDecimal.new(v.to_s)
|
116
|
-
when String
|
117
|
-
v
|
118
|
-
when OCI8::LOB
|
119
|
-
if get_lob_value
|
120
|
-
data = v.read
|
121
|
-
# In Ruby 1.9.1 always change encoding to ASCII-8BIT for binaries
|
122
|
-
data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) && v.is_a?(OCI8::BLOB)
|
123
|
-
data
|
124
|
-
else
|
125
|
-
v
|
126
|
-
end
|
127
|
-
# ruby-oci8 1.0 returns OraDate
|
128
|
-
when OraDate
|
129
|
-
# RSI: added emulate_dates_by_column_name functionality
|
130
|
-
if OracleEnhancedAdapter.emulate_dates && (v.hour == 0 && v.minute == 0 && v.second == 0)
|
131
|
-
v.to_date
|
132
|
-
else
|
133
|
-
# code from Time.time_with_datetime_fallback
|
134
|
-
begin
|
135
|
-
Time.send(Base.default_timezone, v.year, v.month, v.day, v.hour, v.minute, v.second)
|
136
|
-
rescue
|
137
|
-
offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
|
138
|
-
::DateTime.civil(v.year, v.month, v.day, v.hour, v.minute, v.second, offset)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
# ruby-oci8 2.0 returns Time or DateTime
|
142
|
-
when Time, DateTime
|
143
|
-
if OracleEnhancedAdapter.emulate_dates && (v.hour == 0 && v.min == 0 && v.sec == 0)
|
144
|
-
v.to_date
|
145
|
-
else
|
146
|
-
# recreate Time or DateTime using Base.default_timezone
|
147
|
-
begin
|
148
|
-
Time.send(Base.default_timezone, v.year, v.month, v.day, v.hour, v.min, v.sec)
|
149
|
-
rescue
|
150
|
-
offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
|
151
|
-
::DateTime.civil(v.year, v.month, v.day, v.hour, v.min, v.sec, offset)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
else v
|
155
|
-
end
|
120
|
+
hash[col] = typecast_result_value(row[i], get_lob_value)
|
156
121
|
end
|
157
122
|
|
158
123
|
rows << hash
|
@@ -168,12 +133,79 @@ module ActiveRecord
|
|
168
133
|
end
|
169
134
|
|
170
135
|
def describe(name)
|
136
|
+
# fall back to SELECT based describe if using database link
|
137
|
+
return super if name.to_s.include?('@')
|
171
138
|
quoted_name = OracleEnhancedAdapter.valid_table_name?(name) ? name : "\"#{name}\""
|
172
139
|
@raw_connection.describe(quoted_name)
|
173
140
|
rescue OCIException => e
|
174
|
-
|
141
|
+
# fall back to SELECT which can handle synonyms to database links
|
142
|
+
super
|
143
|
+
end
|
144
|
+
|
145
|
+
# Return OCIError error code
|
146
|
+
def error_code(exception)
|
147
|
+
exception.code
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def typecast_result_value(value, get_lob_value)
|
153
|
+
case value
|
154
|
+
when Fixnum, Bignum
|
155
|
+
value
|
156
|
+
when String
|
157
|
+
value
|
158
|
+
when Float
|
159
|
+
value == (v_to_i = value.to_i) ? v_to_i : value
|
160
|
+
# ruby-oci8 2.0 returns OraNumber if Oracle type is NUMBER
|
161
|
+
when OraNumber
|
162
|
+
value == (v_to_i = value.to_i) ? v_to_i : BigDecimal.new(value.to_s)
|
163
|
+
when OCI8::LOB
|
164
|
+
if get_lob_value
|
165
|
+
data = value.read
|
166
|
+
# In Ruby 1.9.1 always change encoding to ASCII-8BIT for binaries
|
167
|
+
data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) && value.is_a?(OCI8::BLOB)
|
168
|
+
data
|
169
|
+
else
|
170
|
+
value
|
171
|
+
end
|
172
|
+
# ruby-oci8 1.0 returns OraDate
|
173
|
+
# ruby-oci8 2.0 returns Time or DateTime
|
174
|
+
when OraDate, Time, DateTime
|
175
|
+
if OracleEnhancedAdapter.emulate_dates && date_without_time?(value)
|
176
|
+
value.to_date
|
177
|
+
else
|
178
|
+
create_time_with_default_timezone(value)
|
179
|
+
end
|
180
|
+
else
|
181
|
+
value
|
182
|
+
end
|
175
183
|
end
|
176
184
|
|
185
|
+
def date_without_time?(value)
|
186
|
+
case value
|
187
|
+
when OraDate
|
188
|
+
value.hour == 0 && value.minute == 0 && value.second == 0
|
189
|
+
else
|
190
|
+
value.hour == 0 && value.min == 0 && value.sec == 0
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def create_time_with_default_timezone(value)
|
195
|
+
year, month, day, hour, min, sec = case value
|
196
|
+
when OraDate
|
197
|
+
[value.year, value.month, value.day, value.hour, value.minute, value.second]
|
198
|
+
else
|
199
|
+
[value.year, value.month, value.day, value.hour, value.min, value.sec]
|
200
|
+
end
|
201
|
+
# code from Time.time_with_datetime_fallback
|
202
|
+
begin
|
203
|
+
Time.send(Base.default_timezone, year, month, day, hour, min, sec)
|
204
|
+
rescue
|
205
|
+
offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
|
206
|
+
::DateTime.civil(year, month, day, hour, min, sec, offset)
|
207
|
+
end
|
208
|
+
end
|
177
209
|
end
|
178
210
|
|
179
211
|
# The OracleEnhancedOCIFactory factors out the code necessary to connect and
|
@@ -184,7 +216,9 @@ module ActiveRecord
|
|
184
216
|
privilege = config[:privilege] && config[:privilege].to_sym
|
185
217
|
async = config[:allow_concurrency]
|
186
218
|
prefetch_rows = config[:prefetch_rows] || 100
|
187
|
-
cursor_sharing = config[:cursor_sharing] || '
|
219
|
+
cursor_sharing = config[:cursor_sharing] || 'force'
|
220
|
+
# by default VARCHAR2 column size will be interpreted as max number of characters (and not bytes)
|
221
|
+
nls_length_semantics = config[:nls_length_semantics] || 'CHAR'
|
188
222
|
|
189
223
|
conn = OCI8.new username, password, database, privilege
|
190
224
|
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
@@ -193,6 +227,7 @@ module ActiveRecord
|
|
193
227
|
conn.non_blocking = true if async
|
194
228
|
conn.prefetch_rows = prefetch_rows
|
195
229
|
conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
|
230
|
+
conn.exec "alter session set nls_length_semantics = '#{nls_length_semantics}'"
|
196
231
|
conn
|
197
232
|
end
|
198
233
|
end
|
@@ -274,17 +309,18 @@ end
|
|
274
309
|
# this would be dangerous (as the earlier part of the implied transaction
|
275
310
|
# may have failed silently if the connection died) -- so instead the
|
276
311
|
# connection is marked as dead, to be reconnected on it's next use.
|
312
|
+
#:stopdoc:
|
277
313
|
class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
|
278
|
-
attr_accessor :active
|
279
|
-
alias :active? :active
|
314
|
+
attr_accessor :active #:nodoc:
|
315
|
+
alias :active? :active #:nodoc:
|
280
316
|
|
281
317
|
cattr_accessor :auto_retry
|
282
318
|
class << self
|
283
|
-
alias :auto_retry? :auto_retry
|
319
|
+
alias :auto_retry? :auto_retry #:nodoc:
|
284
320
|
end
|
285
321
|
@@auto_retry = false
|
286
322
|
|
287
|
-
def initialize(config, factory)
|
323
|
+
def initialize(config, factory) #:nodoc:
|
288
324
|
@active = true
|
289
325
|
@config = config
|
290
326
|
@factory = factory
|
@@ -295,7 +331,7 @@ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
|
|
295
331
|
# Checks connection, returns true if active. Note that ping actively
|
296
332
|
# checks the connection, while #active? simply returns the last
|
297
333
|
# known state.
|
298
|
-
def ping
|
334
|
+
def ping #:nodoc:
|
299
335
|
@connection.exec("select 1 from dual") { |r| nil }
|
300
336
|
@active = true
|
301
337
|
rescue
|
@@ -304,7 +340,7 @@ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
|
|
304
340
|
end
|
305
341
|
|
306
342
|
# Resets connection, by logging off and creating a new connection.
|
307
|
-
def reset!
|
343
|
+
def reset! #:nodoc:
|
308
344
|
logoff rescue nil
|
309
345
|
begin
|
310
346
|
@connection = @factory.new_connection @config
|
@@ -321,18 +357,18 @@ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
|
|
321
357
|
# ORA-03113: end-of-file on communication channel
|
322
358
|
# ORA-03114: not connected to ORACLE
|
323
359
|
# ORA-03135: connection lost contact
|
324
|
-
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ]
|
360
|
+
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ] #:nodoc:
|
325
361
|
|
326
362
|
# Adds auto-recovery functionality.
|
327
363
|
#
|
328
364
|
# See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
|
329
|
-
def exec(sql, *bindvars, &block)
|
365
|
+
def exec(sql, *bindvars, &block) #:nodoc:
|
330
366
|
should_retry = self.class.auto_retry? && autocommit?
|
331
367
|
|
332
368
|
begin
|
333
369
|
@connection.exec(sql, *bindvars, &block)
|
334
370
|
rescue OCIException => e
|
335
|
-
raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
|
371
|
+
raise unless e.is_a?(OCIError) && LOST_CONNECTION_ERROR_CODES.include?(e.code)
|
336
372
|
@active = false
|
337
373
|
raise unless should_retry
|
338
374
|
should_retry = false
|
@@ -341,11 +377,12 @@ class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
|
|
341
377
|
end
|
342
378
|
end
|
343
379
|
|
344
|
-
#
|
380
|
+
# otherwise not working in Ruby 1.9.1
|
345
381
|
if RUBY_VERSION =~ /^1\.9/
|
346
|
-
def describe(name)
|
382
|
+
def describe(name) #:nodoc:
|
347
383
|
@connection.describe(name)
|
348
384
|
end
|
349
385
|
end
|
350
386
|
|
351
387
|
end
|
388
|
+
#:startdoc:
|