oracle_adapter.rb 25.7 KB
Newer Older
1
# oracle_adapter.rb -- ActiveRecord adapter for Oracle 8i, 9i, 10g
2 3 4 5 6 7 8
#
# Original author: Graham Jenkins
#
# Current maintainer: Michael Schoen <schoenm@earthlink.net>
#
#########################################################################
#
9
# Implementation notes:
10 11 12 13 14 15 16 17 18
# 1. Redefines (safely) a method in ActiveRecord to make it possible to
#    implement an autonumbering solution for Oracle.
# 2. The OCI8 driver is patched to properly handle values for LONG and
#    TIMESTAMP columns. The driver-author has indicated that a future
#    release of the driver will obviate this patch.
# 3. LOB support is implemented through an after_save callback.
# 4. Oracle does not offer native LIMIT and OFFSET options; this
#    functionality is mimiced through the use of nested selects.
#    See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
19
#
20 21 22 23
# Do what you want with this code, at your own peril, but if any
# significant portion of my code remains then please acknowledge my
# contribution.
# portions Copyright 2005 Graham Jenkins
24

25
require 'active_record/connection_adapters/abstract_adapter'
26
require 'delegate'
27 28 29 30 31

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

  module ActiveRecord
32
    class Base
33
      def self.oracle_connection(config) #:nodoc:
34
        # Use OCI8AutoRecover instead of normal OCI8 driver.
35 36 37 38 39 40 41
        ConnectionAdapters::OracleAdapter.new OCI8AutoRecover.new(config), logger
      end

      # for backwards-compatibility
      def self.oci_connection(config) #:nodoc:
        config[:database] = config[:host]
        self.oracle_connection(config)
42 43 44 45
      end

      # After setting large objects to empty, select the OCI8::LOB
      # and write back the data.
46
      after_save :write_lobs
47
      def write_lobs() #:nodoc:
48
        if connection.is_a?(ConnectionAdapters::OracleAdapter)
49
          self.class.columns.select { |c| c.sql_type =~ /LOB$/i }.each { |c|
50
            value = self[c.name]
51
            value = value.to_yaml if unserializable_attribute?(c.name, c)
52 53
            next if value.nil?  || (value == '')
            lob = connection.select_one(
54
              "SELECT #{c.name} FROM #{self.class.table_name} WHERE #{self.class.primary_key} = #{quote_value(id)}",
55 56 57 58 59 60 61 62 63 64
              'Writable Large Object')[c.name]
            lob.write value
          }
        end
      end

      private :write_lobs
    end


65
    module ConnectionAdapters #:nodoc:
66
      class OracleColumn < Column #:nodoc:
67 68

        def type_cast(value)
69 70
          return guess_date_or_time(value) if type == :datetime && OracleAdapter.emulate_dates
          super
71 72
        end

73 74
        private
        def simplified_type(field_type)
75
          return :boolean if OracleAdapter.emulate_booleans && field_type == 'NUMBER(1)'
76
          case field_type
77 78
            when /date|time/i then :datetime
            else super
79 80 81
          end
        end

82 83 84 85 86 87
        def guess_date_or_time(value)
          (value.hour == 0 and value.min == 0 and value.sec == 0) ?
            Date.new(value.year, value.month, value.day) : value
        end
      end

88 89 90 91 92 93

      # This is an Oracle/OCI adapter for the ActiveRecord persistence
      # framework. It relies upon the OCI8 driver, which works with Oracle 8i
      # and above. Most recent development has been on Debian Linux against
      # a 10g database, ActiveRecord 1.12.1 and OCI8 0.1.13.
      # See: http://rubyforge.org/projects/ruby-oci8/
94 95
      #
      # Usage notes:
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
      # * Key generation assumes a "${table_name}_seq" sequence is available
      #   for all tables; the sequence name can be changed using
      #   ActiveRecord::Base.set_sequence_name. When using Migrations, these
      #   sequences are created automatically.
      # * Oracle uses DATE or TIMESTAMP datatypes for both dates and times.
      #   Consequently some hacks are employed to map data back to Date or Time
      #   in Ruby. If the column_name ends in _time it's created as a Ruby Time.
      #   Else if the hours/minutes/seconds are 0, I make it a Ruby Date. Else
      #   it's a Ruby Time. This is a bit nasty - but if you use Duck Typing
      #   you'll probably not care very much. In 9i and up it's tempting to
      #   map DATE to Date and TIMESTAMP to Time, but too many databases use
      #   DATE for both. Timezones and sub-second precision on timestamps are
      #   not supported.
      # * Default values that are functions (such as "SYSDATE") are not
      #   supported. This is a restriction of the way ActiveRecord supports
      #   default values.
112 113 114
      # * Support for Oracle8 is limited by Rails' use of ANSI join syntax, which
      #   is supported in Oracle9i and later. You will need to use #finder_sql for
      #   has_and_belongs_to_many associations to run against Oracle8.
115
      #
116
      # Required parameters:
117
      #
118 119 120 121
      # * <tt>:username</tt>
      # * <tt>:password</tt>
      # * <tt>:database</tt>
      class OracleAdapter < AbstractAdapter
122

123 124 125
        @@emulate_booleans = true
        cattr_accessor :emulate_booleans

126 127 128
        @@emulate_dates = false
        cattr_accessor :emulate_dates

129
        def adapter_name #:nodoc:
130
          'Oracle'
131 132
        end

133 134 135
        def supports_migrations? #:nodoc:
          true
        end
136

137
        def native_database_types #:nodoc:
138
          {
139
            :primary_key => "NUMBER(38) NOT NULL PRIMARY KEY",
140
            :string      => { :name => "VARCHAR2", :limit => 255 },
141
            :text        => { :name => "CLOB" },
142 143
            :integer     => { :name => "NUMBER", :limit => 38 },
            :float       => { :name => "NUMBER" },
144
            :decimal     => { :name => "DECIMAL" },
145 146 147 148 149 150 151 152 153
            :datetime    => { :name => "DATE" },
            :timestamp   => { :name => "DATE" },
            :time        => { :name => "DATE" },
            :date        => { :name => "DATE" },
            :binary      => { :name => "BLOB" },
            :boolean     => { :name => "NUMBER", :limit => 1 }
          }
        end

154 155 156
        def table_alias_length
          30
        end
157 158 159 160 161 162 163 164 165 166 167

        # QUOTING ==================================================
        #
        # see: abstract/quoting.rb

        # camelCase column names need to be quoted; not that anyone using Oracle
        # would really do this, but handling this case means we pass the test...
        def quote_column_name(name) #:nodoc:
          name =~ /[A-Z]/ ? "\"#{name}\"" : name
        end

168 169
        def quote_string(s) #:nodoc:
          s.gsub(/'/, "''")
170 171
        end

172
        def quote(value, column = nil) #:nodoc:
173
          if value && column && [:text, :binary].include?(column.type)
174
            %Q{empty_#{ column.sql_type.downcase rescue 'blob' }()}
175
          else
176
            super
177 178 179
          end
        end

180 181 182
        def quoted_true
          "1"
        end
183

184 185 186 187
        def quoted_false
          "0"
        end

188

189 190
        # CONNECTION MANAGEMENT ====================================
        #
191 192 193

        # Returns true if the connection is active.
        def active?
194
          # Pings the connection to check if it's still good. Note that an
195
          # #active? method is also available, but that simply returns the
196 197 198
          # last known state, which isn't good enough if the connection has
          # gone stale since the last use.
          @connection.ping
199
        rescue OCIException
200
          false
201 202 203 204
        end

        # Reconnects to the database.
        def reconnect!
205
          @connection.reset!
206
        rescue OCIException => e
207
          @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}"
208 209
        end

210 211 212 213 214 215
        # Disconnects from the database.
        def disconnect!
          @connection.logoff rescue nil
          @connection.active = false
        end

216

217 218 219 220 221 222 223 224
        # DATABASE STATEMENTS ======================================
        #
        # see: abstract/database_statements.rb

        def execute(sql, name = nil) #:nodoc:
          log(sql, name) { @connection.exec sql }
        end

225 226 227 228 229 230 231 232
        # Returns the next sequence value from a sequence generator. Not generally
        # called directly; used by ActiveRecord to get the next primary key value
        # when inserting a new database record (see #prefetch_primary_key?).
        def next_sequence_value(sequence_name)
          id = 0
          @connection.exec("select #{sequence_name}.nextval id from dual") { |r| id = r[0].to_i }
          id
        end
233

234 235
        def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
          execute(sql, name)
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
          id_value
        end

        def begin_db_transaction #:nodoc:
          @connection.autocommit = false
        end

        def commit_db_transaction #:nodoc:
          @connection.commit
        ensure
          @connection.autocommit = true
        end

        def rollback_db_transaction #:nodoc:
          @connection.rollback
        ensure
          @connection.autocommit = true
        end

        def add_limit_offset!(sql, options) #:nodoc:
          offset = options[:offset] || 0

          if limit = options[:limit]
            sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_ where rownum <= #{offset+limit}) where raw_rnum_ > #{offset}"
          elsif offset > 0
            sql.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{sql}) raw_sql_) where raw_rnum_ > #{offset}"
          end
        end

265 266 267 268 269 270
        # Returns true for Oracle adapter (since Oracle requires primary key
        # values to be pre-fetched before insert). See also #next_sequence_value.
        def prefetch_primary_key?(table_name = nil)
          true
        end

271 272 273 274 275 276 277 278 279
        def default_sequence_name(table, column) #:nodoc:
          "#{table}_seq"
        end


        # SCHEMA STATEMENTS ========================================
        #
        # see: abstract/schema_statements.rb

280 281 282 283
        def current_database #:nodoc:
          select_one("select sys_context('userenv','db_name') db from dual")["db"]
        end

284
        def tables(name = nil) #:nodoc:
285 286 287 288 289 290 291 292 293
          select_all("select lower(table_name) from user_tables").inject([]) do | tabs, t |
            tabs << t.to_a.first.last
          end
        end

        def indexes(table_name, name = nil) #:nodoc:
          result = select_all(<<-SQL, name)
            SELECT lower(i.index_name) as index_name, i.uniqueness, lower(c.column_name) as column_name
              FROM user_indexes i, user_ind_columns c
294 295
             WHERE i.table_name = '#{table_name.to_s.upcase}'
               AND c.index_name = i.index_name
296
               AND i.index_name NOT IN (SELECT uc.index_name FROM user_constraints uc WHERE uc.constraint_type = 'P')
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
              ORDER BY i.index_name, c.column_position
          SQL

          current_index = nil
          indexes = []

          result.each do |row|
            if current_index != row['index_name']
              indexes << IndexDefinition.new(table_name, row['index_name'], row['uniqueness'] == "UNIQUE", [])
              current_index = row['index_name']
            end

            indexes.last.columns << row['column_name']
          end

          indexes
        end

315
        def columns(table_name, name = nil) #:nodoc:
316
          (owner, table_name) = @connection.describe(table_name)
317

318
          table_cols = <<-SQL
319
            select column_name as name, data_type as sql_type, data_default, nullable,
320
                   decode(data_type, 'NUMBER', data_precision,
321
                                     'FLOAT', data_precision,
322
                                     'VARCHAR2', data_length,
323
                                      null) as limit,
324
                   decode(data_type, 'NUMBER', data_scale, null) as scale
325
              from all_tab_columns
326 327
             where owner      = '#{owner}'
               and table_name = '#{table_name}'
328
             order by column_id
329
          SQL
330

331
          select_all(table_cols, name).map do |row|
332 333 334 335 336 337
            limit, scale = row['limit'], row['scale']
            if limit || scale
              row['sql_type'] << "(#{(limit || 38).to_i}" + ((scale = scale.to_i) > 0 ? ",#{scale})" : ")")
            end

            # clean up odd default spacing from Oracle
338 339 340
            if row['data_default']
              row['data_default'].sub!(/^(.*?)\s*$/, '\1')
              row['data_default'].sub!(/^'(.*)'$/, '\1')
341
              row['data_default'] = nil if row['data_default'] =~ /^(null|empty_[bc]lob\(\))$/i
342
            end
343

344 345 346
            OracleColumn.new(oracle_downcase(row['name']),
                             row['data_default'],
                             row['sql_type'],
347
                             row['nullable'] == 'Y')
348 349 350 351 352
          end
        end

        def create_table(name, options = {}) #:nodoc:
          super(name, options)
353 354
          seq_name = options[:sequence_name] || "#{name}_seq"
          execute "CREATE SEQUENCE #{seq_name} START WITH 10000" unless options[:id] == false
355 356 357 358
        end

        def rename_table(name, new_name) #:nodoc:
          execute "RENAME #{name} TO #{new_name}"
359
          execute "RENAME #{name}_seq TO #{new_name}_seq" rescue nil
360
        end
361

362
        def drop_table(name, options = {}) #:nodoc:
363
          super(name)
364 365
          seq_name = options[:sequence_name] || "#{name}_seq"
          execute "DROP SEQUENCE #{seq_name}" rescue nil
366 367 368 369 370 371 372 373 374 375 376
        end

        def remove_index(table_name, options = {}) #:nodoc:
          execute "DROP INDEX #{index_name(table_name, options)}"
        end

        def change_column_default(table_name, column_name, default) #:nodoc:
          execute "ALTER TABLE #{table_name} MODIFY #{column_name} DEFAULT #{quote(default)}"
        end

        def change_column(table_name, column_name, type, options = {}) #:nodoc:
377
          change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
378 379 380 381 382 383 384 385 386 387 388 389
          add_column_options!(change_column_sql, options)
          execute(change_column_sql)
        end

        def rename_column(table_name, column_name, new_column_name) #:nodoc:
          execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} to #{new_column_name}"
        end

        def remove_column(table_name, column_name) #:nodoc:
          execute "ALTER TABLE #{table_name} DROP COLUMN #{column_name}"
        end

390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
        # Find a table's primary key and sequence. 
        # *Note*: Only primary key is implemented - sequence will be nil.
        def pk_and_sequence_for(table_name)
          (owner, table_name) = @connection.describe(table_name)

          pks = select_values(<<-SQL, 'Primary Key')
            select cc.column_name
              from all_constraints c, all_cons_columns cc
             where c.owner = '#{owner}'
               and c.table_name = '#{table_name}'
               and c.constraint_type = 'P'
               and cc.owner = c.owner
               and cc.constraint_name = c.constraint_name
          SQL

          # only support single column keys
          pks.size == 1 ? [oracle_downcase(pks.first), nil] : nil
        end

409
        def structure_dump #:nodoc:
410 411 412 413 414
          s = select_all("select sequence_name from user_sequences").inject("") do |structure, seq|
            structure << "create sequence #{seq.to_a.first.last};\n\n"
          end

          select_all("select table_name from user_tables").inject(s) do |structure, table|
415
            ddl = "create table #{table.to_a.first.last} (\n "
416 417 418 419 420
            cols = select_all(%Q{
              select column_name, data_type, data_length, data_precision, data_scale, data_default, nullable
              from user_tab_columns
              where table_name = '#{table.to_a.first.last}'
              order by column_id
421 422
            }).map do |row|
              col = "#{row['column_name'].downcase} #{row['data_type'].downcase}"
423 424 425 426 427
              if row['data_type'] =='NUMBER' and !row['data_precision'].nil?
                col << "(#{row['data_precision'].to_i}"
                col << ",#{row['data_scale'].to_i}" if !row['data_scale'].nil?
                col << ')'
              elsif row['data_type'].include?('CHAR')
428
                col << "(#{row['data_length'].to_i})"
429 430 431 432
              end
              col << " default #{row['data_default']}" if !row['data_default'].nil?
              col << ' not null' if row['nullable'] == 'N'
              col
433
            end
434 435 436 437 438 439
            ddl << cols.join(",\n ")
            ddl << ");\n\n"
            structure << ddl
          end
        end

440
        def structure_drop #:nodoc:
441 442 443 444 445 446
          s = select_all("select sequence_name from user_sequences").inject("") do |drop, seq|
            drop << "drop sequence #{seq.to_a.first.last};\n\n"
          end

          select_all("select table_name from user_tables").inject(s) do |drop, table|
            drop << "drop table #{table.to_a.first.last} cascade constraints;\n\n"
447 448 449
          end
        end

450 451 452 453 454 455 456 457
        def add_column_options!(sql, options) #:nodoc:
          # handle case of defaults for CLOB columns, which would otherwise get "quoted" incorrectly
          if options_include_default?(options) && (column = options[:column]) && column.type == :text
            sql << " DEFAULT #{quote(options.delete(:default))}" 
          end
          super
        end

458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
        # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause.
        #
        # Oracle requires the ORDER BY columns to be in the SELECT list for DISTINCT
        # queries. However, with those columns included in the SELECT DISTINCT list, you
        # won't actually get a distinct list of the column you want (presuming the column
        # has duplicates with multiple values for the ordered-by columns. So we use the 
        # FIRST_VALUE function to get a single (first) value for each column, effectively
        # making every row the same.
        #
        #   distinct("posts.id", "posts.created_at desc")
        def distinct(columns, order_by)
          return "DISTINCT #{columns}" if order_by.blank?

          # construct a valid DISTINCT clause, ie. one that includes the ORDER BY columns, using
          # FIRST_VALUE such that the inclusion of these columns doesn't invalidate the DISTINCT
473 474 475 476
          order_columns = order_by.split(',').map { |s| s.strip }.reject(&:blank?)
          order_columns = order_columns.zip((0...order_columns.size).to_a).map do |c, i|
            "FIRST_VALUE(#{c.split.first}) OVER (PARTITION BY #{columns} ORDER BY #{c}) AS alias_#{i}__"
          end
477 478 479
          sql = "DISTINCT #{columns}, "
          sql << order_columns * ", "
        end
480

481 482 483 484 485 486 487 488 489 490 491 492 493
        # ORDER BY clause for the passed order option.
        # 
        # Uses column aliases as defined by #distinct.
        def add_order_by_for_association_limiting!(sql, options)
          return sql if options[:order].blank?

          order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?)
          order.map! {|s| $1 if s =~ / (.*)/}
          order = order.zip((0...order.size).to_a).map { |s,i| "alias_#{i}__ #{s}" }.join(', ')

          sql << "ORDER BY #{order}"
        end

494 495 496
        private

        def select(sql, name = nil)
497
          cursor = execute(sql, name)
498
          cols = cursor.get_col_names.map { |x| oracle_downcase(x) }
499
          rows = []
500

501 502
          while row = cursor.fetch
            hash = Hash.new
503 504

            cols.each_with_index do |col, i|
505 506
              hash[col] =
                case row[i]
507 508 509
                when OCI8::LOB
                  name == 'Writable Large Object' ? row[i]: row[i].read
                when OraDate
510 511 512 513 514
                  if emulate_dates && (row[i].hour == 0 && row[i].minute == 0 && row[i].second == 0)
                    row[i].to_date
                  else
                    row[i].to_time rescue row[i].to_datetime
                  end
515
                else row[i]
516
                end unless col == 'raw_rnum_'
517 518
            end

519 520
            rows << hash
          end
521

522 523 524 525 526
          rows
        ensure
          cursor.close if cursor
        end

527 528 529 530 531 532
        # Oracle column names by default are case-insensitive, but treated as upcase;
        # for neatness, we'll downcase within Rails. EXCEPT that folks CAN quote
        # their column names when creating Oracle tables, which makes then case-sensitive.
        # I don't know anybody who does this, but we'll handle the theoretical case of a
        # camelCase column name. I imagine other dbs handle this different, since there's a
        # unit test that's currently failing test_oci.
533
        def oracle_downcase(column_name)
534
          column_name =~ /[a-z]/ ? column_name : column_name.downcase
535 536 537 538 539 540 541
        end

      end
    end
  end


542
  class OCI8 #:nodoc:
543 544 545

    # This OCI8 patch may not longer be required with the upcoming
    # release of version 0.2.
546
    class Cursor #:nodoc:
547 548 549 550 551
      alias :define_a_column_pre_ar :define_a_column
      def define_a_column(i)
        case do_ocicall(@ctx) { @parms[i - 1].attrGet(OCI_ATTR_DATA_TYPE) }
        when 8    : @stmt.defineByPos(i, String, 65535) # Read LONG values
        when 187  : @stmt.defineByPos(i, OraDate) # Read TIMESTAMP values
552 553 554 555 556 557
        when 108
          if @parms[i - 1].attrGet(OCI_ATTR_TYPE_NAME) == 'XMLTYPE'
            @stmt.defineByPos(i, String, 65535)
          else
            raise 'unsupported datatype'
          end
558 559
        else define_a_column_pre_ar i
        end
560
      end
561
    end
562

563 564
    # missing constant from oci8 < 0.1.14
    OCI_PTYPE_UNK = 0 unless defined?(OCI_PTYPE_UNK)
565

566 567 568 569
    # Uses the describeAny OCI call to find the target owner and table_name
    # indicated by +name+, parsing through synonynms as necessary. Returns
    # an array of [owner, table_name].
    def describe(name)
570
      @desc ||= @@env.alloc(OCIDescribe)
571
      @desc.attrSet(OCI_ATTR_DESC_PUBLIC, -1) if VERSION >= '0.1.14'
572
      @desc.describeAny(@svc, name.to_s, OCI_PTYPE_UNK) rescue raise %Q{"DESC #{name}" failed; does it exist?}
573 574 575 576 577 578 579 580 581 582 583
      info = @desc.attrGet(OCI_ATTR_PARAM)

      case info.attrGet(OCI_ATTR_PTYPE)
      when OCI_PTYPE_TABLE, OCI_PTYPE_VIEW
        owner      = info.attrGet(OCI_ATTR_OBJ_SCHEMA)
        table_name = info.attrGet(OCI_ATTR_OBJ_NAME)
        [owner, table_name]
      when OCI_PTYPE_SYN
        schema = info.attrGet(OCI_ATTR_SCHEMA_NAME)
        name   = info.attrGet(OCI_ATTR_NAME)
        describe(schema + '.' + name)
584
      else raise %Q{"DESC #{name}" failed; not a table or view.}
585 586
      end
    end
587

588
  end
589

590

591 592 593
  # The OracleConnectionFactory factors out the code necessary to connect and
  # configure an Oracle/OCI connection.
  class OracleConnectionFactory #:nodoc:
594
    def new_connection(username, password, database, async, prefetch_rows, cursor_sharing)
595
      conn = OCI8.new username, password, database
596
      conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'}
597
      conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil
598
      conn.autocommit = true
599
      conn.non_blocking = true if async
600 601
      conn.prefetch_rows = prefetch_rows
      conn.exec "alter session set cursor_sharing = #{cursor_sharing}" rescue nil
602 603 604 605 606 607 608
      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
609
  # (ie., we're not in the middle of a longer transaction), it will
610 611
  # automatically reconnect and try again. If autocommit is turned off,
  # this would be dangerous (as the earlier part of the implied transaction
612
  # may have failed silently if the connection died) -- so instead the
613
  # connection is marked as dead, to be reconnected on it's next use.
614
  class OCI8AutoRecover < DelegateClass(OCI8) #:nodoc:
615 616 617 618 619 620 621 622 623
    attr_accessor :active
    alias :active? :active

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

624
    def initialize(config, factory = OracleConnectionFactory.new)
625
      @active = true
626 627
      @username, @password, @database, = config[:username], config[:password], config[:database]
      @async = config[:allow_concurrency]
628 629
      @prefetch_rows = config[:prefetch_rows] || 100
      @cursor_sharing = config[:cursor_sharing] || 'similar'
630
      @factory = factory
631
      @connection  = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
632 633 634 635 636 637 638
      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
639
      @connection.exec("select 1 from dual") { |r| nil }
640
      @active = true
641 642 643
    rescue
      @active = false
      raise
644 645 646 647 648 649
    end

    # Resets connection, by logging off and creating a new connection.
    def reset!
      logoff rescue nil
      begin
650
        @connection = @factory.new_connection @username, @password, @database, @async, @prefetch_rows, @cursor_sharing
651 652 653 654 655 656 657 658 659
        __setobj__ @connection
        @active = true
      rescue
        @active = false
        raise
      end
    end

    # ORA-00028: your session has been killed
660
    # ORA-01012: not logged on
661 662 663 664 665 666 667
    # 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
668
    def exec(sql, *bindvars, &block)
669 670 671
      should_retry = self.class.auto_retry? && autocommit?

      begin
672
        @connection.exec(sql, *bindvars, &block)
673
      rescue OCIException => e
674 675 676 677 678 679 680 681 682 683 684
        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

685 686
rescue LoadError
  # OCI8 driver is unavailable.
687
  module ActiveRecord # :nodoc:
688
    class Base
689
      @@oracle_error_message = "Oracle/OCI libraries could not be loaded: #{$!.to_s}"
690 691
      def self.oracle_connection(config) # :nodoc:
        # Set up a reasonable error message
692
        raise LoadError, @@oracle_error_message
693
      end
694 695
      def self.oci_connection(config) # :nodoc:
        # Set up a reasonable error message
696
        raise LoadError, @@oracle_error_message
697 698 699
      end
    end
  end
700
end