Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Oracle: active? and reconnect! methods for handling stale connections…

…. 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...
commit 92045be6b37baffbf98b13d1e5fc0fa9be908a36 1 parent 9a2de02
@jeremy jeremy authored
View
4 activerecord/CHANGELOG
@@ -1,12 +1,14 @@
*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]
* Oracle: test case for column default parsing. #2788 [Michael Schoen <schoenm@earthlink.net>]
* 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>]
View
124 activerecord/lib/active_record/connection_adapters/oci_adapter.rb
@@ -23,6 +23,7 @@
# portions Copyright 2005 Graham Jenkins
require 'active_record/connection_adapters/abstract_adapter'
+require 'delegate'
begin
require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8
@@ -30,11 +31,8 @@
module ActiveRecord
class Base
def self.oci_connection(config) #:nodoc:
- conn = OCI8.new config[:username], config[:password], config[: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
- ConnectionAdapters::OCIAdapter.new conn, logger
+ # Use OCI8AutoRecover instead of normal OCI8 driver.
+ ConnectionAdapters::OCIAdapter.new OCI8AutoRecover.new(config), logger
end
# Enable the id column to be bound into the sql later, by the adapter's insert method.
@@ -213,6 +211,27 @@ def quote(value, column = nil) #:nodoc:
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 ======================================
#
# see: abstract/database_statements.rb
@@ -337,7 +356,7 @@ def columns(table_name, name = nil) #:nodoc:
and syn.owner (+)= cat.owner }
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']
OCIColumn.new(
oci_downcase(row['column_name']),
@@ -485,10 +504,101 @@ def define_a_column(i)
when 187 : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
else define_a_column_pre_ar i
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
# OCI8 driver is unavailable.
end
Please sign in to comment.
Something went wrong with that request. Please try again.