#
# See DBI::BaseStatement.
#
class DBI::DBD::ODBC::Statement < DBI::BaseStatement
    def initialize(handle, statement)
        @statement = statement
        @handle = handle
        @params = []
        @arr = []
    end

    #
    # See DBI::BaseStatement#bind_param. This method will also raise
    # DBI::InterfaceError if +param+ is not a Fixnum, to prevent incorrect
    # binding.
    #
    def bind_param(param, value, attribs)
        raise DBI::InterfaceError, "only ? parameters supported" unless param.is_a? Fixnum
        @params[param-1] = value
    end

    def execute
        @handle.execute(*@params)
    rescue DBI::DBD::ODBC::ODBCErr => err
        raise DBI::DatabaseError.new(err.message)
    end

    def finish
        @handle.drop
    rescue DBI::DBD::ODBC::ODBCErr => err
        raise DBI::DatabaseError.new(err.message)
    end

    def cancel
        @handle.cancel
    rescue DBI::DBD::ODBC::ODBCErr => err
        raise DBI::DatabaseError.new(err.message)
    end

    def fetch
        convert_row(@handle.fetch)
    rescue DBI::DBD::ODBC::ODBCErr => err
        raise DBI::DatabaseError.new(err.message)
    end

    #
    # See DBI::BaseStatement#fetch_scroll.
    #
    # ODBC has a native version of this method and the constnats in the ODBC
    # driver themselves are supported. If you'd prefer to use DBI constants
    # (recommended), you can use these which map to the ODBC functionality:
    #
    # * DBI::SQL_FETCH_FIRST
    # * DBI::SQL_FETCH_LAST
    # * DBI::SQL_FETCH_NEXT
    # * DBI::SQL_FETCH_PRIOR
    # * DBI::SQL_FETCH_ABSOLUTE
    # * DBI::SQL_FETCH_RELATIVE
    #
    def fetch_scroll(direction, offset)
        direction = case direction
                    when DBI::SQL_FETCH_FIRST    then ::ODBC::SQL_FETCH_FIRST
                    when DBI::SQL_FETCH_LAST     then ::ODBC::SQL_FETCH_LAST
                    when DBI::SQL_FETCH_NEXT     then ::ODBC::SQL_FETCH_NEXT
                    when DBI::SQL_FETCH_PRIOR    then ::ODBC::SQL_FETCH_PRIOR
                    when DBI::SQL_FETCH_ABSOLUTE then ::ODBC::SQL_FETCH_ABSOLUTE
                    when DBI::SQL_FETCH_RELATIVE then ::ODBC::SQL_FETCH_RELATIVE
                    else
                        direction
                    end

        convert_row(@handle.fetch_scroll(direction, offset))
    rescue DBI::DBD::ODBC::ODBCErr => err
        raise DBI::DatabaseError.new(err.message)
    end

    #
    # See DBI::BaseStatement#column_info. These additional attributes are also
    # supported:
    #
    # * table: the table this column came from, if available.
    # * nullable: boolean, true if NULL is accepted as a value in this column.
    # * searchable: FIXME DOCUMENT
    # * length: FIXME DOCUMENT
    # * unsigned: For numeric columns, whether or not the result value is signed.
    #
    def column_info
        info = []
        @handle.columns(true).each do |col|
            info << {
                'name'       => col.name, 
                'table'      => col.table,
                'nullable'   => col.nullable,
                'searchable' => col.searchable,
                'precision'  => col.precision,
                'scale'      => col.scale,
                'sql_type'   => col.type,
                'type_name'  => DBI::SQL_TYPE_NAMES[col.type],
                'length'     => col.length,
                'unsigned'   => col.unsigned
            }
        end
        info
    rescue DBI::DBD::ODBC::ODBCErr => err
        raise DBI::DatabaseError.new(err.message)
    end

    #
    # See DBI::BaseStatement#rows.
    #
    # For queries which DBI::SQL.query? returns true, will explicitly return 0.
    # Otherwise, it will return the row processed count.
    #
    def rows
        return 0 if DBI::SQL.query?(@statement)
        return @handle.nrows
    rescue DBI::DBD::ODBC::ODBCErr => err
        raise DBI::DatabaseError.new(err.message)
    end

    private # -----------------------------------

    # convert the ODBC datatypes to DBI datatypes
    def convert_row(row)
        return nil if row.nil?
        row.collect do |col|
            case col
            when nil
                nil
            when ODBC::TimeStamp
                DBI::Type::Timestamp.create col.year, col.month, col.day, col.hour, col.minute, col.second
            else
                col.to_s
            end
        end
    end 
end
