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

Ticket #428: oci_reconnect.patch

File oci_reconnect.patch, 6.6 kB (added by mschoen, 3 years ago)

oracle implementation

  • activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

    old new  
    2525      @@reconnect_success = 0 
    2626      @@reconnect_failure = 0 
    2727      def self.reconnect_success_rate 
    28         @@reconnect_success.to_f / (@@reconnect_success + @@reconnect_failure) 
     28        @@reconnect_success.to_f / (@@reconnect_success + @@reconnect_failure) * 100 
    2929      end 
    3030 
    3131      def initialize(connection, logger = nil) #:nodoc: 
     
    3838      def adapter_name 
    3939        'Abstract' 
    4040      end 
    41        
     41 
    4242      # Does this adapter support migrations?  Backend specific, as the 
    4343      # abstract adapter always returns +false+. 
    4444      def supports_migrations? 
     
    5050        rt 
    5151      end 
    5252 
    53       protected   
     53      protected 
    5454        def log(sql, name) 
    5555          if block_given? 
    5656            if @logger and @logger.level <= Logger::INFO 
     
    124124    end 
    125125  end 
    126126end 
     127 
  • activerecord/lib/active_record/connection_adapters/oci_adapter.rb

    old new  
    2323# portions Copyright 2005 Graham Jenkins 
    2424 
    2525require 'active_record/connection_adapters/abstract_adapter' 
     26require 'delegate' 
    2627 
    2728begin 
    2829  require_library_or_gem 'oci8' unless self.class.const_defined? :OCI8 
     
    3031  module ActiveRecord 
    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 
    4038      # Enable the id column to be bound into the sql later, by the adapter's insert method. 
     
    213211        end 
    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        # 
    218237        # see: abstract/database_statements.rb 
     
    337356               and syn.owner (+)= cat.owner } 
    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( 
    343362              oci_downcase(row['column_name']),  
     
    485504        when 187  : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values 
    486505        else define_a_column_pre_ar i 
    487506        end 
    488        end 
     507      end 
    489508    end 
    490509  end 
    491510 
     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 
     600  end 
     601 
    492602rescue LoadError 
    493603  # OCI8 driver is unavailable. 
    494604end