rsim/oracle-enhanced

View on GitHub
lib/active_record/connection_adapters/oracle_enhanced/connection.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# frozen_string_literal: true

module ActiveRecord
  module ConnectionAdapters
    # interface independent methods
    module OracleEnhanced
      class Connection # :nodoc:
        def self.create(config)
          case ORACLE_ENHANCED_CONNECTION
          when :oci
            OracleEnhanced::OCIConnection.new(config)
          when :jdbc
            OracleEnhanced::JDBCConnection.new(config)
          else
            nil
          end
        end

        attr_reader :raw_connection

        private
          # Used always by JDBC connection as well by OCI connection when describing tables over database link
          def describe(name)
            name = name.to_s
            if name.include?("@")
              raise ArgumentError "db link is not supported"
            else
              default_owner = @owner
            end
            real_name = OracleEnhanced::Quoting.valid_table_name?(name) ? name.upcase : name
            if real_name.include?(".")
              table_owner, table_name = real_name.split(".")
            else
              table_owner, table_name = default_owner, real_name
            end
            sql = <<~SQL.squish
              SELECT owner, table_name, 'TABLE' name_type
              FROM all_tables
              WHERE owner = :table_owner
                AND table_name = :table_name
              UNION ALL
              SELECT owner, view_name table_name, 'VIEW' name_type
              FROM all_views
              WHERE owner = :table_owner
                AND view_name = :table_name
              UNION ALL
              SELECT table_owner, table_name, 'SYNONYM' name_type
              FROM all_synonyms
              WHERE owner = :table_owner
                AND synonym_name = :table_name
              UNION ALL
              SELECT table_owner, table_name, 'SYNONYM' name_type
              FROM all_synonyms
              WHERE owner = 'PUBLIC'
                AND synonym_name = :real_name
            SQL
            if result = _select_one(sql, "CONNECTION", [table_owner, table_name, table_owner, table_name, table_owner, table_name, real_name])
              case result["name_type"]
              when "SYNONYM"
                describe("#{result['owner'] && "#{result['owner']}."}#{result['table_name']}")
              else
                [result["owner"], result["table_name"]]
              end
            else
              raise OracleEnhanced::ConnectionException, %Q{"DESC #{name}" failed; does it exist?}
            end
          end

          # 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.
          #
          # `_oracle_downcase` is expected to be called only from
          # `ActiveRecord::ConnectionAdapters::OracleEnhanced::OCIConnection`
          # or `ActiveRecord::ConnectionAdapters::OracleEnhanced::JDBCConnection`.
          # Other method should call `ActiveRecord:: ConnectionAdapters::OracleEnhanced::Quoting#oracle_downcase`
          # since this is kind of quoting, not connection.
          # To avoid it is called from anywhere else, added _ at the beginning of the method name.
          def _oracle_downcase(column_name)
            return nil if column_name.nil?
            /[a-z]/.match?(column_name) ? column_name : column_name.downcase
          end

          # _select_one and _select_value methods are expected to be called
          # only from `ActiveRecord::ConnectionAdapters::OracleEnhanced::Connection#describe`
          # Other methods should call `ActiveRecord::ConnectionAdapters::DatabaseStatements#select_one`
          # and  `ActiveRecord::ConnectionAdapters::DatabaseStatements#select_value`
          # To avoid called from its subclass added a underscore in each method.

          # Returns a record hash with the column names as keys and column values
          # as values.
          # binds is a array of native values in contrast to ActiveRecord::Relation::QueryAttribute
          def _select_one(arel, name = nil, binds = [])
            cursor = prepare(arel)
            cursor.bind_params(binds)
            cursor.exec
            columns = cursor.get_col_names.map do |col_name|
              _oracle_downcase(col_name)
            end
            row = cursor.fetch
            columns.each_with_index.to_h { |x, i| [x, row[i]] } if row
          ensure
            cursor.close
          end

          # Returns a single value from a record
          def _select_value(arel, name = nil, binds = [])
            if result = _select_one(arel, name, binds)
              result.values.first
            end
          end
      end

      # Returns array with major and minor version of database (e.g. [12, 1])
      def database_version
        raise NoMethodError, "Not implemented for this raw driver"
      end
      class ConnectionException < StandardError # :nodoc:
      end
    end
  end
end

# if MRI or YARV or TruffleRuby
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby" || RUBY_ENGINE == "truffleruby"
  ORACLE_ENHANCED_CONNECTION = :oci
  require "active_record/connection_adapters/oracle_enhanced/oci_connection"
# if JRuby
elsif RUBY_ENGINE == "jruby"
  ORACLE_ENHANCED_CONNECTION = :jdbc
  require "active_record/connection_adapters/oracle_enhanced/jdbc_connection"
else
  raise "Unsupported Ruby engine #{RUBY_ENGINE}"
end