aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Kemper <jeremy@bitsweat.net>2005-11-14 10:01:09 +0000
committerJeremy Kemper <jeremy@bitsweat.net>2005-11-14 10:01:09 +0000
commit92045be6b37baffbf98b13d1e5fc0fa9be908a36 (patch)
tree0a93f95dd75408d79b940e494d63d84c7bb74c02
parent9a2de028dc68525bead48a2c9d6aca21b0dc328a (diff)
downloadrails-92045be6b37baffbf98b13d1e5fc0fa9be908a36.tar.gz
rails-92045be6b37baffbf98b13d1e5fc0fa9be908a36.tar.bz2
rails-92045be6b37baffbf98b13d1e5fc0fa9be908a36.zip
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
-rw-r--r--activerecord/CHANGELOG4
-rw-r--r--activerecord/lib/active_record/connection_adapters/oci_adapter.rb124
2 files changed, 120 insertions, 8 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index a184ad192c..c5b2f9838c 100644
--- a/activerecord/CHANGELOG
+++ b/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>]
diff --git a/activerecord/lib/active_record/connection_adapters/oci_adapter.rb b/activerecord/lib/active_record/connection_adapters/oci_adapter.rb
index 2bcd68f2a9..d615ba33a1 100644
--- a/activerecord/lib/active_record/connection_adapters/oci_adapter.rb
+++ b/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 @@ begin
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 @@ begin
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 @@ begin
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 @@ begin
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