Skip to content

Commit

Permalink
Oracle: active? and reconnect! methods for handling stale connections…
Browse files Browse the repository at this point in the history
…. Optionally retry queries after reconnect. References #428.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3025 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information
jeremy committed Nov 14, 2005
1 parent 9a2de02 commit 92045be
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 8 deletions.
4 changes: 3 additions & 1 deletion activerecord/CHANGELOG
@@ -1,12 +1,14 @@
*SVN* *SVN*


* Oracle: active? and reconnect! methods for handling stale connections. Optionally retry queries after reconnect. #428 [Michael Schoen <schoenm@earthlink.net>]

* Correct documentation for Base.delete_all. #1568 [Newhydra] * Correct documentation for Base.delete_all. #1568 [Newhydra]


* Oracle: test case for column default parsing. #2788 [Michael Schoen <schoenm@earthlink.net>] * Oracle: test case for column default parsing. #2788 [Michael Schoen <schoenm@earthlink.net>]


* Update documentation for Migrations. #2861 [Tom Werner <tom@cube6media.com>] * Update documentation for Migrations. #2861 [Tom Werner <tom@cube6media.com>]


* When AbstractAdapter#log rescues an exception, attempt to detect and reconnect to an inactive database connection. Connection adapter must respond to the active? and reconnect! instance methods. Initial support for PostgreSQL, MySQL, and SQLite. Make certain that all statements which may need reconnection are performed within a logged block: for example, this means no avoiding log(sql, name) { } if @logger.nil? [Jeremy Kemper] * When AbstractAdapter#log rescues an exception, attempt to detect and reconnect to an inactive database connection. Connection adapter must respond to the active? and reconnect! instance methods. Initial support for PostgreSQL, MySQL, and SQLite. Make certain that all statements which may need reconnection are performed within a logged block: for example, this means no avoiding log(sql, name) { } if @logger.nil? #428 [Jeremy Kemper]


* Oracle: Much faster column reflection. #2848 [Michael Schoen <schoenm@earthlink.net>] * Oracle: Much faster column reflection. #2848 [Michael Schoen <schoenm@earthlink.net>]


Expand Down
124 changes: 117 additions & 7 deletions activerecord/lib/active_record/connection_adapters/oci_adapter.rb
Expand Up @@ -23,18 +23,16 @@
# portions Copyright 2005 Graham Jenkins # portions Copyright 2005 Graham Jenkins


require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/abstract_adapter'
require 'delegate'


begin begin
require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8 require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8


module ActiveRecord module ActiveRecord
class Base class Base
def self.oci_connection(config) #:nodoc: def self.oci_connection(config) #:nodoc:
conn = OCI8.new config[:username], config[:password], config[:host] # Use OCI8AutoRecover instead of normal OCI8 driver.
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} ConnectionAdapters::OCIAdapter.new OCI8AutoRecover.new(config), logger
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'}
conn.autocommit = true
ConnectionAdapters::OCIAdapter.new conn, logger
end end


# Enable the id column to be bound into the sql later, by the adapter's insert method. # Enable the id column to be bound into the sql later, by the adapter's insert method.
Expand Down Expand Up @@ -213,6 +211,27 @@ def quote(value, column = nil) #:nodoc:
end end




# CONNECTION MANAGEMENT ====================================#

# Returns true if the connection is active.
def active?
# Just checks the active flag, which is set false if the last exec
# got an error indicating a bad connection. An alternative would be
# to call #ping, which is more expensive (and should always get
# the same result).
@connection.active?
end

# Reconnects to the database.
def reconnect!
begin
@connection.reset!
rescue OCIError => e
@logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
end
end


# DATABASE STATEMENTS ====================================== # DATABASE STATEMENTS ======================================
# #
# see: abstract/database_statements.rb # see: abstract/database_statements.rb
Expand Down Expand Up @@ -337,7 +356,7 @@ def columns(table_name, name = nil) #:nodoc:
and syn.owner (+)= cat.owner } and syn.owner (+)= cat.owner }
end end


select_all(table_cols).map do |row| select_all(table_cols, name).map do |row|
row['data_default'].sub!(/^'(.*)'\s*$/, '\1') if row['data_default'] row['data_default'].sub!(/^'(.*)'\s*$/, '\1') if row['data_default']
OCIColumn.new( OCIColumn.new(
oci_downcase(row['column_name']), oci_downcase(row['column_name']),
Expand Down Expand Up @@ -485,10 +504,101 @@ def define_a_column(i)
when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
else define_a_column_pre_ar i else define_a_column_pre_ar i
end end
end end
end end
end end



# The OCIConnectionFactory factors out the code necessary to connect and
# configure an OCI connection.
class OCIConnectionFactory
def new_connection(username, password, host)
conn = OCI8.new username, password, host
conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'}
conn.autocommit = true
conn
end
end


# The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and
# reset functionality. If a call to #exec fails, and autocommit is turned on
# (ie., we're not in the middle of a longer transaction), it will
# automatically reconnect and try again. If autocommit is turned off,
# this would be dangerous (as the earlier part of the implied transaction
# may have failed silently if the connection died) -- so instead the
# connection is marked as dead, to be reconnected on it's next use.
class OCI8AutoRecover < DelegateClass(OCI8)
attr_accessor :active
alias :active? :active

cattr_accessor :auto_retry
class << self
alias :auto_retry? :auto_retry
end
@@auto_retry = false

def initialize(config, factory = OCIConnectionFactory.new)
@active = true
@username, @password, @host = config[:username], config[:password], config[:host]
@factory = factory
@connection = @factory.new_connection @username, @password, @host
super @connection
end

# Checks connection, returns true if active. Note that ping actively
# checks the connection, while #active? simply returns the last
# known state.
def ping
@active = true
begin
@connection.commit
rescue
@active = false
end
active?
end

# Resets connection, by logging off and creating a new connection.
def reset!
logoff rescue nil
begin
@connection = @factory.new_connection @username, @password, @host
__setobj__ @connection
@active = true
rescue
@active = false
raise
end
end

# ORA-00028: your session has been killed
# ORA-01012: not logged on
# ORA-03113: end-of-file on communication channel
# ORA-03114: not connected to ORACLE
LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ]

# Adds auto-recovery functionality.
#
# See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11
def exec(sql, *bindvars)
should_retry = self.class.auto_retry? && autocommit?

begin
@connection.exec(sql, *bindvars)
rescue OCIError => e
raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code)
@active = false
raise unless should_retry
should_retry = false
reset! rescue nil
retry
end
end

end

rescue LoadError rescue LoadError
# OCI8 driver is unavailable. # OCI8 driver is unavailable.
end end

0 comments on commit 92045be

Please sign in to comment.