rsim-activerecord-oracle_enhanced-adapter 1.1.9.90 → 1.1.9.91
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/Manifest.txt +4 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +71 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +345 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +322 -0
- data/oracle-enhanced.gemspec +5 -1
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +166 -0
- metadata +5 -1
data/Manifest.txt
CHANGED
@@ -4,13 +4,17 @@ README.txt
|
|
4
4
|
lib/active_record/connection_adapters/emulation/oracle_adapter.rb
|
5
5
|
lib/active_record/connection_adapters/oracle_enhanced.rake
|
6
6
|
lib/active_record/connection_adapters/oracle_enhanced_adapter.rb
|
7
|
+
lib/active_record/connection_adapters/oracle_enhanced_connection.rb
|
7
8
|
lib/active_record/connection_adapters/oracle_enhanced_cpk.rb
|
8
9
|
lib/active_record/connection_adapters/oracle_enhanced_dirty.rb
|
10
|
+
lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb
|
11
|
+
lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb
|
9
12
|
lib/active_record/connection_adapters/oracle_enhanced_procedures.rb
|
10
13
|
lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb
|
11
14
|
lib/active_record/connection_adapters/oracle_enhanced_tasks.rb
|
12
15
|
lib/active_record/connection_adapters/oracle_enhanced_version.rb
|
13
16
|
spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb
|
17
|
+
spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb
|
14
18
|
spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb
|
15
19
|
spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb
|
16
20
|
spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
# interface independent methods
|
4
|
+
class OracleEnhancedConnection
|
5
|
+
|
6
|
+
def self.create(config)
|
7
|
+
case ORACLE_ENHANCED_CONNECTION
|
8
|
+
when :oci
|
9
|
+
OracleEnhancedOCIConnection.new(config)
|
10
|
+
when :jdbc
|
11
|
+
OracleEnhancedJDBCConnection.new(config)
|
12
|
+
else
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :raw_connection
|
18
|
+
|
19
|
+
# Oracle column names by default are case-insensitive, but treated as upcase;
|
20
|
+
# for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
|
21
|
+
# their column names when creating Oracle tables, which makes then case-sensitive.
|
22
|
+
# I don't know anybody who does this, but we'll handle the theoretical case of a
|
23
|
+
# camelCase column name. I imagine other dbs handle this different, since there's a
|
24
|
+
# unit test that's currently failing test_oci.
|
25
|
+
def oracle_downcase(column_name)
|
26
|
+
column_name =~ /[a-z]/ ? column_name : column_name.downcase
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Returns a record hash with the column names as keys and column values
|
32
|
+
# as values.
|
33
|
+
def select_one(sql)
|
34
|
+
result = select(sql)
|
35
|
+
result.first if result
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns a single value from a record
|
39
|
+
def select_value(sql)
|
40
|
+
if result = select_one(sql)
|
41
|
+
result.values.first
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns an array of the values of the first column in a select:
|
46
|
+
# select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]
|
47
|
+
def select_values(sql)
|
48
|
+
result = select(sql)
|
49
|
+
result.map { |r| r.values.first }
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class OracleEnhancedConnectionException < StandardError
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# if MRI or YARV
|
62
|
+
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby'
|
63
|
+
ORACLE_ENHANCED_CONNECTION = :oci
|
64
|
+
require 'active_record/connection_adapters/oracle_enhanced_oci_connection'
|
65
|
+
# if JRuby
|
66
|
+
elsif RUBY_ENGINE == 'jruby'
|
67
|
+
ORACLE_ENHANCED_CONNECTION = :jdbc
|
68
|
+
require 'active_record/connection_adapters/oracle_enhanced_jdbc_connection'
|
69
|
+
else
|
70
|
+
raise "Unsupported Ruby engine #{RUBY_ENGINE}"
|
71
|
+
end
|
@@ -0,0 +1,345 @@
|
|
1
|
+
begin
|
2
|
+
require "java"
|
3
|
+
require "jruby"
|
4
|
+
# Adds JRuby classloader to current thread classloader - as a result ojdbc14.jar should not be in $JRUBY_HOME/lib
|
5
|
+
java.lang.Thread.currentThread.setContextClassLoader(JRuby.runtime.jruby_class_loader)
|
6
|
+
|
7
|
+
ojdbc_jar = "ojdbc14.jar"
|
8
|
+
if ojdbc_jar_path = ENV["PATH"].split(/[:;]/).find{|d| File.exists?(File.join(d,ojdbc_jar))}
|
9
|
+
require File.join(ojdbc_jar_path,ojdbc_jar)
|
10
|
+
end
|
11
|
+
# import java.sql.Statement
|
12
|
+
# import java.sql.Connection
|
13
|
+
# import java.sql.SQLException
|
14
|
+
# import java.sql.Types
|
15
|
+
# import java.sql.DriverManager
|
16
|
+
java.sql.DriverManager.registerDriver Java::oracle.jdbc.driver.OracleDriver.new
|
17
|
+
|
18
|
+
rescue LoadError, NameError
|
19
|
+
# JDBC driver is unavailable.
|
20
|
+
error_message = "ERROR: ActiveRecord oracle_enhanced adapter could not load Oracle JDBC driver. "+
|
21
|
+
"Please install ojdbc14.jar library."
|
22
|
+
if defined?(RAILS_DEFAULT_LOGGER)
|
23
|
+
RAILS_DEFAULT_LOGGER.error error_message
|
24
|
+
else
|
25
|
+
STDERR.puts error_message
|
26
|
+
end
|
27
|
+
raise LoadError
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
module ActiveRecord
|
32
|
+
module ConnectionAdapters
|
33
|
+
|
34
|
+
# JDBC database interface for JRuby
|
35
|
+
class OracleEnhancedJDBCConnection < OracleEnhancedConnection
|
36
|
+
|
37
|
+
attr_accessor :active
|
38
|
+
alias :active? :active
|
39
|
+
|
40
|
+
attr_accessor :auto_retry
|
41
|
+
alias :auto_retry? :auto_retry
|
42
|
+
@auto_retry = false
|
43
|
+
|
44
|
+
def initialize(config)
|
45
|
+
@active = true
|
46
|
+
@config = config
|
47
|
+
new_connection(@config)
|
48
|
+
end
|
49
|
+
|
50
|
+
def new_connection(config)
|
51
|
+
username, password, database = config[:username].to_s, config[:password].to_s, config[:database].to_s
|
52
|
+
privilege = config[:privilege] && config[:privilege].to_s
|
53
|
+
host, port = config[:host], config[:port]
|
54
|
+
|
55
|
+
url = config[:url] || "jdbc:oracle:thin:@#{host || 'localhost'}:#{port || 1521}:#{database || 'XE'}"
|
56
|
+
|
57
|
+
prefetch_rows = config[:prefetch_rows] || 100
|
58
|
+
cursor_sharing = config[:cursor_sharing] || 'similar'
|
59
|
+
|
60
|
+
properties = java.util.Properties.new
|
61
|
+
properties.put("user", username)
|
62
|
+
properties.put("password", password)
|
63
|
+
properties.put("defaultRowPrefetch", "#{prefetch_rows}") if prefetch_rows
|
64
|
+
properties.put("internal_logon", privilege) if privilege
|
65
|
+
|
66
|
+
@raw_connection = java.sql.DriverManager.getConnection(url, properties)
|
67
|
+
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'} # rescue nil
|
69
|
+
exec "alter session set cursor_sharing = #{cursor_sharing}" # rescue nil
|
70
|
+
self.autocommit = true
|
71
|
+
|
72
|
+
# Set session time zone to current time zone
|
73
|
+
@raw_connection.setSessionTimeZone(java.util.TimeZone.default.getID)
|
74
|
+
|
75
|
+
# default schema owner
|
76
|
+
@owner = username.upcase
|
77
|
+
|
78
|
+
@raw_connection
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def logoff
|
83
|
+
@active = false
|
84
|
+
@raw_connection.close
|
85
|
+
true
|
86
|
+
rescue
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
def commit
|
91
|
+
@raw_connection.commit
|
92
|
+
end
|
93
|
+
|
94
|
+
def rollback
|
95
|
+
@raw_connection.rollback
|
96
|
+
end
|
97
|
+
|
98
|
+
def autocommit?
|
99
|
+
@raw_connection.getAutoCommit
|
100
|
+
end
|
101
|
+
|
102
|
+
def autocommit=(value)
|
103
|
+
@raw_connection.setAutoCommit(value)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Checks connection, returns true if active. Note that ping actively
|
107
|
+
# checks the connection, while #active? simply returns the last
|
108
|
+
# known state.
|
109
|
+
def ping
|
110
|
+
exec_no_retry("select 1 from dual")
|
111
|
+
@active = true
|
112
|
+
rescue NativeException => e
|
113
|
+
@active = false
|
114
|
+
if e.message =~ /^java\.sql\.SQLException/
|
115
|
+
raise OracleEnhancedConnectionException, e.message
|
116
|
+
else
|
117
|
+
raise
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Resets connection, by logging off and creating a new connection.
|
122
|
+
def reset!
|
123
|
+
logoff rescue nil
|
124
|
+
begin
|
125
|
+
new_connection(@config)
|
126
|
+
@active = true
|
127
|
+
rescue NativeException => e
|
128
|
+
@active = false
|
129
|
+
if e.message =~ /^java\.sql\.SQLException/
|
130
|
+
raise OracleEnhancedConnectionException, e.message
|
131
|
+
else
|
132
|
+
raise
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# mark connection as dead if connection lost
|
138
|
+
def with_retry(&block)
|
139
|
+
should_retry = auto_retry? && autocommit?
|
140
|
+
begin
|
141
|
+
yield if block_given?
|
142
|
+
rescue NativeException => e
|
143
|
+
raise unless e.message =~ /^java\.sql\.SQLException: (Closed Connection|Io exception:|No more data to read from socket)/
|
144
|
+
@active = false
|
145
|
+
raise unless should_retry
|
146
|
+
should_retry = false
|
147
|
+
reset! rescue nil
|
148
|
+
retry
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def exec(sql)
|
153
|
+
with_retry do
|
154
|
+
exec_no_retry(sql)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def exec_no_retry(sql)
|
159
|
+
cs = prepare_call(sql)
|
160
|
+
cs.execute
|
161
|
+
true
|
162
|
+
ensure
|
163
|
+
cs.close rescue nil
|
164
|
+
end
|
165
|
+
|
166
|
+
def select(sql, name = nil)
|
167
|
+
with_retry do
|
168
|
+
select_no_retry(sql, name)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def select_no_retry(sql, name = nil)
|
173
|
+
stmt = prepare_statement(sql)
|
174
|
+
rset = stmt.executeQuery
|
175
|
+
metadata = rset.getMetaData
|
176
|
+
column_count = metadata.getColumnCount
|
177
|
+
cols = (1..column_count).map do |i|
|
178
|
+
oracle_downcase(metadata.getColumnName(i))
|
179
|
+
end
|
180
|
+
col_types = (1..column_count).map do |i|
|
181
|
+
metadata.getColumnTypeName(i)
|
182
|
+
end
|
183
|
+
|
184
|
+
rows = []
|
185
|
+
|
186
|
+
while rset.next
|
187
|
+
hash = Hash.new
|
188
|
+
|
189
|
+
cols.each_with_index do |col, i0|
|
190
|
+
i = i0 + 1
|
191
|
+
hash[col] =
|
192
|
+
case column_type = col_types[i0]
|
193
|
+
when /CLOB/
|
194
|
+
name == 'Writable Large Object' ? rset.getClob(i) : get_ruby_value_from_result_set(rset, i, column_type)
|
195
|
+
when /BLOB/
|
196
|
+
name == 'Writable Large Object' ? rset.getBlob(i) : get_ruby_value_from_result_set(rset, i, column_type)
|
197
|
+
when 'DATE'
|
198
|
+
t = get_ruby_value_from_result_set(rset, i, column_type)
|
199
|
+
# RSI: added emulate_dates_by_column_name functionality
|
200
|
+
# if emulate_dates_by_column_name && self.class.is_date_column?(col)
|
201
|
+
# d.to_date
|
202
|
+
# elsif
|
203
|
+
if t && OracleEnhancedAdapter.emulate_dates && (t.hour == 0 && t.min == 0 && t.sec == 0)
|
204
|
+
t.to_date
|
205
|
+
else
|
206
|
+
# JRuby Time supports time before year 1900 therefore now need to fall back to DateTime
|
207
|
+
t
|
208
|
+
end
|
209
|
+
# RSI: added emulate_integers_by_column_name functionality
|
210
|
+
when "NUMBER"
|
211
|
+
n = get_ruby_value_from_result_set(rset, i, column_type)
|
212
|
+
if n && n.is_a?(Float) && OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(col)
|
213
|
+
n.to_i
|
214
|
+
else
|
215
|
+
n
|
216
|
+
end
|
217
|
+
else
|
218
|
+
get_ruby_value_from_result_set(rset, i, column_type)
|
219
|
+
end unless col == 'raw_rnum_'
|
220
|
+
end
|
221
|
+
|
222
|
+
rows << hash
|
223
|
+
end
|
224
|
+
|
225
|
+
rows
|
226
|
+
ensure
|
227
|
+
rset.close rescue nil
|
228
|
+
stmt.close rescue nil
|
229
|
+
end
|
230
|
+
|
231
|
+
def write_lob(lob, value, is_binary = false)
|
232
|
+
if is_binary
|
233
|
+
lob.setBytes(1, value.to_java_bytes)
|
234
|
+
else
|
235
|
+
lob.setString(1,value)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def describe(name)
|
240
|
+
real_name = OracleEnhancedAdapter.valid_table_name?(name) ? name.upcase : name
|
241
|
+
if name.include?('.')
|
242
|
+
table_owner, table_name = real_name.split('.')
|
243
|
+
else
|
244
|
+
table_owner, table_name = @owner, real_name
|
245
|
+
end
|
246
|
+
sql = <<-SQL
|
247
|
+
SELECT owner, table_name, 'TABLE' name_type
|
248
|
+
FROM all_tables
|
249
|
+
WHERE owner = '#{table_owner}'
|
250
|
+
AND table_name = '#{table_name}'
|
251
|
+
UNION ALL
|
252
|
+
SELECT owner, view_name table_name, 'VIEW' name_type
|
253
|
+
FROM all_views
|
254
|
+
WHERE owner = '#{table_owner}'
|
255
|
+
AND view_name = '#{table_name}'
|
256
|
+
UNION ALL
|
257
|
+
SELECT table_owner, table_name, 'SYNONYM' name_type
|
258
|
+
FROM all_synonyms
|
259
|
+
WHERE owner = '#{table_owner}'
|
260
|
+
AND synonym_name = '#{table_name}'
|
261
|
+
UNION ALL
|
262
|
+
SELECT table_owner, table_name, 'SYNONYM' name_type
|
263
|
+
FROM all_synonyms
|
264
|
+
WHERE owner = 'PUBLIC'
|
265
|
+
AND synonym_name = '#{name.upcase}'
|
266
|
+
SQL
|
267
|
+
if result = select_one(sql)
|
268
|
+
case result['name_type']
|
269
|
+
when 'SYNONYM'
|
270
|
+
describe("#{result['owner']}.#{result['table_name']}")
|
271
|
+
else
|
272
|
+
[result['owner'], result['table_name']]
|
273
|
+
end
|
274
|
+
else
|
275
|
+
raise OracleEnhancedConnectionException, %Q{"DESC #{name}" failed; does it exist?}
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
private
|
281
|
+
|
282
|
+
def prepare_statement(sql)
|
283
|
+
@raw_connection.prepareStatement(sql)
|
284
|
+
end
|
285
|
+
|
286
|
+
def prepare_call(sql, *bindvars)
|
287
|
+
@raw_connection.prepareCall(sql)
|
288
|
+
end
|
289
|
+
|
290
|
+
def get_ruby_value_from_result_set(rset, i, type_name)
|
291
|
+
case type_name
|
292
|
+
when "CHAR", "VARCHAR2"
|
293
|
+
rset.getString(i)
|
294
|
+
when "CLOB"
|
295
|
+
ora_value_to_ruby_value(rset.getClob(i))
|
296
|
+
when "BLOB"
|
297
|
+
ora_value_to_ruby_value(rset.getBlob(i))
|
298
|
+
when "NUMBER"
|
299
|
+
d = rset.getBigDecimal(i)
|
300
|
+
if d.nil?
|
301
|
+
nil
|
302
|
+
elsif d.scale == 0
|
303
|
+
d.longValue
|
304
|
+
else
|
305
|
+
d.doubleValue
|
306
|
+
end
|
307
|
+
when "DATE", /^TIMESTAMP/
|
308
|
+
ts = rset.getTimestamp(i)
|
309
|
+
ts && Time.at(ts.getTime/1000)
|
310
|
+
else
|
311
|
+
nil
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def ora_value_to_ruby_value(val)
|
316
|
+
case val
|
317
|
+
when Float, BigDecimal
|
318
|
+
ora_number_to_ruby_number(val)
|
319
|
+
when ::Java::OracleSql::CLOB
|
320
|
+
if val.isEmptyLob
|
321
|
+
nil
|
322
|
+
else
|
323
|
+
val.getSubString(1, val.length)
|
324
|
+
end
|
325
|
+
when ::Java::OracleSql::BLOB
|
326
|
+
if val.isEmptyLob
|
327
|
+
nil
|
328
|
+
else
|
329
|
+
String.from_java_bytes(val.getBytes(1, val.length))
|
330
|
+
end
|
331
|
+
else
|
332
|
+
val
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
def ora_number_to_ruby_number(num)
|
337
|
+
num.to_i == num.to_f ? num.to_i : num.to_f
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
@@ -0,0 +1,322 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'oci8' unless self.class.const_defined? :OCI8
|
5
|
+
|
6
|
+
# RSI: added mapping for TIMESTAMP / WITH TIME ZONE / LOCAL TIME ZONE types
|
7
|
+
# currently Ruby-OCI8 does not support fractional seconds for timestamps
|
8
|
+
OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP] = OCI8::BindType::OraDate
|
9
|
+
OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_TZ] = OCI8::BindType::OraDate
|
10
|
+
OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_LTZ] = OCI8::BindType::OraDate
|
11
|
+
rescue LoadError
|
12
|
+
# OCI8 driver is unavailable.
|
13
|
+
error_message = "ERROR: ActiveRecord oracle_enhanced adapter could not load ruby-oci8 library. "+
|
14
|
+
"Please install ruby-oci8 library or gem."
|
15
|
+
if defined?(RAILS_DEFAULT_LOGGER)
|
16
|
+
RAILS_DEFAULT_LOGGER.error error_message
|
17
|
+
else
|
18
|
+
STDERR.puts error_message
|
19
|
+
end
|
20
|
+
raise LoadError
|
21
|
+
end
|
22
|
+
|
23
|
+
module ActiveRecord
|
24
|
+
module ConnectionAdapters
|
25
|
+
|
26
|
+
# OCI database interface for MRI
|
27
|
+
class OracleEnhancedOCIConnection < OracleEnhancedConnection
|
28
|
+
|
29
|
+
def initialize(config)
|
30
|
+
@raw_connection = OCI8EnhancedAutoRecover.new(config, OracleEnhancedOCIFactory)
|
31
|
+
end
|
32
|
+
|
33
|
+
def auto_retry
|
34
|
+
@raw_connection.auto_retry if @raw_connection
|
35
|
+
end
|
36
|
+
|
37
|
+
def auto_retry=(value)
|
38
|
+
@raw_connection.auto_retry = value if @raw_connection
|
39
|
+
end
|
40
|
+
|
41
|
+
def logoff
|
42
|
+
@raw_connection.logoff
|
43
|
+
@raw_connection.active = false
|
44
|
+
end
|
45
|
+
|
46
|
+
def commit
|
47
|
+
@raw_connection.commit
|
48
|
+
end
|
49
|
+
|
50
|
+
def rollback
|
51
|
+
@raw_connection.rollback
|
52
|
+
end
|
53
|
+
|
54
|
+
def autocommit?
|
55
|
+
@raw_connection.autocommit?
|
56
|
+
end
|
57
|
+
|
58
|
+
def autocommit=(value)
|
59
|
+
@raw_connection.autocommit = value
|
60
|
+
end
|
61
|
+
|
62
|
+
# Checks connection, returns true if active. Note that ping actively
|
63
|
+
# checks the connection, while #active? simply returns the last
|
64
|
+
# known state.
|
65
|
+
def ping
|
66
|
+
@raw_connection.ping
|
67
|
+
rescue OCIException => e
|
68
|
+
raise OracleEnhancedConnectionException, e.message
|
69
|
+
end
|
70
|
+
|
71
|
+
def active?
|
72
|
+
@raw_connection.active?
|
73
|
+
end
|
74
|
+
|
75
|
+
def reset!
|
76
|
+
@raw_connection.reset!
|
77
|
+
rescue OCIException => e
|
78
|
+
raise OracleEnhancedConnectionException, e.message
|
79
|
+
end
|
80
|
+
|
81
|
+
def exec(sql, *bindvars, &block)
|
82
|
+
@raw_connection.exec(sql, *bindvars, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def select(sql, name = nil)
|
86
|
+
cursor = @raw_connection.exec(sql)
|
87
|
+
cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
|
88
|
+
rows = []
|
89
|
+
|
90
|
+
while row = cursor.fetch
|
91
|
+
hash = Hash.new
|
92
|
+
|
93
|
+
cols.each_with_index do |col, i|
|
94
|
+
hash[col] =
|
95
|
+
case row[i]
|
96
|
+
when OCI8::LOB
|
97
|
+
name == 'Writable Large Object' ? row[i]: row[i].read
|
98
|
+
when OraDate
|
99
|
+
d = row[i]
|
100
|
+
# RSI: added emulate_dates_by_column_name functionality
|
101
|
+
# if emulate_dates_by_column_name && self.class.is_date_column?(col)
|
102
|
+
# d.to_date
|
103
|
+
# elsif
|
104
|
+
if OracleEnhancedAdapter.emulate_dates && (d.hour == 0 && d.minute == 0 && d.second == 0)
|
105
|
+
d.to_date
|
106
|
+
else
|
107
|
+
# see string_to_time; Time overflowing to DateTime, respecting the default timezone
|
108
|
+
time_array = [d.year, d.month, d.day, d.hour, d.minute, d.second]
|
109
|
+
begin
|
110
|
+
Time.send(Base.default_timezone, *time_array)
|
111
|
+
rescue
|
112
|
+
zone_offset = if Base.default_timezone == :local then DateTime.now.offset else 0 end
|
113
|
+
# Append zero calendar reform start to account for dates skipped by calendar reform
|
114
|
+
DateTime.new(*time_array[0..5] << zone_offset << 0) rescue nil
|
115
|
+
end
|
116
|
+
end
|
117
|
+
# RSI: added emulate_integers_by_column_name functionality
|
118
|
+
when Float
|
119
|
+
n = row[i]
|
120
|
+
if OracleEnhancedAdapter.emulate_integers_by_column_name && OracleEnhancedAdapter.is_integer_column?(col)
|
121
|
+
n.to_i
|
122
|
+
else
|
123
|
+
n
|
124
|
+
end
|
125
|
+
else row[i]
|
126
|
+
end unless col == 'raw_rnum_'
|
127
|
+
end
|
128
|
+
|
129
|
+
rows << hash
|
130
|
+
end
|
131
|
+
|
132
|
+
rows
|
133
|
+
ensure
|
134
|
+
cursor.close if cursor
|
135
|
+
end
|
136
|
+
|
137
|
+
def write_lob(lob, value, is_binary = false)
|
138
|
+
lob.write value
|
139
|
+
end
|
140
|
+
|
141
|
+
def describe(name)
|
142
|
+
quoted_name = OracleEnhancedAdapter.valid_table_name?(name) ? name : "\"#{name}\""
|
143
|
+
@raw_connection.describe(quoted_name)
|
144
|
+
rescue OCIException => e
|
145
|
+
raise OracleEnhancedConnectionException, e.message
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
# The OracleEnhancedOCIFactory factors out the code necessary to connect and
|
151
|
+
# configure an Oracle/OCI connection.
|
152
|
+
class OracleEnhancedOCIFactory #:nodoc:
|
153
|
+
def self.new_connection(config)
|
154
|
+
username, password, database = config[:username].to_s, config[:password].to_s, config[:database].to_s
|
155
|
+
privilege = config[:privilege] && config[:privilege].to_sym
|
156
|
+
async = config[:allow_concurrency]
|
157
|
+
prefetch_rows = config[:prefetch_rows] || 100
|
158
|
+
cursor_sharing = config[:cursor_sharing] || 'similar'
|
159
|
+
|
160
|
+
conn = OCI8.new username, password, database, privilege
|
161
|
+
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
|
162
|
+
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
|
163
|
+
conn.autocommit = true
|
164
|
+
conn.non_blocking = true if async
|
165
|
+
conn.prefetch_rows = prefetch_rows
|
166
|
+
conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
|
167
|
+
conn
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
class OCI8 #:nodoc:
|
178
|
+
|
179
|
+
class Cursor #:nodoc:
|
180
|
+
if method_defined? :define_a_column
|
181
|
+
# This OCI8 patch is required with the ruby-oci8 1.0.x or lower.
|
182
|
+
# Set OCI8::BindType::Mapping[] to change the column type
|
183
|
+
# when using ruby-oci8 2.0.
|
184
|
+
|
185
|
+
alias :enhanced_define_a_column_pre_ar :define_a_column
|
186
|
+
def define_a_column(i)
|
187
|
+
case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
|
188
|
+
when 8; @stmt.defineByPos(i, String, 65535) # Read LONG values
|
189
|
+
when 187; @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
|
190
|
+
when 108
|
191
|
+
if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
|
192
|
+
@stmt.defineByPos(i, String, 65535)
|
193
|
+
else
|
194
|
+
raise 'unsupported datatype'
|
195
|
+
end
|
196
|
+
else enhanced_define_a_column_pre_ar i
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
if OCI8.public_method_defined?(:describe_table)
|
203
|
+
# ruby-oci8 2.0 or upper
|
204
|
+
|
205
|
+
def describe(name)
|
206
|
+
info = describe_table(name.to_s)
|
207
|
+
raise %Q{"DESC #{name}" failed} if info.nil?
|
208
|
+
[info.obj_schema, info.obj_name]
|
209
|
+
end
|
210
|
+
else
|
211
|
+
# ruby-oci8 1.0.x or lower
|
212
|
+
|
213
|
+
# missing constant from oci8 < 0.1.14
|
214
|
+
OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
|
215
|
+
|
216
|
+
# Uses the describeAny OCI call to find the target owner and table_name
|
217
|
+
# indicated by +name+, parsing through synonynms as necessary. Returns
|
218
|
+
# an array of [owner, table_name].
|
219
|
+
def describe(name)
|
220
|
+
@desc ||= @@env.alloc(OCIDescribe)
|
221
|
+
@desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
|
222
|
+
do_ocicall(@ctx) { @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) } rescue raise %Q{"DESC #{name}" failed; does it exist?}
|
223
|
+
info = @desc.attrGet(OCI_ATTR_PARAM)
|
224
|
+
|
225
|
+
case info.attrGet(OCI_ATTR_PTYPE)
|
226
|
+
when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
|
227
|
+
owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
|
228
|
+
table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
|
229
|
+
[owner, table_name]
|
230
|
+
when OCI_PTYPE_SYN
|
231
|
+
schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
|
232
|
+
name = info.attrGet(OCI_ATTR_NAME)
|
233
|
+
describe(schema + '.' + name)
|
234
|
+
else raise %Q{"DESC #{name}" failed; not a table or view.}
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
# The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
|
242
|
+
# reset functionality. If a call to #exec fails, and autocommit is turned on
|
243
|
+
# (ie., we're not in the middle of a longer transaction), it will
|
244
|
+
# automatically reconnect and try again. If autocommit is turned off,
|
245
|
+
# this would be dangerous (as the earlier part of the implied transaction
|
246
|
+
# may have failed silently if the connection died) -- so instead the
|
247
|
+
# connection is marked as dead, to be reconnected on it's next use.
|
248
|
+
class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
|
249
|
+
attr_accessor :active
|
250
|
+
alias :active? :active
|
251
|
+
|
252
|
+
cattr_accessor :auto_retry
|
253
|
+
class << self
|
254
|
+
alias :auto_retry? :auto_retry
|
255
|
+
end
|
256
|
+
@@auto_retry = false
|
257
|
+
|
258
|
+
def initialize(config, factory)
|
259
|
+
@active = true
|
260
|
+
@config = config
|
261
|
+
@factory = factory
|
262
|
+
@connection = @factory.new_connection @config
|
263
|
+
super @connection
|
264
|
+
end
|
265
|
+
|
266
|
+
# Checks connection, returns true if active. Note that ping actively
|
267
|
+
# checks the connection, while #active? simply returns the last
|
268
|
+
# known state.
|
269
|
+
def ping
|
270
|
+
@connection.exec("select 1 from dual") { |r| nil }
|
271
|
+
@active = true
|
272
|
+
rescue
|
273
|
+
@active = false
|
274
|
+
raise
|
275
|
+
end
|
276
|
+
|
277
|
+
# Resets connection, by logging off and creating a new connection.
|
278
|
+
def reset!
|
279
|
+
logoff rescue nil
|
280
|
+
begin
|
281
|
+
@connection = @factory.new_connection @config
|
282
|
+
__setobj__ @connection
|
283
|
+
@active = true
|
284
|
+
rescue
|
285
|
+
@active = false
|
286
|
+
raise
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# ORA-00028: your session has been killed
|
291
|
+
# ORA-01012: not logged on
|
292
|
+
# ORA-03113: end-of-file on communication channel
|
293
|
+
# ORA-03114: not connected to ORACLE
|
294
|
+
# ORA-03135: connection lost contact
|
295
|
+
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ]
|
296
|
+
|
297
|
+
# Adds auto-recovery functionality.
|
298
|
+
#
|
299
|
+
# See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
|
300
|
+
def exec(sql, *bindvars, &block)
|
301
|
+
should_retry = self.class.auto_retry? && autocommit?
|
302
|
+
|
303
|
+
begin
|
304
|
+
@connection.exec(sql, *bindvars, &block)
|
305
|
+
rescue OCIException => e
|
306
|
+
raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
|
307
|
+
@active = false
|
308
|
+
raise unless should_retry
|
309
|
+
should_retry = false
|
310
|
+
reset! rescue nil
|
311
|
+
retry
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
# RSI: otherwise not working in Ruby 1.9.1
|
316
|
+
if RUBY_VERSION =~ /^1\.9/
|
317
|
+
def describe(name)
|
318
|
+
@connection.describe(name)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
data/oracle-enhanced.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{activerecord-oracle_enhanced-adapter}
|
5
|
-
s.version = "1.1.9.
|
5
|
+
s.version = "1.1.9.91"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Raimonds Simanovskis"]
|
@@ -14,14 +14,18 @@ Gem::Specification.new do |s|
|
|
14
14
|
"lib/active_record/connection_adapters/emulation/oracle_adapter.rb",
|
15
15
|
"lib/active_record/connection_adapters/oracle_enhanced.rake",
|
16
16
|
"lib/active_record/connection_adapters/oracle_enhanced_adapter.rb",
|
17
|
+
"lib/active_record/connection_adapters/oracle_enhanced_connection.rb",
|
17
18
|
"lib/active_record/connection_adapters/oracle_enhanced_cpk.rb",
|
18
19
|
"lib/active_record/connection_adapters/oracle_enhanced_dirty.rb",
|
20
|
+
"lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb",
|
21
|
+
"lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb",
|
19
22
|
"lib/active_record/connection_adapters/oracle_enhanced_procedures.rb",
|
20
23
|
"lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb",
|
21
24
|
"lib/active_record/connection_adapters/oracle_enhanced_tasks.rb",
|
22
25
|
"lib/active_record/connection_adapters/oracle_enhanced_version.rb",
|
23
26
|
"oracle-enhanced.gemspec",
|
24
27
|
"spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb",
|
28
|
+
"spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb",
|
25
29
|
"spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb",
|
26
30
|
"spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb",
|
27
31
|
"spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb",
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper.rb'
|
2
|
+
|
3
|
+
describe "OracleEnhancedConnection create connection" do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
@conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
|
7
|
+
end
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS) unless @conn.active?
|
11
|
+
end
|
12
|
+
|
13
|
+
after(:all) do
|
14
|
+
@conn.logoff if @conn.active?
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create new connection" do
|
18
|
+
@conn.should be_active
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should ping active connection" do
|
22
|
+
@conn.ping.should be_true
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not ping inactive connection" do
|
26
|
+
@conn.logoff
|
27
|
+
lambda { @conn.ping }.should raise_error(ActiveRecord::ConnectionAdapters::OracleEnhancedConnectionException)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should reset active connection" do
|
31
|
+
@conn.reset!
|
32
|
+
@conn.should be_active
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be in autocommit mode after connection" do
|
36
|
+
@conn.should be_autocommit
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "OracleEnhancedConnection SQL execution" do
|
42
|
+
|
43
|
+
before(:all) do
|
44
|
+
@conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
|
45
|
+
end
|
46
|
+
|
47
|
+
before(:each) do
|
48
|
+
@conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS) unless @conn.active?
|
49
|
+
end
|
50
|
+
|
51
|
+
after(:all) do
|
52
|
+
@conn.logoff if @conn.active?
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should execute SQL statement" do
|
56
|
+
@conn.exec("SELECT * FROM dual").should_not be_nil
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should execute SQL select" do
|
60
|
+
@conn.select("SELECT * FROM dual").should == [{'dummy' => 'X'}]
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "OracleEnhancedConnection auto reconnection" do
|
66
|
+
|
67
|
+
before(:all) do
|
68
|
+
ActiveRecord::Base.establish_connection(CONNECTION_PARAMS)
|
69
|
+
@conn = ActiveRecord::Base.connection.instance_variable_get("@connection")
|
70
|
+
@sys_conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(SYS_CONNECTION_PARAMS)
|
71
|
+
end
|
72
|
+
|
73
|
+
before(:each) do
|
74
|
+
ActiveRecord::Base.connection.reconnect! unless @conn.active?
|
75
|
+
end
|
76
|
+
|
77
|
+
after(:all) do
|
78
|
+
ActiveRecord::Base.connection.disconnect! if @conn.active?
|
79
|
+
end
|
80
|
+
|
81
|
+
def kill_current_session
|
82
|
+
audsid = @conn.select("SELECT userenv('sessionid') audsid FROM dual").first['audsid']
|
83
|
+
sid_serial = @sys_conn.select("SELECT s.sid||','||s.serial# sid_serial
|
84
|
+
FROM v$session s
|
85
|
+
WHERE audsid = '#{audsid}'").first['sid_serial']
|
86
|
+
@sys_conn.exec "ALTER SYSTEM KILL SESSION '#{sid_serial}' IMMEDIATE"
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should reconnect and execute SQL statement if connection is lost and auto retry is enabled" do
|
90
|
+
# @conn.auto_retry = true
|
91
|
+
ActiveRecord::Base.connection.auto_retry = true
|
92
|
+
kill_current_session
|
93
|
+
@conn.exec("SELECT * FROM dual").should_not be_nil
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should not reconnect and execute SQL statement if connection is lost and auto retry is disabled" do
|
97
|
+
# @conn.auto_retry = false
|
98
|
+
ActiveRecord::Base.connection.auto_retry = false
|
99
|
+
kill_current_session
|
100
|
+
lambda { @conn.exec("SELECT * FROM dual") }.should raise_error
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should reconnect and execute SQL select if connection is lost and auto retry is enabled" do
|
104
|
+
# @conn.auto_retry = true
|
105
|
+
ActiveRecord::Base.connection.auto_retry = true
|
106
|
+
kill_current_session
|
107
|
+
@conn.select("SELECT * FROM dual").should == [{'dummy' => 'X'}]
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should not reconnect and execute SQL select if connection is lost and auto retry is disabled" do
|
111
|
+
# @conn.auto_retry = false
|
112
|
+
ActiveRecord::Base.connection.auto_retry = false
|
113
|
+
kill_current_session
|
114
|
+
lambda { @conn.select("SELECT * FROM dual") }.should raise_error
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
describe "OracleEnhancedConnection describe table" do
|
120
|
+
|
121
|
+
before(:all) do
|
122
|
+
@conn = ActiveRecord::ConnectionAdapters::OracleEnhancedConnection.create(CONNECTION_PARAMS)
|
123
|
+
@owner = CONNECTION_PARAMS[:username].upcase
|
124
|
+
end
|
125
|
+
|
126
|
+
after(:all) do
|
127
|
+
@conn.logoff if @conn.active?
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should describe existing table" do
|
131
|
+
@conn.exec "CREATE TABLE test_employees (first_name VARCHAR2(20))" rescue nil
|
132
|
+
@conn.describe("test_employees").should == [@owner, "TEST_EMPLOYEES"]
|
133
|
+
@conn.exec "DROP TABLE test_employees" rescue nil
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should not describe non-existing table" do
|
137
|
+
lambda { @conn.describe("test_xxx") }.should raise_error(ActiveRecord::ConnectionAdapters::OracleEnhancedConnectionException)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should describe table in other schema" do
|
141
|
+
@conn.describe("sys.dual").should == ["SYS", "DUAL"]
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should describe existing view" do
|
145
|
+
@conn.exec "CREATE TABLE test_employees (first_name VARCHAR2(20))" rescue nil
|
146
|
+
@conn.exec "CREATE VIEW test_employees_v AS SELECT * FROM test_employees" rescue nil
|
147
|
+
@conn.describe("test_employees_v").should == [@owner, "TEST_EMPLOYEES_V"]
|
148
|
+
@conn.exec "DROP VIEW test_employees_v" rescue nil
|
149
|
+
@conn.exec "DROP TABLE test_employees" rescue nil
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should describe view in other schema" do
|
153
|
+
@conn.describe("sys.v_$version").should == ["SYS", "V_$VERSION"]
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should describe existing private synonym" do
|
157
|
+
@conn.exec "CREATE SYNONYM test_dual FOR sys.dual" rescue nil
|
158
|
+
@conn.describe("test_dual").should == ["SYS", "DUAL"]
|
159
|
+
@conn.exec "DROP SYNONYM test_dual" rescue nil
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should describe existing public synonym" do
|
163
|
+
@conn.describe("all_tables").should == ["SYS", "ALL_TABLES"]
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rsim-activerecord-oracle_enhanced-adapter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.9.
|
4
|
+
version: 1.1.9.91
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Raimonds Simanovskis
|
@@ -42,14 +42,18 @@ files:
|
|
42
42
|
- lib/active_record/connection_adapters/emulation/oracle_adapter.rb
|
43
43
|
- lib/active_record/connection_adapters/oracle_enhanced.rake
|
44
44
|
- lib/active_record/connection_adapters/oracle_enhanced_adapter.rb
|
45
|
+
- lib/active_record/connection_adapters/oracle_enhanced_connection.rb
|
45
46
|
- lib/active_record/connection_adapters/oracle_enhanced_cpk.rb
|
46
47
|
- lib/active_record/connection_adapters/oracle_enhanced_dirty.rb
|
48
|
+
- lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb
|
49
|
+
- lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb
|
47
50
|
- lib/active_record/connection_adapters/oracle_enhanced_procedures.rb
|
48
51
|
- lib/active_record/connection_adapters/oracle_enhanced_reserved_words.rb
|
49
52
|
- lib/active_record/connection_adapters/oracle_enhanced_tasks.rb
|
50
53
|
- lib/active_record/connection_adapters/oracle_enhanced_version.rb
|
51
54
|
- oracle-enhanced.gemspec
|
52
55
|
- spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb
|
56
|
+
- spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb
|
53
57
|
- spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb
|
54
58
|
- spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb
|
55
59
|
- spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb
|