Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 3025

Show
Ignore:
Timestamp:
11/14/05 10:01:09 (3 years ago)
Author:
bitsweat
Message:

Oracle: active? and reconnect! methods for handling stale connections. Optionally retry queries after reconnect. References #428.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activerecord/CHANGELOG

    r3021 r3025  
    11*SVN* 
    22 
     3* Oracle: active? and reconnect! methods for handling stale connections.  Optionally retry queries after reconnect.  #428 [Michael Schoen <schoenm@earthlink.net>] 
     4 
    35* Correct documentation for Base.delete_all.  #1568 [Newhydra] 
    46 
     
    79* Update documentation for Migrations.  #2861 [Tom Werner <tom@cube6media.com>] 
    810 
    9 * 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] 
     11* 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] 
    1012 
    1113* Oracle: Much faster column reflection.  #2848 [Michael Schoen <schoenm@earthlink.net>] 
  • trunk/activerecord/lib/active_record/connection_adapters/oci_adapter.rb

    r3019 r3025  
    2424 
    2525require 'active_record/connection_adapters/abstract_adapter' 
     26require 'delegate' 
    2627 
    2728begin 
     
    3132    class Base 
    3233      def self.oci_connection(config) #:nodoc: 
    33         conn = OCI8.new config[:username], config[:password], config[:host] 
    34         conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} 
    35         conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} 
    36         conn.autocommit = true 
    37         ConnectionAdapters::OCIAdapter.new conn, logger 
     34        # Use OCI8AutoRecover instead of normal OCI8 driver. 
     35        ConnectionAdapters::OCIAdapter.new OCI8AutoRecover.new(config), logger 
    3836      end 
    3937 
     
    214212 
    215213 
     214        # CONNECTION MANAGEMENT ====================================# 
     215 
     216        # Returns true if the connection is active. 
     217        def active? 
     218          # Just checks the active flag, which is set false if the last exec 
     219          # got an error indicating a bad connection. An alternative would be 
     220          # to call #ping, which is more expensive (and should always get 
     221          # the same result). 
     222          @connection.active? 
     223        end 
     224 
     225        # Reconnects to the database. 
     226        def reconnect! 
     227          begin 
     228            @connection.reset! 
     229          rescue OCIError => e 
     230            @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" 
     231          end 
     232        end 
     233 
     234 
    216235        # DATABASE STATEMENTS ====================================== 
    217236        # 
     
    338357          end 
    339358 
    340           select_all(table_cols).map do |row| 
     359          select_all(table_cols, name).map do |row| 
    341360            row['data_default'].sub!(/^'(.*)'\s*$/, '\1') if row['data_default'] 
    342361            OCIColumn.new( 
     
    486505        else define_a_column_pre_ar i 
    487506        end 
    488         end 
    489     end 
     507      end 
     508    end 
     509  end 
     510 
     511 
     512  # The OCIConnectionFactory factors out the code necessary to connect and 
     513  # configure an OCI connection. 
     514  class OCIConnectionFactory 
     515    def new_connection(username, password, host) 
     516      conn = OCI8.new username, password, host 
     517      conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} 
     518      conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} 
     519      conn.autocommit = true 
     520      conn 
     521    end 
     522  end 
     523 
     524 
     525  # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and 
     526  # reset functionality. If a call to #exec fails, and autocommit is turned on 
     527  # (ie., we're not in the middle of a longer transaction), it will  
     528  # automatically reconnect and try again. If autocommit is turned off, 
     529  # this would be dangerous (as the earlier part of the implied transaction 
     530  # may have failed silently if the connection died) -- so instead the  
     531  # connection is marked as dead, to be reconnected on it's next use. 
     532  class OCI8AutoRecover < DelegateClass(OCI8) 
     533    attr_accessor :active 
     534    alias :active? :active 
     535 
     536    cattr_accessor :auto_retry 
     537    class << self 
     538      alias :auto_retry? :auto_retry 
     539    end 
     540    @@auto_retry = false 
     541 
     542    def initialize(config, factory = OCIConnectionFactory.new) 
     543      @active = true 
     544      @username, @password, @host = config[:username], config[:password], config[:host] 
     545      @factory = factory 
     546      @connection  = @factory.new_connection @username, @password, @host 
     547      super @connection 
     548    end 
     549 
     550    # Checks connection, returns true if active. Note that ping actively 
     551    # checks the connection, while #active? simply returns the last 
     552    # known state. 
     553    def ping 
     554      @active = true 
     555      begin 
     556        @connection.commit 
     557      rescue 
     558        @active = false 
     559      end 
     560      active? 
     561    end 
     562 
     563    # Resets connection, by logging off and creating a new connection. 
     564    def reset! 
     565      logoff rescue nil 
     566      begin 
     567        @connection = @factory.new_connection @username, @password, @host 
     568        __setobj__ @connection 
     569        @active = true 
     570      rescue 
     571        @active = false 
     572        raise 
     573      end 
     574    end 
     575 
     576    # ORA-00028: your session has been killed 
     577    # ORA-01012: not logged on  
     578    # ORA-03113: end-of-file on communication channel 
     579    # ORA-03114: not connected to ORACLE 
     580    LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ] 
     581 
     582    # Adds auto-recovery functionality. 
     583    # 
     584    # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11 
     585    def exec(sql, *bindvars) 
     586      should_retry = self.class.auto_retry? && autocommit? 
     587 
     588      begin 
     589        @connection.exec(sql, *bindvars) 
     590      rescue OCIError => e 
     591        raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code) 
     592        @active = false 
     593        raise unless should_retry 
     594        should_retry = false 
     595        reset! rescue nil 
     596        retry 
     597      end 
     598    end 
     599 
    490600  end 
    491601