activerecord-oracle_enhanced-adapter-with-schema 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rspec +2 -0
- data/Gemfile +52 -0
- data/History.md +301 -0
- data/License.txt +20 -0
- data/README.md +123 -0
- data/RUNNING_TESTS.md +45 -0
- data/Rakefile +59 -0
- data/VERSION +1 -0
- data/activerecord-oracle_enhanced-adapter-with-schema.gemspec +130 -0
- data/lib/active_record/connection_adapters/emulation/oracle_adapter.rb +5 -0
- data/lib/active_record/connection_adapters/oracle_enhanced.rake +105 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_activerecord_patches.rb +41 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_adapter.rb +1399 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_base_ext.rb +121 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_column.rb +146 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_connection.rb +119 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_context_index.rb +359 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_core_ext.rb +25 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_cpk.rb +21 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_dirty.rb +46 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_jdbc_connection.rb +565 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_oci_connection.rb +494 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_procedures.rb +260 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_definitions.rb +227 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_dumper.rb +260 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements.rb +428 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_schema_statements_ext.rb +258 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_structure_dump.rb +294 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_tasks.rb +17 -0
- data/lib/active_record/connection_adapters/oracle_enhanced_version.rb +1 -0
- data/lib/activerecord-oracle_enhanced-adapter-with-schema.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_adapter_spec.rb +778 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_connection_spec.rb +332 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_context_index_spec.rb +427 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_core_ext_spec.rb +19 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_cpk_spec.rb +113 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_data_types_spec.rb +1388 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dbms_output_spec.rb +69 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_dirty_spec.rb +141 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_emulate_oracle_adapter_spec.rb +25 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_procedures_spec.rb +378 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_dump_spec.rb +440 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_schema_statements_spec.rb +1385 -0
- data/spec/active_record/connection_adapters/oracle_enhanced_structure_dump_spec.rb +339 -0
- data/spec/spec_helper.rb +189 -0
- metadata +260 -0
@@ -0,0 +1,494 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "oci8"
|
5
|
+
rescue LoadError
|
6
|
+
# OCI8 driver is unavailable.
|
7
|
+
raise LoadError, "ERROR: ActiveRecord oracle_enhanced adapter could not load ruby-oci8 library. Please install ruby-oci8 gem."
|
8
|
+
end
|
9
|
+
|
10
|
+
# check ruby-oci8 version
|
11
|
+
required_oci8_version = [2, 0, 3]
|
12
|
+
oci8_version_ints = OCI8::VERSION.scan(/\d+/).map{|s| s.to_i}
|
13
|
+
if (oci8_version_ints <=> required_oci8_version) < 0
|
14
|
+
raise LoadError, "ERROR: ruby-oci8 version #{OCI8::VERSION} is too old. Please install ruby-oci8 version #{required_oci8_version.join('.')} or later."
|
15
|
+
end
|
16
|
+
|
17
|
+
module ActiveRecord
|
18
|
+
module ConnectionAdapters
|
19
|
+
|
20
|
+
# OCI database interface for MRI
|
21
|
+
class OracleEnhancedOCIConnection < OracleEnhancedConnection #:nodoc:
|
22
|
+
|
23
|
+
def initialize(config)
|
24
|
+
@raw_connection = OCI8EnhancedAutoRecover.new(config, OracleEnhancedOCIFactory)
|
25
|
+
# default schema owner
|
26
|
+
@owner = config[:username].to_s.upcase
|
27
|
+
end
|
28
|
+
|
29
|
+
def raw_oci_connection
|
30
|
+
if @raw_connection.is_a? OCI8
|
31
|
+
@raw_connection
|
32
|
+
# ActiveRecord Oracle enhanced adapter puts OCI8EnhancedAutoRecover wrapper around OCI8
|
33
|
+
# in this case we need to pass original OCI8 connection
|
34
|
+
else
|
35
|
+
@raw_connection.instance_variable_get(:@connection)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def auto_retry
|
40
|
+
@raw_connection.auto_retry if @raw_connection
|
41
|
+
end
|
42
|
+
|
43
|
+
def auto_retry=(value)
|
44
|
+
@raw_connection.auto_retry = value if @raw_connection
|
45
|
+
end
|
46
|
+
|
47
|
+
def logoff
|
48
|
+
@raw_connection.logoff
|
49
|
+
@raw_connection.active = false
|
50
|
+
end
|
51
|
+
|
52
|
+
def commit
|
53
|
+
@raw_connection.commit
|
54
|
+
end
|
55
|
+
|
56
|
+
def rollback
|
57
|
+
@raw_connection.rollback
|
58
|
+
end
|
59
|
+
|
60
|
+
def autocommit?
|
61
|
+
@raw_connection.autocommit?
|
62
|
+
end
|
63
|
+
|
64
|
+
def autocommit=(value)
|
65
|
+
@raw_connection.autocommit = value
|
66
|
+
end
|
67
|
+
|
68
|
+
# Checks connection, returns true if active. Note that ping actively
|
69
|
+
# checks the connection, while #active? simply returns the last
|
70
|
+
# known state.
|
71
|
+
def ping
|
72
|
+
@raw_connection.ping
|
73
|
+
rescue OCIException => e
|
74
|
+
raise OracleEnhancedConnectionException, e.message
|
75
|
+
end
|
76
|
+
|
77
|
+
def active?
|
78
|
+
@raw_connection.active?
|
79
|
+
end
|
80
|
+
|
81
|
+
def reset!
|
82
|
+
@raw_connection.reset!
|
83
|
+
rescue OCIException => e
|
84
|
+
raise OracleEnhancedConnectionException, e.message
|
85
|
+
end
|
86
|
+
|
87
|
+
def exec(sql, *bindvars, &block)
|
88
|
+
@raw_connection.exec(sql, *bindvars, &block)
|
89
|
+
end
|
90
|
+
|
91
|
+
def returning_clause(quoted_pk)
|
92
|
+
" RETURNING #{quoted_pk} INTO :insert_id"
|
93
|
+
end
|
94
|
+
|
95
|
+
# execute sql with RETURNING ... INTO :insert_id
|
96
|
+
# and return :insert_id value
|
97
|
+
def exec_with_returning(sql)
|
98
|
+
cursor = @raw_connection.parse(sql)
|
99
|
+
cursor.bind_param(':insert_id', nil, Integer)
|
100
|
+
cursor.exec
|
101
|
+
cursor[':insert_id']
|
102
|
+
ensure
|
103
|
+
cursor.close rescue nil
|
104
|
+
end
|
105
|
+
|
106
|
+
def prepare(sql)
|
107
|
+
Cursor.new(self, @raw_connection.parse(sql))
|
108
|
+
end
|
109
|
+
|
110
|
+
class Cursor
|
111
|
+
def initialize(connection, raw_cursor)
|
112
|
+
@connection = connection
|
113
|
+
@raw_cursor = raw_cursor
|
114
|
+
end
|
115
|
+
|
116
|
+
def bind_param(position, value, col_type = nil)
|
117
|
+
if value.nil?
|
118
|
+
@raw_cursor.bind_param(position, nil, String)
|
119
|
+
else
|
120
|
+
case col_type
|
121
|
+
when :text, :binary
|
122
|
+
# ruby-oci8 cannot create CLOB/BLOB from ''
|
123
|
+
lob_value = value == '' ? ' ' : value
|
124
|
+
bind_type = col_type == :text ? OCI8::CLOB : OCI8::BLOB
|
125
|
+
ora_value = bind_type.new(@connection.raw_oci_connection, lob_value)
|
126
|
+
ora_value.size = 0 if value == ''
|
127
|
+
@raw_cursor.bind_param(position, ora_value)
|
128
|
+
when :raw
|
129
|
+
@raw_cursor.bind_param(position, OracleEnhancedAdapter.encode_raw(value))
|
130
|
+
when :decimal
|
131
|
+
@raw_cursor.bind_param(position, BigDecimal.new(value.to_s))
|
132
|
+
else
|
133
|
+
@raw_cursor.bind_param(position, value)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def bind_returning_param(position, bind_type)
|
139
|
+
@raw_cursor.bind_param(position, nil, bind_type)
|
140
|
+
end
|
141
|
+
|
142
|
+
def exec
|
143
|
+
@raw_cursor.exec
|
144
|
+
end
|
145
|
+
|
146
|
+
def exec_update
|
147
|
+
@raw_cursor.exec
|
148
|
+
end
|
149
|
+
|
150
|
+
def get_col_names
|
151
|
+
@raw_cursor.get_col_names
|
152
|
+
end
|
153
|
+
|
154
|
+
def fetch(options={})
|
155
|
+
if row = @raw_cursor.fetch
|
156
|
+
get_lob_value = options[:get_lob_value]
|
157
|
+
row.map do |col|
|
158
|
+
@connection.typecast_result_value(col, get_lob_value)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_returning_param(position, type)
|
164
|
+
@raw_cursor[position]
|
165
|
+
end
|
166
|
+
|
167
|
+
def close
|
168
|
+
@raw_cursor.close
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
def select(sql, name = nil, return_column_names = false)
|
174
|
+
cursor = @raw_connection.exec(sql)
|
175
|
+
cols = []
|
176
|
+
# Ignore raw_rnum_ which is used to simulate LIMIT and OFFSET
|
177
|
+
cursor.get_col_names.each do |col_name|
|
178
|
+
col_name = oracle_downcase(col_name)
|
179
|
+
cols << col_name unless col_name == 'raw_rnum_'
|
180
|
+
end
|
181
|
+
# Reuse the same hash for all rows
|
182
|
+
column_hash = {}
|
183
|
+
cols.each {|c| column_hash[c] = nil}
|
184
|
+
rows = []
|
185
|
+
get_lob_value = !(name == 'Writable Large Object')
|
186
|
+
|
187
|
+
while row = cursor.fetch
|
188
|
+
hash = column_hash.dup
|
189
|
+
|
190
|
+
cols.each_with_index do |col, i|
|
191
|
+
hash[col] = typecast_result_value(row[i], get_lob_value)
|
192
|
+
end
|
193
|
+
|
194
|
+
rows << hash
|
195
|
+
end
|
196
|
+
|
197
|
+
return_column_names ? [rows, cols] : rows
|
198
|
+
ensure
|
199
|
+
cursor.close if cursor
|
200
|
+
end
|
201
|
+
|
202
|
+
def write_lob(lob, value, is_binary = false)
|
203
|
+
lob.write value
|
204
|
+
end
|
205
|
+
|
206
|
+
def describe(name)
|
207
|
+
# fall back to SELECT based describe if using database link
|
208
|
+
return super if name.to_s.include?('@')
|
209
|
+
quoted_name = OracleEnhancedAdapter.valid_table_name?(name) ? name : "\"#{name}\""
|
210
|
+
@raw_connection.describe(quoted_name)
|
211
|
+
rescue OCIException => e
|
212
|
+
# fall back to SELECT which can handle synonyms to database links
|
213
|
+
super
|
214
|
+
end
|
215
|
+
|
216
|
+
# Return OCIError error code
|
217
|
+
def error_code(exception)
|
218
|
+
case exception
|
219
|
+
when OCIError
|
220
|
+
exception.code
|
221
|
+
else
|
222
|
+
nil
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def typecast_result_value(value, get_lob_value)
|
227
|
+
case value
|
228
|
+
when Fixnum, Bignum
|
229
|
+
value
|
230
|
+
when String
|
231
|
+
value
|
232
|
+
when Float, BigDecimal
|
233
|
+
# return Fixnum or Bignum if value is integer (to avoid issues with _before_type_cast values for id attributes)
|
234
|
+
value == (v_to_i = value.to_i) ? v_to_i : value
|
235
|
+
when OraNumber
|
236
|
+
# change OraNumber value (returned in early versions of ruby-oci8 2.0.x) to BigDecimal
|
237
|
+
value == (v_to_i = value.to_i) ? v_to_i : BigDecimal.new(value.to_s)
|
238
|
+
when OCI8::LOB
|
239
|
+
if get_lob_value
|
240
|
+
data = value.read || "" # if value.read returns nil, then we have an empty_clob() i.e. an empty string
|
241
|
+
# In Ruby 1.9.1 always change encoding to ASCII-8BIT for binaries
|
242
|
+
data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) && value.is_a?(OCI8::BLOB)
|
243
|
+
data
|
244
|
+
else
|
245
|
+
value
|
246
|
+
end
|
247
|
+
# ruby-oci8 1.0 returns OraDate
|
248
|
+
# ruby-oci8 2.0 returns Time or DateTime
|
249
|
+
when OraDate, Time, DateTime
|
250
|
+
if OracleEnhancedAdapter.emulate_dates && date_without_time?(value)
|
251
|
+
value.to_date
|
252
|
+
else
|
253
|
+
create_time_with_default_timezone(value)
|
254
|
+
end
|
255
|
+
else
|
256
|
+
value
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
def date_without_time?(value)
|
263
|
+
case value
|
264
|
+
when OraDate
|
265
|
+
value.hour == 0 && value.minute == 0 && value.second == 0
|
266
|
+
else
|
267
|
+
value.hour == 0 && value.min == 0 && value.sec == 0
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def create_time_with_default_timezone(value)
|
272
|
+
year, month, day, hour, min, sec, usec = case value
|
273
|
+
when Time
|
274
|
+
[value.year, value.month, value.day, value.hour, value.min, value.sec, value.usec]
|
275
|
+
when OraDate
|
276
|
+
[value.year, value.month, value.day, value.hour, value.minute, value.second, 0]
|
277
|
+
else
|
278
|
+
[value.year, value.month, value.day, value.hour, value.min, value.sec, 0]
|
279
|
+
end
|
280
|
+
# code from Time.time_with_datetime_fallback
|
281
|
+
begin
|
282
|
+
Time.send(Base.default_timezone, year, month, day, hour, min, sec, usec)
|
283
|
+
rescue
|
284
|
+
offset = Base.default_timezone.to_sym == :local ? ::DateTime.local_offset : 0
|
285
|
+
::DateTime.civil(year, month, day, hour, min, sec, offset)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
|
291
|
+
# The OracleEnhancedOCIFactory factors out the code necessary to connect and
|
292
|
+
# configure an Oracle/OCI connection.
|
293
|
+
class OracleEnhancedOCIFactory #:nodoc:
|
294
|
+
def self.new_connection(config)
|
295
|
+
# to_s needed if username, password or database is specified as number in database.yml file
|
296
|
+
username = config[:username] && config[:username].to_s
|
297
|
+
password = config[:password] && config[:password].to_s
|
298
|
+
database = config[:database] && config[:database].to_s
|
299
|
+
host, port = config[:host], config[:port]
|
300
|
+
privilege = config[:privilege] && config[:privilege].to_sym
|
301
|
+
async = config[:allow_concurrency]
|
302
|
+
prefetch_rows = config[:prefetch_rows] || 100
|
303
|
+
cursor_sharing = config[:cursor_sharing] || 'force'
|
304
|
+
# get session time_zone from configuration or from TZ environment variable
|
305
|
+
time_zone = config[:time_zone] || ENV['TZ']
|
306
|
+
# get schema details from database.yml
|
307
|
+
schema = config[:schema] && config[:schema].to_s
|
308
|
+
|
309
|
+
# connection using host, port and database name
|
310
|
+
connection_string = if host || port
|
311
|
+
host ||= 'localhost'
|
312
|
+
host = "[#{host}]" if host =~ /^[^\[].*:/ # IPv6
|
313
|
+
port ||= 1521
|
314
|
+
database = "/#{database}" unless database.match(/^\//)
|
315
|
+
"//#{host}:#{port}#{database}"
|
316
|
+
# if no host is specified then assume that
|
317
|
+
# database parameter is TNS alias or TNS connection string
|
318
|
+
else
|
319
|
+
database
|
320
|
+
end
|
321
|
+
|
322
|
+
conn = OCI8.new username, password, connection_string, privilege
|
323
|
+
conn.autocommit = true
|
324
|
+
conn.non_blocking = true if async
|
325
|
+
conn.prefetch_rows = prefetch_rows
|
326
|
+
conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
|
327
|
+
conn.exec "alter session set time_zone = '#{time_zone}'" unless time_zone.blank?
|
328
|
+
# Setting current schema
|
329
|
+
conn.exec "alter session set current_schema = #{schema}" unless schema.blank?
|
330
|
+
# Initialize NLS parameters
|
331
|
+
OracleEnhancedAdapter::DEFAULT_NLS_PARAMETERS.each do |key, default_value|
|
332
|
+
value = config[key] || ENV[key.to_s.upcase] || default_value
|
333
|
+
if value
|
334
|
+
conn.exec "alter session set #{key} = '#{value}'"
|
335
|
+
end
|
336
|
+
end
|
337
|
+
conn
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
|
346
|
+
|
347
|
+
class OCI8 #:nodoc:
|
348
|
+
|
349
|
+
class Cursor #:nodoc:
|
350
|
+
if method_defined? :define_a_column
|
351
|
+
# This OCI8 patch is required with the ruby-oci8 1.0.x or lower.
|
352
|
+
# Set OCI8::BindType::Mapping[] to change the column type
|
353
|
+
# when using ruby-oci8 2.0.
|
354
|
+
|
355
|
+
alias :enhanced_define_a_column_pre_ar :define_a_column
|
356
|
+
def define_a_column(i)
|
357
|
+
case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
|
358
|
+
when 8; @stmt.defineByPos(i, String, 65535) # Read LONG values
|
359
|
+
when 187; @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
|
360
|
+
when 108
|
361
|
+
if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
|
362
|
+
@stmt.defineByPos(i, String, 65535)
|
363
|
+
else
|
364
|
+
raise 'unsupported datatype'
|
365
|
+
end
|
366
|
+
else enhanced_define_a_column_pre_ar i
|
367
|
+
end
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
if OCI8.public_method_defined?(:describe_table)
|
373
|
+
# ruby-oci8 2.0 or upper
|
374
|
+
|
375
|
+
def describe(name)
|
376
|
+
info = describe_table(name.to_s)
|
377
|
+
raise %Q{"DESC #{name}" failed} if info.nil?
|
378
|
+
[info.obj_schema, info.obj_name]
|
379
|
+
end
|
380
|
+
else
|
381
|
+
# ruby-oci8 1.0.x or lower
|
382
|
+
|
383
|
+
# missing constant from oci8 < 0.1.14
|
384
|
+
OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
|
385
|
+
|
386
|
+
# Uses the describeAny OCI call to find the target owner and table_name
|
387
|
+
# indicated by +name+, parsing through synonynms as necessary. Returns
|
388
|
+
# an array of [owner, table_name].
|
389
|
+
def describe(name)
|
390
|
+
@desc ||= @@env.alloc(OCIDescribe)
|
391
|
+
@desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
|
392
|
+
do_ocicall(@ctx) { @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) } rescue raise %Q{"DESC #{name}" failed; does it exist?}
|
393
|
+
info = @desc.attrGet(OCI_ATTR_PARAM)
|
394
|
+
|
395
|
+
case info.attrGet(OCI_ATTR_PTYPE)
|
396
|
+
when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
|
397
|
+
owner = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
|
398
|
+
table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
|
399
|
+
[owner, table_name]
|
400
|
+
when OCI_PTYPE_SYN
|
401
|
+
schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
|
402
|
+
name = info.attrGet(OCI_ATTR_NAME)
|
403
|
+
describe(schema + '.' + name)
|
404
|
+
else raise %Q{"DESC #{name}" failed; not a table or view.}
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
end
|
410
|
+
|
411
|
+
# The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
|
412
|
+
# reset functionality. If a call to #exec fails, and autocommit is turned on
|
413
|
+
# (ie., we're not in the middle of a longer transaction), it will
|
414
|
+
# automatically reconnect and try again. If autocommit is turned off,
|
415
|
+
# this would be dangerous (as the earlier part of the implied transaction
|
416
|
+
# may have failed silently if the connection died) -- so instead the
|
417
|
+
# connection is marked as dead, to be reconnected on it's next use.
|
418
|
+
#:stopdoc:
|
419
|
+
class OCI8EnhancedAutoRecover < DelegateClass(OCI8) #:nodoc:
|
420
|
+
attr_accessor :active #:nodoc:
|
421
|
+
alias :active? :active #:nodoc:
|
422
|
+
|
423
|
+
cattr_accessor :auto_retry
|
424
|
+
class << self
|
425
|
+
alias :auto_retry? :auto_retry #:nodoc:
|
426
|
+
end
|
427
|
+
@@auto_retry = false
|
428
|
+
|
429
|
+
def initialize(config, factory) #:nodoc:
|
430
|
+
@active = true
|
431
|
+
@config = config
|
432
|
+
@factory = factory
|
433
|
+
@connection = @factory.new_connection @config
|
434
|
+
super @connection
|
435
|
+
end
|
436
|
+
|
437
|
+
# Checks connection, returns true if active. Note that ping actively
|
438
|
+
# checks the connection, while #active? simply returns the last
|
439
|
+
# known state.
|
440
|
+
def ping #:nodoc:
|
441
|
+
@connection.exec("select 1 from dual") { |r| nil }
|
442
|
+
@active = true
|
443
|
+
rescue
|
444
|
+
@active = false
|
445
|
+
raise
|
446
|
+
end
|
447
|
+
|
448
|
+
# Resets connection, by logging off and creating a new connection.
|
449
|
+
def reset! #:nodoc:
|
450
|
+
logoff rescue nil
|
451
|
+
begin
|
452
|
+
@connection = @factory.new_connection @config
|
453
|
+
__setobj__ @connection
|
454
|
+
@active = true
|
455
|
+
rescue
|
456
|
+
@active = false
|
457
|
+
raise
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
# ORA-00028: your session has been killed
|
462
|
+
# ORA-01012: not logged on
|
463
|
+
# ORA-03113: end-of-file on communication channel
|
464
|
+
# ORA-03114: not connected to ORACLE
|
465
|
+
# ORA-03135: connection lost contact
|
466
|
+
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114, 3135 ] #:nodoc:
|
467
|
+
|
468
|
+
# Adds auto-recovery functionality.
|
469
|
+
#
|
470
|
+
# See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
|
471
|
+
def exec(sql, *bindvars, &block) #:nodoc:
|
472
|
+
should_retry = self.class.auto_retry? && autocommit?
|
473
|
+
|
474
|
+
begin
|
475
|
+
@connection.exec(sql, *bindvars, &block)
|
476
|
+
rescue OCIException => e
|
477
|
+
raise unless e.is_a?(OCIError) && LOST_CONNECTION_ERROR_CODES.include?(e.code)
|
478
|
+
@active = false
|
479
|
+
raise unless should_retry
|
480
|
+
should_retry = false
|
481
|
+
reset! rescue nil
|
482
|
+
retry
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
# otherwise not working in Ruby 1.9.1
|
487
|
+
if RUBY_VERSION =~ /^1\.9/
|
488
|
+
def describe(name) #:nodoc:
|
489
|
+
@connection.describe(name)
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
end
|
494
|
+
#:startdoc:
|