# frozen_string_literal: true

module ActiveRecord
  # Represents exceptions that have propagated up through the JDBC API.
  class JDBCError < WrappedDatabaseException

    def initialize(message = nil, cause = $!)
      super( ( message.nil? && cause ) ? cause.message : message )
      if cause.is_a? Java::JavaSql::SQLException
        @jdbc_exception, @cause = cause, nil
      else
        @cause, @jdbc_exception = cause, nil
      end
    end

    # The DB (or JDBC driver implementation specific) vendor error code.
    # @see #jdbc_exception
    # @return [Integer, NilClass]
    def error_code
      if ( @error_code ||= nil ).nil?
        @error_code = jdbc_exception ? jdbc_exception.getErrorCode : nil
      else
        @error_code
      end
    end
    # @deprecated
    # @see #error_code
    def errno; error_code end

    # SQL code as standardized by ISO/ANSI and Open Group (X/Open), although
    # some codes have been reserved for DB vendors to define for themselves.
    # @see #jdbc_exception
    # @return [String, NilClass]
    def sql_state; jdbc_exception ? jdbc_exception.getSQLState : nil end

    # The full Java exception (SQLException) object that was raised (if any).
    # @note Navigate through chained exceptions using `jdbc_exception.next_exception`.
    def jdbc_exception; @jdbc_exception end
    alias_method :sql_exception, :jdbc_exception

    # true if the current error might be recovered e.g. by re-trying the transaction
    def recoverable?; jdbc_exception.is_a?(Java::JavaSql::SQLRecoverableException) end
    # true when a failed operation might be able to succeed when retried (e.g. timeouts)
    def transient?; jdbc_exception.is_a?(Java::JavaSql::SQLTransientException) end

    # Likely (but not necessarily) the same as {#jdbc_exception}.
    def cause; ( @cause ||= nil ) || jdbc_exception end
    # @override
    # @private for correct super-class (StatementInvalid) compatibility
    alias_method :original_exception, :cause

    # @override
    def set_backtrace(backtrace)
      @raw_backtrace = backtrace
      if ( nested = cause ) && ! nested.equal?(self)
        backtrace = backtrace - ( nested.respond_to?(:raw_backtrace) ? nested.raw_backtrace : nested.backtrace )
        backtrace << "#{nested.backtrace.first}: #{nested.message} (#{nested.class.name})"
        backtrace.concat nested.backtrace[1..-1] || []
      end
      super(backtrace)
    end

    # @private
    def raw_backtrace; @raw_backtrace ||= backtrace end

  end
end
