/*
 * Copyright (C) 2008 Search Solution Corporation. All rights reserved by Search Solution.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * - Neither the name of the <ORGANIZATION> nor the names of its contributors
 *   may be used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 */

#include "cubrid.h"
#include <fcntl.h>

#ifdef WIN32
#define  open(file, flag, mode) PerlLIO_open3(file, flag, mode)
#endif

/* Loading dynamic cascci library needs this header. */
#ifdef WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif

#define CUBRID_ER_MSG_LEN 1024
#define CUBRID_BUFFER_LEN 4096

static struct _error_message {
    int err_code;
    char *err_msg;
} cubrid_err_msgs[] = {
    {CUBRID_ER_CANNOT_GET_COLUMN_INFO, "Cannot get column info"},
    {CUBRID_ER_CANNOT_FETCH_DATA, "Cannot fetch data"},
    {CUBRID_ER_WRITE_FILE, "Cannot write file"},
    {CUBRID_ER_READ_FILE, "Cannot read file"},
    {CUBRID_ER_NOT_LOB_TYPE, "Not a lob type, can only support SQL_BLOB or SQL_CLOB"},
    {CUBRID_ER_INVALID_PARAM, "Invalid parameter"},
    {CUBRID_ER_ROW_INDEX_EXCEEDED, "Row index exceeds the allowed range(1 ~ the number of affected rows)"},
    {CUBRID_ER_EXPORT_NULL_LOB_INVALID, "Exporting NULL LOB is invalid"},
    {0, ""}
};

static void *libcascci = NULL;
typedef int (*CCI_GET_LAST_INSERT_ID) (int con, void *buff,
                                       T_CCI_ERROR * err_buf);
static CCI_GET_LAST_INSERT_ID cci_get_last_insert_id_fp = NULL;


/***************************************************************************
 * Private function prototypes
 **************************************************************************/

static int _dbd_db_end_tran (SV *dbh, imp_dbh_t *imp_dbh, int type);

static int _cubrid_lob_bind (SV *sv,
                             int index,
                             IV sql_type,
                             char *buf,
                             T_CCI_ERROR *error);
static int _cubrid_lob_new (int conn, 
                            T_CCI_LOB *lob, 
                            T_CCI_U_TYPE type, 
                            T_CCI_ERROR *error);
static long long _cubrid_lob_size( T_CCI_LOB lob, T_CCI_U_TYPE type );
static int _cubrid_lob_write (int conn, 
                              T_CCI_LOB lob, 
                              T_CCI_U_TYPE type, 
                              long long start_pos, 
                              int length, 
                              const char *buf, 
                              T_CCI_ERROR *error);
static int _cubrid_lob_read (int conn, 
                             T_CCI_LOB lob, 
                             T_CCI_U_TYPE type, 
                             long long start_pos, 
                             int length, 
                             char *buf, 
                             T_CCI_ERROR *error);
static int _cubrid_lob_free (T_CCI_LOB lob, T_CCI_U_TYPE type);

static int _cubrid_fetch_schema (AV *rows_av, 
                                 int req_handle, 
                                 int col_count, 
                                 T_CCI_COL_INFO *col_info, 
                                 T_CCI_ERROR *error);
static int _cubrid_fetch_row (AV *av, 
                              int req_handle, 
                              int col_count, 
                              T_CCI_COL_INFO *col_info, 
                              T_CCI_ERROR *error);

/***************************************************************************
 * 
 * Name:    dbd_init
 * 
 * Purpose: Called when the driver is installed by DBI
 *
 * Input:   dbistate - pointer to the DBIS variable, used for some
 *                     DBI internal things
 *
 * Returns: Nothing
 *
 **************************************************************************/

void
dbd_init( dbistate_t *dbistate )
{
    DBIS = dbistate;
    cci_init();

    /* 
     * use cci_get_last_id if possible
     * early distributed libraries don't include it
     */
#ifdef WIN32
    libcascci = LoadLibrary ("cascci.dll");

    if (libcascci != NULL) {
        cci_get_last_insert_id_fp =
            (CCI_GET_LAST_INSERT_ID) GetProcAddress (libcascci,
                                                 "cci_get_last_insert_id");
    }
#else
    libcascci = dlopen ("libcascci.so", RTLD_LAZY);

    if (libcascci != NULL) {
        cci_get_last_insert_id_fp = dlsym (libcascci, "cci_get_last_insert_id");
    }
#endif
}

/***************************************************************************
 * 
 * Name:    dbd_discon_all
 * 
 * Purpose: Disconnect all database handles at shutdown time
 *
 * Input:   dbh - database handle being disconnecte
 *          imp_dbh - drivers private database handle data
 * 
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_discon_all( SV *drh, imp_drh_t *imp_drh )
{
    cci_end ();

    if (!PL_dirty && !SvTRUE(perl_get_sv("DBI::PERL_ENDING",0))) {
        sv_setiv(DBIc_ERR(imp_drh), (IV)1);
        sv_setpv(DBIc_ERRSTR(imp_drh), (char*)"disconnect_all not implemented");
        return FALSE;
    }

    if (libcascci != NULL) {
#ifdef WIN32
        FreeLibrary (libcascci);
#else
        dlclose (libcascci);
#endif
    }

    return FALSE;
}


/***************************************************************************
 * 
 * Name:    handle_error
 * 
 * Purpose: Called to associate an error code and an error message 
 *          to some handle
 *
 * Input:   h - the handle in error condition
 *          e - the error code
 *          error - the error message
 *
 * Returns: Nothing
 *
 **************************************************************************/

static int
get_error_msg( int err_code, char *err_msg )
{
    int i;

    for (i = 0; ; i++) {
        if (!cubrid_err_msgs[i].err_code)
            break;
        if (cubrid_err_msgs[i].err_code == err_code) {
            snprintf (err_msg, CUBRID_ER_MSG_LEN, 
                        "ERROR: CLIENT, %d, %s", 
                        err_code, cubrid_err_msgs[i].err_msg);
            return 0;
        }
    }
    return -1;
}

static void
handle_error( SV* h, int e, T_CCI_ERROR *error )
{
    D_imp_xxh (h);
    char msg[CUBRID_ER_MSG_LEN] = {'\0'};
    SV *errstr;

    if (DBIc_TRACE_LEVEL (imp_xxh) >= 2)
        PerlIO_printf (DBILOGFP, "\t\t--> handle_error\n");

    errstr = DBIc_ERRSTR (imp_xxh);
    sv_setiv (DBIc_ERR (imp_xxh), (IV)e);

    if (e < CUBRID_ER_START) {
        get_error_msg (e, msg);
    } else if (cci_get_error_msg (e, error, msg, CUBRID_ER_MSG_LEN) < 0) {
        snprintf (msg, CUBRID_ER_MSG_LEN, "Unknown Error");
    }     

    sv_setpv (errstr, msg);

    if (DBIc_TRACE_LEVEL (imp_xxh) >= 2)
        PerlIO_printf (DBIc_LOGPIO (imp_xxh), "%s\n", msg);

    if (DBIc_TRACE_LEVEL (imp_xxh) >= 2)
        PerlIO_printf (DBILOGFP, "\t\t<-- handle_error\n");

    return;
}

/***************************************************************************
 * 
 * Name:    dbd_db_login6
 * 
 * Purpose: Called for connecting to a database and logging in.
 *
 * Input:   dbh - database handle being initialized
 *          imp_dbh - drivers private database handle data
 *          dbname - the database we want to log into; may be like
 *                   "dbname:host" or "dbname:host:port"
 *          user - user name to connect as
 *          password - passwort to connect with
 *
 * Returns: Nothing
 *
 **************************************************************************/

int
dbd_db_login6( SV *dbh, imp_dbh_t *imp_dbh,
        char *dbname, char *uid, char *pwd, SV *attr )
{
    int  con, res;
    T_CCI_ERROR error;

    if ((con = cci_connect_with_url_ex (dbname, uid, pwd, &error)) < 0)
      {
        handle_error (dbh, con, &error);
        return FALSE;
      }

    imp_dbh->handle = con;

    if ((res = cci_end_tran (con, CCI_TRAN_COMMIT, &error)) < 0)
      {
        handle_error (dbh, res, &error);
        return FALSE;
      }

    DBIc_IMPSET_on(imp_dbh);
    DBIc_ACTIVE_on(imp_dbh);

    return TRUE;  
}

/***************************************************************************
 *
 * Name:    dbd_db_commit
 *          dbd_db_rollback
 *
 * Purpose: Commit/Rollback any pending transaction to the database.
 *
 * Input:   dbh - database handle being commit/rollback
 *          imp_dbh - drivers private database handle data
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_db_commit( SV *dbh, imp_dbh_t *imp_dbh )
{
    return _dbd_db_end_tran (dbh, imp_dbh, CCI_TRAN_COMMIT);
}

int
dbd_db_rollback( SV *dbh, imp_dbh_t *imp_dbh )
{
    return _dbd_db_end_tran (dbh, imp_dbh, CCI_TRAN_ROLLBACK);
}

static int
_dbd_db_end_tran( SV *dbh, imp_dbh_t *imp_dbh, int type )
{
    int res;
    T_CCI_ERROR error;

    if ((res = cci_end_tran (imp_dbh->handle, type, &error)) < 0) {
        handle_error (dbh, res, &error);
        return FALSE;
    }
    return TRUE;
}

/***************************************************************************
 *
 * Name:    dbd_db_destroy
 *
 * Purpose: Our part of the dbh destructor
 *
 * Input:   dbh - database handle being disconnected
 *          imp_dbh - drivers private database handle data
 *
 * Returns: Nothing
 *
 **************************************************************************/

void
dbd_db_destroy( SV *dbh, imp_dbh_t *imp_dbh )
{
    if (DBIc_ACTIVE(imp_dbh)) {
        (void)dbd_db_disconnect(dbh, imp_dbh);
    }

    DBIc_IMPSET_off(imp_dbh);
}

/***************************************************************************
 *
 * Name:    dbd_db_disconnect
 *
 * Purpose: Disconnect a database handle from its database
 *
 * Input:   dbh - database handle being disconnected
 *          imp_dbh - drivers private database handle data
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_db_disconnect( SV *dbh, imp_dbh_t *imp_dbh )
{
    int res;
    T_CCI_ERROR error;

    DBIc_ACTIVE_off(imp_dbh);

    if ((res = cci_disconnect (imp_dbh->handle, &error)) < 0) {
        handle_error (dbh, res, &error);
        return FALSE;
    }

    return TRUE;
}

/***************************************************************************
 *
 * Name:    dbd_db_STORE_attrib
 *
 * Purpose: Function for storing dbh attributes
 *
 * Input:   dbh - database handle being disconnected
 *          imp_dbh - drivers private database handle data
 *          keysv - the attribute name
 *          valuesv - the attribute value
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_db_STORE_attrib( SV *dbh, imp_dbh_t *imp_dbh, SV *keysv, SV *valuesv )
{
    STRLEN kl;
    char *key = SvPV (keysv,kl);
    int on = SvTRUE (valuesv);

    switch (kl) {
    case 10:
        if (strEQ("AutoCommit", key)) 
        {
            if (on)
            {
                cci_set_autocommit (imp_dbh->handle, CCI_AUTOCOMMIT_TRUE);
            }
            else 
            {
                cci_set_autocommit (imp_dbh->handle, CCI_AUTOCOMMIT_FALSE);
            }

            DBIc_set (imp_dbh, DBIcf_AutoCommit, on);
            return TRUE;
        }
    }
    return FALSE;
}

/***************************************************************************
 *
 * Name:    dbd_db_FETCH_attrib
 *
 * Purpose: Function for fetching dbh attributes
 *
 * Input:   dbh - database handle being disconnected
 *          imp_dbh - drivers private database handle data
 *          keysv - the attribute name
 *          valuesv - the attribute value
 *
 * Returns: An SV*, if sucessfull; NULL otherwise
 *
 **************************************************************************/

SV * 
dbd_db_FETCH_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv)
{
    STRLEN kl;
    char *key = SvPV (keysv, kl);
    SV *retsv = Nullsv;
    
    switch (kl) {
    case 10:
        if (strEQ("AutoCommit", key)) {
            retsv = boolSV(DBIc_has(imp_dbh,DBIcf_AutoCommit));
        }
        break;
    }
    return sv_2mortal(retsv);
}

/**************************************************************************
 *
 * Name:    dbd_db_last_insert_id
 *
 * Purpose: Returns a value identifying the row just inserted 
 *
 * Input:   dbh - database handle
 *          imp_dbh - drivers private database handle data
 *          catalog - NULL
 *          schema  - NULL
 *          table   - NULL
 *          field   - NULL
 *          attr    - NULL
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

SV *
dbd_db_last_insert_id( SV *dbh, imp_dbh_t *imp_dbh,
        SV *catalog, SV *schema, SV *table, SV *field, SV *attr )
{
    SV *sv;
    char *name = NULL;
    int res;
    T_CCI_ERROR error;

    if (cci_get_last_insert_id_fp != NULL) {
        /* cci_get_last_id set last_id as con_handle's static buffer */
        res = cci_get_last_insert_id (imp_dbh->handle, &name, &error);
    } else {
        /* cci_last_id set last_id as allocated string */
        res = cci_last_insert_id (imp_dbh->handle, &name, &error);
    }
    
    if (res < 0) {
        handle_error (dbh, res, &error);
        return Nullsv;
    }

    if (!name) {
        return Nullsv;
    } else {
        sv = newSVpvn (name, strlen(name));

        /* free last_id which is allocated in cci_last_insert_id */
        if (cci_get_last_insert_id_fp == NULL) {
#ifndef WIN32
            free(name);
#endif
        }
    }

    return sv_2mortal (sv);
}

/**************************************************************************
 *
 * Name:    dbd_db_ping
 *
 * Purpose: Check whether the database server is still running and the 
 *          connection to it is still working.
 *
 * Input:   Nothing
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_db_ping( SV *dbh )
{
    int res;
    T_CCI_ERROR error;
    char *query = "SELECT 1+1 from db_root /*+ shard_id(0) */";
    int req_handle = 0, result = 0, ind = 0;

    D_imp_dbh (dbh);

    if ((res = cci_prepare (imp_dbh->handle, query, 0, &error)) < 0) {
		goto ER_DB_PING;
    }

    req_handle = res;

    if ((res = cci_execute (req_handle, 0, 0, &error)) < 0) {
        goto ER_DB_PING;
    }

    while (1) {
        res = cci_cursor (req_handle, 1, CCI_CURSOR_CURRENT, &error);
        if (res == CCI_ER_NO_MORE_DATA) {
            break;
        }
        if (res < 0) {
            goto ER_DB_PING;
        }

        if ((res = cci_fetch (req_handle, &error)) < 0) {
            goto ER_DB_PING;
        }

        if ((res = cci_get_data (req_handle, 1, CCI_A_TYPE_INT, &result, &ind)) < 0) {
            goto ER_DB_PING;
        }

        if (result == 2) {
            cci_close_req_handle (req_handle);
            return TRUE;
        }
    }
	cci_close_req_handle (req_handle);
ER_DB_PING:
    handle_error (dbh, res, &error);
    return FALSE;
}

/***************************************************************************
 *
 * Name:    dbd_st_prepare
 *
 * Purpose: Called for preparing an SQL statement; our part of the
 *          statement handle constructor
 *
 * Input:   sth - statement handle being initialized
 *          imp_sth - drivers private statement handle data
 *          statement - pointer to string with SQL statement
 *          attribs - statement attributes, currently not in use
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_st_prepare( SV *sth, imp_sth_t *imp_sth, char *statement, SV *attribs )
{
    int res;
    T_CCI_ERROR error;

    D_imp_dbh_from_sth;

    if ('\0' == *statement)
        croak ("Cannot preapre empty statement");

    imp_sth->conn = imp_dbh->handle;

    imp_sth->col_count = -1;
    imp_sth->sql_type = 0;
    imp_sth->affected_rows = -1;
    imp_sth->lob = NULL;

    if ((res = cci_prepare (imp_sth->conn, statement, 0, &error)) < 0) {
        handle_error (sth, res, &error);
        return FALSE;
    }

    imp_sth->handle = res;

    DBIc_NUM_PARAMS(imp_sth) = cci_get_bind_num (res);

    DBIc_IMPSET_on(imp_sth);

    return TRUE;
}

/***************************************************************************
 *
 * Name:    dbd_st_execute
 *
 * Purpose: Called for execute the prepared SQL statement; our part of
 *          the statement handle constructor
 *
 * Input:   sth - statement handle
 *          imp_sth - drivers private statement handle data
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_st_execute( SV *sth, imp_sth_t *imp_sth )
{
    int res, option = 0, max_col_size = 0;
    T_CCI_ERROR error;
    T_CCI_COL_INFO *col_info;
    T_CCI_SQLX_CMD sql_type;
    int col_count;

    if ((res = cci_execute (imp_sth->handle, option, 
                    max_col_size, &error)) < 0) {
        handle_error (sth, res, &error);
        return -2;
    }

    col_info = cci_get_result_info (imp_sth->handle, &sql_type, &col_count);
    if (sql_type == SQLX_CMD_SELECT && !col_info) {
        handle_error(sth, CUBRID_ER_CANNOT_GET_COLUMN_INFO, NULL);
        return -2;
    }

    imp_sth->col_info = col_info;
    imp_sth->sql_type = sql_type;
    imp_sth->col_count = col_count;

    switch (sql_type) {
    case SQLX_CMD_INSERT:
    case SQLX_CMD_UPDATE:
    case SQLX_CMD_DELETE:
    case SQLX_CMD_CALL:
    case SQLX_CMD_SELECT:
        imp_sth->affected_rows = res;
        break;
    default:
        imp_sth->affected_rows = -1;
    }

    if (sql_type == SQLX_CMD_SELECT) {
        res = cci_cursor (imp_sth->handle, 1, CCI_CURSOR_CURRENT, &error);
        if (res < 0 && res != CCI_ER_NO_MORE_DATA) {
            handle_error (sth, res, &error);
            return -2;
        }

        DBIc_NUM_FIELDS (imp_sth) = col_count;
        DBIc_ACTIVE_on (imp_sth);
    }
    
    return imp_sth->affected_rows;
}

/***************************************************************************
 *
 * Name:    dbd_st_fetch
 *
 * Purpose: Called for execute the prepared SQL statement; our part of
 *          the statement handle constructor
 *
 * Input:   sth - statement handle being initialized
 *          imp_sth - drivers private statement handle data
 *
 * Returns: array of columns; the array is allocated by DBI via
 *          DBIS->get_fbav(imp_sth), even the values of the array
 *          are prepared, we just need to modify them appropriately
 *
 **************************************************************************/

AV *
dbd_st_fetch( SV *sth, imp_sth_t *imp_sth )
{
    AV *av;
    int res;
    T_CCI_ERROR error;

    if (DBIc_ACTIVE(imp_sth)) {
        DBIc_ACTIVE_off(imp_sth);
    }

    res = cci_cursor (imp_sth->handle, 0, CCI_CURSOR_CURRENT, &error);
    if (res == CCI_ER_NO_MORE_DATA) {
        return Nullav;
    } else if (res < 0) {
        goto ERR_ST_FETCH;
    }

    if ((res = cci_fetch (imp_sth->handle, &error)) < 0) {
        goto ERR_ST_FETCH;
    }

    av = DBIS->get_fbav(imp_sth);
    if ((res = _cubrid_fetch_row (av, 
                                  imp_sth->handle, 
                                  imp_sth->col_count, 
                                  imp_sth->col_info, 
                                  &error)) < 0) {
        goto ERR_ST_FETCH;
    }

    res = cci_cursor (imp_sth->handle, 1, CCI_CURSOR_CURRENT, &error);
    if (res < 0 && res != CCI_ER_NO_MORE_DATA) {
        goto ERR_ST_FETCH;
    }

    return av;
ERR_ST_FETCH:
    handle_error (sth, res, &error);
    return Nullav;	  
}

/***************************************************************************
 *
 * Name:    dbd_st_finish
 *
 * Purpose: Called for freeing a CUBRID result
 *
 * Input:   sth - statement handle being finished
 *          imp_sth - drivers private statement handle data
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_st_finish( SV *sth, imp_sth_t *imp_sth )
{
    if (!DBIc_ACTIVE(imp_sth))
        return TRUE;

    DBIc_ACTIVE_off(imp_sth);

    return TRUE;
}

/***************************************************************************
 *
 * Name:    dbd_st_destroy
 *
 * Purpose: Our part of the statement handles destructor
 *
 * Input:   sth - statement handle being destroyed
 *          imp_sth - drivers private statement handle data
 *
 * Returns: Nothing
 *
 **************************************************************************/

void
dbd_st_destroy( SV *sth, imp_sth_t *imp_sth )
{
    if (imp_sth->handle) {
        if (imp_sth->lob) {
            int i;
            for (i = 0; i < imp_sth->affected_rows; i++) {
                _cubrid_lob_free (imp_sth->lob[i].lob, imp_sth->lob[i].type);
            }

            free (imp_sth->lob);
            imp_sth->lob = NULL;
        }

        cci_close_req_handle (imp_sth->handle);
        imp_sth->handle = 0;

        imp_sth->col_count = -1;
        imp_sth->sql_type = 0;
        imp_sth->affected_rows = -1;
    }

    DBIc_IMPSET_off(imp_sth);

    return;
}

/***************************************************************************
 *
 * Name:    dbd_st_STORE_attrib
 *
 * Purpose: Modifies a statement handles attributes
 *
 * Input:   sth - statement handle being initialized
 *          imp_sth - drivers private statement handle data
 *          keysv - attribute name
 *          valuesv - attribute value
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_st_STORE_attrib( SV *sth, imp_sth_t *imp_sth, SV *keysv, SV *valuesv )
{
    return TRUE;
}

/***************************************************************************
 *
 * Name:    dbd_st_FETCH_attrib
 *
 * Purpose: Retrieves a statement handles attributes
 *
 * Input:   sth - statement handle
 *          imp_sth - drivers private statement handle data
 *          keysv - attribute name
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

SV *
dbd_st_FETCH_attrib( SV *sth, imp_sth_t *imp_sth, SV *keysv )
{
    SV *retsv = Nullsv;
    STRLEN kl;
    char *key = SvPV (keysv, kl);
    int i;
    char col_name[128] = {'\0'};

    switch (kl) {
    case 4:
        if (strEQ ("NAME", key)) {
            AV *av = newAV ();
            retsv = newRV_inc (sv_2mortal ((SV *)av));
            for (i = 1; i<= imp_sth->col_count; i++) {
                strcpy (col_name, 
                        CCI_GET_RESULT_INFO_NAME (imp_sth->col_info, i));
                av_store (av, i-1, newSVpv (col_name, 0));
            }
        }
        else if (strEQ("TYPE", key)) {
            int type;
            AV *av = newAV ();
            retsv = newRV_inc (sv_2mortal ((SV *)av));
            for (i = 1; i<= imp_sth->col_count; i++) {
                type = CCI_GET_RESULT_INFO_TYPE (imp_sth->col_info, i);
                av_store (av, i-1, newSViv (type));
            }
        }
        break;
    case 5:
        if (strEQ ("SCALE", key)) {
            AV *av = newAV ();
            int scale;
            retsv = newRV_inc (sv_2mortal ((SV *)av));
            for (i = 1; i<= imp_sth->col_count; i++) {
                scale = CCI_GET_RESULT_INFO_SCALE (imp_sth->col_info, i);
                av_store (av, i-1, newSViv (scale));
            }
        }
        break;
    case 8:
        if (strEQ ("NULLABLE", key)) {
            AV *av = newAV ();
            int not_null;
            retsv = newRV_inc (sv_2mortal ((SV *)av));
            for (i = 1; i<= imp_sth->col_count; i++) {
                not_null = CCI_GET_RESULT_INFO_IS_NON_NULL (imp_sth->col_info, i) ? 0 : 1;
                av_store (av, i-1, newSViv (not_null));
            }
        }
        break;
    case 9:
        if (strEQ ("PRECISION", key)) {
            AV *av = newAV ();
            int precision;
            retsv = newRV_inc (sv_2mortal ((SV *)av));
            for (i = 1; i<= imp_sth->col_count; i++) {
                precision = CCI_GET_RESULT_INFO_PRECISION (imp_sth->col_info, i);
                av_store (av, i-1, newSViv (precision));
            }
        }
        break;
    }

    return sv_2mortal (retsv);
}

/***************************************************************************
 *
 * Name:    dbd_st_blob_read (Not implement now)
 *
 * Purpose: Used for blob reads if the statement handles blob 
 *
 * Input:   sth - statement handle from which a blob will be 
 *                fetched (currently not supported by DBD::cubrid)
 *          imp_sth - drivers private statement handle data
 *          field - field number of the blob
 *          offset - the offset of the field, where to start reading
 *          len - maximum number of bytes to read
 *          destrv - RV* that tells us where to store
 *          destoffset - destination offset
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_st_blob_read(SV *sth, imp_sth_t *imp_sth,
        int field, long offset, long len, SV *destrv, long destoffset)
{
    return FALSE;
}

/***************************************************************************
 *
 * Name:    dbd_st_rows
 *
 * Purpose: used to get the number of rows affected by the SQL statement
 *          (INSERT, DELETE, UPDATE).
 *
 * Input:   sth - statement handle
 *          imp_sth - drivers private statement handle data
 *
 * Returns: Number of rows affected by the SQL statement for success.
 *          -1, when SQL statement is not INSERT, DELETE or UPDATE.
 *
 **************************************************************************/

int
dbd_st_rows( SV * sth, imp_sth_t * imp_sth )
{
    return imp_sth->affected_rows;
}
/***************************************************************************
 *
 * Name:    cubrid_str2bit
 *
 * Purpose: convert string to bit
 *
 **************************************************************************/
static char* cubrid_str2bit(char* str)
{
    int i=0,len=0,t=0;
    char* buf=NULL;
    int shift = 8;

    if(str == NULL)
        return NULL;
    len = strlen(str);

    if(0 == len%shift)
        t =1;

    buf = (char*)malloc(len/shift+1+1);
    memset(buf,0,len/shift+1+1);

    for(i=0;i<len;i++)
    {
        if(str[len-i-1] == '1')
        {
            buf[len/shift - i/shift-t] |= (1<<(i%shift)); 
        }
        else if(str[len-i-1] == '0')
        {
        	//nothing
        }
        else
        {
            return NULL;
        }
    }
    return buf;
}

/***************************************************************************
 *
 * Name:    _cubrid_dup_buf
 *
 * Purpose: copy data to new buf
 *
 **************************************************************************/

static char* _cubrid_dup_buf(char* src_buf,int size)
{
    int len=0;
    char* temp_buf=NULL;

    if(src_buf != NULL)
    {
        len = strlen(src_buf);
    }
    else
    {
        len =size;
    }
    if(len<=0)
    {
        return NULL;
    }
    temp_buf = (char*)malloc(len+1);
    if(NULL==temp_buf)
    {
      return NULL;
    }
    memset(temp_buf,0,len+1);
    if(NULL!= src_buf)
        memcpy(temp_buf,src_buf,len);   

    return temp_buf;
}
/***************************************************************************
 *
 * Name:    _cubrid_get_data_buf
 *
 * Purpose: alloc buf base cubrid data type
 *
 **************************************************************************/
static char* _cubrid_get_data_buf(int type,int num,imp_sth_t *imp_sth)
{
    switch(type)
    {
        case SQL_BIT:  
           return  _cubrid_dup_buf(NULL,sizeof(T_CCI_BIT)* (num+1));
        default:
           return  _cubrid_dup_buf(NULL,sizeof(void*)* (num+1));
    } 
}

/***************************************************************************
 *
 * Name:    dbd_bind_ph
 *
 * Purpose: Binds a statement value to a parameter
 *
 * Input:   sth - statement handle
 *          imp_sth - drivers private statement handle data
 *          param - parameter number, counting starts with 1
 *          value - value being inserted for parameter "param"
 *          sql_type - SQL type of the value
 *          attribs - bind parameter attributes
 *          inout - TRUE, if parameter is an output variable (currently
 *                  this is not supported)
 *          maxlen - ???
 *
 * Returns: TRUE for success, FALSE otherwise
 *
 **************************************************************************/

int
dbd_bind_ph( SV *sth, imp_sth_t *imp_sth, SV *param, SV *value,
             IV sql_type, SV *attribs, int is_inout, IV maxlen )
{
    int res = 0,i=0,num=0,buffer_is_null;
    int* indicator= NULL;   
    char *bind_value = NULL,*temp=NULL;
    STRLEN bind_value_len;
    T_CCI_ERROR error;
    AV* aTemp=NULL;
    SV** element=NULL;
    T_CCI_SET set;
    char** potinter=NULL;  
    T_CCI_U_TYPE u_type = CCI_U_TYPE_STRING;
    T_CCI_BIT Bit_Val={NULL};   

    int index = SvIV(param);
    if (index < 1 || index > DBIc_NUM_PARAMS(imp_sth)) {
        handle_error (sth, CCI_ER_BIND_INDEX, NULL);
        return FALSE;
    }  
    
    buffer_is_null = !(SvOK(newSVsv(value)) && newSVsv(value));
    if(!buffer_is_null)
   {
       if(SvROK(value) && SvTYPE(SvRV(value))==SVt_PVAV)
       {
            aTemp = SvRV(value);
            num = av_len(aTemp);

            potinter = (char**)_cubrid_get_data_buf(sql_type,num+1,imp_sth);
            indicator  = (int*)_cubrid_dup_buf(NULL,sizeof(int)* (num+1));
            if(potinter ==NULL || indicator==NULL)
            {
                handle_error (sth, CCI_ER_NO_MORE_MEMORY, NULL);
                return FALSE;                
            }
            
            for(i=0;i<=num;i++)
            {
                element = av_fetch(aTemp, i, 0);      
                temp= SvPV(*element,bind_value_len);
                if(temp ==NULL)
                {
                    handle_error (sth, CCI_ER_NO_MORE_MEMORY, NULL);
                    return FALSE;                        
                }

                if(strcmp(temp,"NULL")==0)
                {
                    indicator[i]=1;              
                }
                else
                {
                    indicator[i]=0;
                    if(SQL_BIT == sql_type)
                    {
                        u_type = CCI_U_TYPE_BIT;
                        temp =  cubrid_str2bit(SvPV(*element,bind_value_len));
                        if(temp == NULL)
                        {
                            handle_error (sth, CCI_ER_TYPE_CONVERSION, NULL);
                            return FALSE;                             
                        }
                        //Bit_Val = ((T_CCI_BIT *) potinter)[i];
                        ((T_CCI_BIT *) potinter)[i].buf = temp;
                        ((T_CCI_BIT *) potinter)[i].size = strlen(temp);   
                    }
                    else
                    {
                        potinter[i]  = temp;
                    }
                }    
            }
            if(cci_set_make(&set, u_type, num+1, potinter, (int*)indicator)<0)
            {
                handle_error (sth, CCI_ER_TYPE_CONVERSION, NULL);
                return FALSE;    
            } 
       }
       else
       {
           bind_value = SvPV (value, bind_value_len);
       }
   }

    if (SvOK(value) &&
            (sql_type == SQL_NUMERIC  ||
             sql_type == SQL_DECIMAL  ||
             sql_type == SQL_INTEGER  ||
             sql_type == SQL_SMALLINT ||
             sql_type == SQL_FLOAT    ||   
             sql_type == SQL_REAL     ||   
             sql_type == SQL_DOUBLE) )
    {
        if (! looks_like_number(value)) {
            handle_error(sth, CUBRID_ER_INVALID_PARAM, NULL);
            return FALSE;
        }
    }

    switch(sql_type) {
    case SQL_BLOB:
    case SQL_CLOB:
        if ((res = _cubrid_lob_bind (sth, 
                                     index, 
                                     sql_type, 
                                     bind_value, 
                                     &error)) < 0) {
            handle_error (sth, res, &error);
            return FALSE;
        }
        return TRUE;

    case SQL_NUMERIC:
        u_type = CCI_U_TYPE_NUMERIC;
        break;

    case SQL_INTEGER:
        u_type = CCI_U_TYPE_INT;
        break;

    case SQL_SMALLINT:
        u_type = CCI_U_TYPE_SHORT;
        break;

    case SQL_BIGINT:
        u_type = CCI_U_TYPE_BIGINT;
        break;

    case SQL_TINYINT:
        u_type = CCI_U_TYPE_SHORT;
        break;
    case SQL_ARRAY:
            u_type = CCI_U_TYPE_SET;
            break;

    default:
        u_type = CCI_U_TYPE_CHAR;
        break;
    }

    if (! buffer_is_null) 
    {
        if(SvROK(value) && SvTYPE(SvRV(value))==SVt_PVAV)
        {
            res = cci_bind_param (imp_sth->handle, index, CCI_A_TYPE_SET, set, CCI_U_TYPE_SET, 0);            
            cci_set_free (set);        
        }
        else
        {
            res = cci_bind_param (imp_sth->handle, index, CCI_A_TYPE_STR, bind_value, u_type, 0);
        }
    } 
    else 
    {
        res = cci_bind_param (imp_sth->handle, index, CCI_A_TYPE_STR, NULL, u_type, 0);
    }

    if (res < 0) {
        handle_error(sth, res, NULL);
        return FALSE;
    }
    
    return TRUE;
}


/***************************************************************************
 *
 * Name:    dbd_db_quote
 *
 * Purpose: Properly quotes a value
 *
 * Input:   dbh - statement handle
 *          str - input string
 *          type - not used
 *
 * Returns:  SV, contain the converted string.
 *
 **************************************************************************/

SV* dbd_db_quote(SV *dbh, SV *str, SV *type)
{
    dTHX;
    SV *result;

    T_CCI_ERROR error;
    int res;

    if (SvGMAGICAL(str))
        mg_get(str);

    if (!SvOK(str))
        return Nullsv;
    else
    {
        char *escaped_string = NULL, *unescaped_string = NULL;
        STRLEN len;

        int i = 0;
        char *s = NULL;
        STRLEN escaped_str_len = 0;
        char db_ver[16] = {'\0'};
        int major_ver = 0;

        D_imp_dbh(dbh);

        unescaped_string = SvPV (str, len);
        escaped_string = (char *) malloc (len * 2 + 16);
        if (!escaped_string)
        {
            handle_error (dbh, CCI_ER_NO_MORE_MEMORY, NULL);
            return Nullsv;
        }

        memset (escaped_string, 0, len * 2 + 16);

        escaped_string[0] = '\'';

        if ((res = cci_escape_string (imp_dbh->handle, 
                        escaped_string + 1, unescaped_string, len, &error)) < 0)
        {
            if (res != CAS_ER_PARAM_NAME) {

                free(escaped_string);
                handle_error(dbh, res, &error);
                return Nullsv;

            } else {

                res = cci_get_db_version (imp_dbh->handle, db_ver, sizeof (db_ver));
                if (res < 0) {
                    handle_error (dbh, res, NULL);
                    return Nullsv;
                }

                for (i=0; i< sizeof(db_ver); i++) {
                    if (db_ver[i] == '.') {
                        db_ver[i] = '\0';
                        break;
                    }
                }

                major_ver = atoi(db_ver);

                if (major_ver < 9) {
                    /* CUBRID-8.x not support cci_escape_string, so we do it by ourselves. */
                    s = escaped_string + 1;
                    for (i = 0; i < len; i++) {
                        if (unescaped_string[i] == '\'') {
                            *s++ = '\'';
                            *s++ = '\'';
                            escaped_str_len = escaped_str_len + 2;
                        } else {
                            *s++ = unescaped_string[i];
                            escaped_str_len++;
                        }
                    }

                    *s++ = '\'';
                    *s = '\0';
                    escaped_str_len = escaped_str_len + 2;

                } else {

                    free(escaped_string);
                    handle_error(dbh, CAS_ER_PARAM_NAME, &error);
                    return Nullsv;

                }

            }

        } else {

            escaped_string[1 + res] = '\'';
            escaped_str_len = res + 2;

        }

        result = newSVpvn (escaped_string, escaped_str_len);
        free (escaped_string);
    }

    return result;
}


/* Large object functions */

/**************************************************************************/

int
cubrid_st_lob_get( SV *sth, int col )
{
    int res, ind;
    T_CCI_ERROR error;
    int i = 0;
    T_CCI_U_TYPE u_type;

    D_imp_sth (sth);
    imp_sth->lob = NULL;

    if (col < 1 || col > DBIc_NUM_FIELDS (imp_sth)) {
        handle_error (sth, CCI_ER_COLUMN_INDEX, NULL);
        return FALSE;
    }

    if (imp_sth->sql_type != SQLX_CMD_SELECT) {
        handle_error (sth, CCI_ER_NO_MORE_DATA, NULL);
        return FALSE;
    }

    u_type = CCI_GET_RESULT_INFO_TYPE (imp_sth->col_info, col);
    if (!(u_type == CCI_U_TYPE_BLOB ||  u_type == CCI_U_TYPE_CLOB)) {
        handle_error (sth, CUBRID_ER_NOT_LOB_TYPE, NULL);
        return FALSE;
    }

    imp_sth->col_selected = col;

    imp_sth->lob = (T_CUBRID_LOB *) malloc (imp_sth->affected_rows * sizeof (T_CUBRID_LOB));
    memset(imp_sth->lob, 0, imp_sth->affected_rows * sizeof (T_CUBRID_LOB));

    return TRUE;
}

int
cubrid_st_lob_export( SV *sth, int index, char *filename )
{
    char buf[CUBRID_BUFFER_LEN] = {'\0'};
    int fd, res, size;
    long long pos = 0, lob_size;
    T_CCI_ERROR error;
    T_CCI_U_TYPE u_type;
    int ind = 0;

    D_imp_sth (sth);

    if (imp_sth->lob == NULL) {
        handle_error (sth, CUBRID_ER_CANNOT_FETCH_DATA, NULL);
        return FALSE;
    }

    if (index > imp_sth->affected_rows || index < 1) {
        handle_error (sth, CUBRID_ER_ROW_INDEX_EXCEEDED, NULL);
        return FALSE;
    }

    if (imp_sth->col_selected < 1 || imp_sth->col_selected > DBIc_NUM_FIELDS (imp_sth)) {
        handle_error (sth, CCI_ER_COLUMN_INDEX, NULL);
        return FALSE;
    }

    u_type = CCI_GET_RESULT_INFO_TYPE (imp_sth->col_info, imp_sth->col_selected);
    if (!(u_type == CCI_U_TYPE_BLOB ||  u_type == CCI_U_TYPE_CLOB)) {
        handle_error (sth, CUBRID_ER_NOT_LOB_TYPE, NULL);
        return FALSE;
    }

    res = cci_cursor (imp_sth->handle, index, CCI_CURSOR_FIRST, &error);
    if (res == CCI_ER_NO_MORE_DATA) {
        return TRUE;
    } else if (res < 0) {
        handle_error (sth, res, &error);
        return FALSE;
    }

    if ((res = cci_fetch(imp_sth->handle, &error)) < 0) {
        handle_error (sth, res, &error);
        return FALSE;
    }

    if ( u_type == CCI_U_TYPE_BLOB) {
        imp_sth->lob[index-1].type = CCI_U_TYPE_BLOB;
        if ((res = cci_get_data (imp_sth->handle,
                        imp_sth->col_selected,
                        CCI_A_TYPE_BLOB,
                        (void *)&imp_sth->lob[index-1].lob,
                        &ind)) < 0) {
            handle_error (sth, res, NULL);
            return FALSE;
        }
    } else {
        imp_sth->lob[index-1].type = CCI_U_TYPE_BLOB;
        if ((res = cci_get_data (imp_sth->handle,
                        imp_sth->col_selected,
                        CCI_A_TYPE_CLOB,
                        (void *)&imp_sth->lob[index-1].lob,
                        (&ind))) < 0) {
            handle_error (sth, res, NULL);
            return FALSE;
        }
    }

    if ( ind == -1 ) {
        handle_error (sth, CUBRID_ER_EXPORT_NULL_LOB_INVALID, NULL);
        return FALSE;
    }

    if ( imp_sth->lob == NULL || imp_sth->lob[index-1].lob == NULL) {
        handle_error (sth, CCI_ER_INVALID_LOB_HANDLE, NULL);
        return FALSE;
    }

    if ((fd = open (filename, O_CREAT | O_WRONLY | O_TRUNC, 0666)) < 0) {
        handle_error (sth, CCI_ER_FILE, NULL);
        return FALSE;
    }

    lob_size = _cubrid_lob_size (imp_sth->lob[index-1].lob, imp_sth->lob[index-1].type);

    while (1) {
        if ((size = _cubrid_lob_read (imp_sth->conn, 
                                      imp_sth->lob[index-1].lob, 
                                      imp_sth->lob[index-1].type, 
                                      pos, 
                                      CUBRID_BUFFER_LEN, 
                                      buf, 
                                      &error)) < 0) {
            res = size;
            goto ER_LOB_EXPORT;
        }

        if ((res = write (fd, buf, size)) < 0) {
            res = CUBRID_ER_WRITE_FILE;
            goto ER_LOB_EXPORT;
        }

        pos += size;
        if (pos == lob_size) {
            break;
        }
    }

    close (fd);
    return TRUE;

ER_LOB_EXPORT:
    if (fd >= 0) {
        close (fd);
        unlink (filename);
    }

    handle_error (sth, res, &error);
    return FALSE;
}

int
cubrid_st_lob_import( SV *sth, 
                      int index,
                      char *filename,
                      IV sql_type )
{
    T_CCI_ERROR error;
    T_CCI_LOB lob;
    T_CCI_U_TYPE u_type;
    T_CCI_A_TYPE a_type;
    int fd, size, res;
    long long pos = 0;
    char buf[CUBRID_BUFFER_LEN] = {'\0'};

    D_imp_sth (sth);

    if (sql_type == SQL_BLOB) {
        u_type = CCI_U_TYPE_BLOB;
        a_type = CCI_A_TYPE_BLOB;
    }
    else if (sql_type == SQL_CLOB) {
        u_type = CCI_U_TYPE_CLOB;
        a_type = CCI_A_TYPE_CLOB;
    }
    else {
        handle_error (sth, CUBRID_ER_NOT_LOB_TYPE, NULL);
        return FALSE;
    }

    if ((fd = open (filename, O_RDONLY, 0400)) < 0) {
        res = CCI_ER_FILE;
        handle_error(sth, res, NULL);

        return FALSE;
    }

    if ((res = _cubrid_lob_new (imp_sth->conn, 
                                &lob,
                                u_type, 
                                &error)) < 0 ) {
        handle_error (sth, res, &error);
        return FALSE;
    }

    while (1) {
        if ((size = read (fd, buf, CUBRID_BUFFER_LEN)) < 0) {
            res = CUBRID_ER_READ_FILE;
            goto ER_LOB_IMPORT;
        }
        
        if (size == 0) {
            break;
        }

        if ((res = _cubrid_lob_write (imp_sth->conn, 
                                      lob, 
                                      u_type,
                                      pos, 
                                      size, 
                                      buf, 
                                      &error)) < 0) {
            goto ER_LOB_IMPORT;
        }

        pos += size;
    }

    if ((res = cci_bind_param (imp_sth->handle, 
                               index, 
                               a_type, 
                               (void *)lob,
                               u_type, 
                               CCI_BIND_PTR)) < 0) {
        goto ER_LOB_IMPORT;
    }

    close (fd);
    return TRUE;

ER_LOB_IMPORT:
    if (fd >= 0) {
        close (fd);
    }

    _cubrid_lob_free (lob, u_type);
    handle_error (sth, res, &error);
    return FALSE;
}

int
cubrid_st_lob_close (SV *sth)
{
    D_imp_sth (sth);

    if (imp_sth->lob) {
        int i;
        for (i = 0; i < imp_sth->affected_rows; i++) {
            _cubrid_lob_free (imp_sth->lob[i].lob, imp_sth->lob[i].type);
        }

        free (imp_sth->lob);
        imp_sth->lob = NULL;
    }

    return TRUE;
}

static int
_cubrid_lob_bind( SV *sth, 
                  int index, 
                  IV sql_type,
                  char *buf, 
                  T_CCI_ERROR *error )
{
    T_CCI_LOB lob;
    T_CCI_U_TYPE u_type;
    T_CCI_A_TYPE a_type;
    int res;

    D_imp_sth (sth);
   
    if (sql_type == SQL_BLOB) {
        u_type = CCI_U_TYPE_BLOB;
        a_type = CCI_A_TYPE_BLOB;
    } else {
        u_type = CCI_U_TYPE_CLOB;
        a_type = CCI_A_TYPE_CLOB;
    }

    if ((res = _cubrid_lob_new (imp_sth->conn, 
                                &lob, 
                                u_type,
                                error)) < 0 ) {
        return res;
    }

    if ((res = _cubrid_lob_write (imp_sth->conn, 
                                  lob, 
                                  u_type,
                                  0, 
                                  strlen(buf), 
                                  buf, 
                                  error)) < 0) {
        _cubrid_lob_free (lob, u_type);
        return res;
    }

    if ((res = 
        cci_bind_param (imp_sth->handle, 
            index, a_type, (void *)lob, u_type, CCI_BIND_PTR)) < 0)
    {
        _cubrid_lob_free (lob, u_type);
        return res;
    }

    return 0;
}

static int 
_cubrid_lob_new( int conn, 
                 T_CCI_LOB *lob, 
                 T_CCI_U_TYPE type, 
                 T_CCI_ERROR *error )
{
    return (type == CCI_U_TYPE_BLOB) ? 
        cci_blob_new (conn, lob, error) : cci_clob_new (conn, lob, error);
}

static long long
_cubrid_lob_size( T_CCI_LOB lob, T_CCI_U_TYPE type )
{
    return (type == CCI_U_TYPE_BLOB) ? 
        cci_blob_size (lob) : cci_clob_size (lob);
}

static int
_cubrid_lob_write( int conn, 
                   T_CCI_LOB lob, 
                   T_CCI_U_TYPE type, 
                   long long start_pos, 
                   int length, 
                   const char *buf,
                   T_CCI_ERROR *error )
{
    return (type == CCI_U_TYPE_BLOB) ?
        cci_blob_write (conn, lob, start_pos, length, buf, error) :
        cci_clob_write (conn, lob, start_pos, length, buf, error);
}

static int
_cubrid_lob_read( int conn, 
                  T_CCI_LOB lob, 
                  T_CCI_U_TYPE type, 
                  long long start_pos, 
                  int length, 
                  char *buf, 
                  T_CCI_ERROR *error )
{
    return (type == CCI_U_TYPE_BLOB) ?
        cci_blob_read (conn, lob, start_pos, length, buf, error) :
        cci_clob_read (conn, lob, start_pos, length, buf, error);
}

static int 
_cubrid_lob_free( T_CCI_LOB lob, T_CCI_U_TYPE type )
{
    return (type == CCI_U_TYPE_BLOB) ?
        cci_blob_free (lob) : cci_clob_free (lob);
}

/* catalog functions */

/*************************************************************************/
SV *
_cubrid_primary_key( SV *dbh, char *table )
{
    int res, req_handle, col_count;
    T_CCI_COL_INFO *col_info;
    T_CCI_CUBRID_STMT sql_type;
    T_CCI_ERROR error;
    AV *rows_av;
    SV *rows_rvav;

    D_imp_dbh (dbh);

    if ((res = cci_schema_info (imp_dbh->handle, 
                                CCI_SCH_PRIMARY_KEY, 
                                table, 
                                NULL, 
                                0, 
                                &error)) < 0) {
        goto ER_CUBRID_PRIMARY_KEY;
    }

    req_handle = res;

    if (!(col_info = cci_get_result_info (req_handle, &sql_type, &col_count))) {
        handle_error (dbh, CUBRID_ER_CANNOT_GET_COLUMN_INFO, NULL);
        return Nullsv;
    }

    rows_av = newAV ();

    if ((res = _cubrid_fetch_schema (rows_av, 
                                     req_handle, 
                                     col_count, 
                                     col_info, 
                                     &error)) < 0) {
        goto ER_CUBRID_PRIMARY_KEY;
    }

    cci_close_req_handle (req_handle);
    rows_rvav = sv_2mortal(newRV_noinc((SV *)rows_av));
    return rows_rvav;

ER_CUBRID_PRIMARY_KEY:
    cci_close_req_handle (req_handle);
    if (rows_av != Nullav) {
        av_undef(rows_av);
    }
    handle_error (dbh, res, &error);
    return Nullsv;
}

SV *
_cubrid_foreign_key( SV *dbh, char *pk_table, char *fk_table)
{
    int res, req_handle, col_count;
    T_CCI_COL_INFO *col_info;
    T_CCI_CUBRID_STMT sql_type;
    T_CCI_ERROR error;
    AV *rows_av;
    SV *rows_rvav;

    D_imp_dbh (dbh);

    if (strcmp(pk_table, "") != 0  && strcmp (fk_table, "") != 0) {
        if ((res = cci_schema_info (imp_dbh->handle, 
                                      CCI_SCH_CROSS_REFERENCE,
                                      pk_table, 
                                      fk_table, 
                                      0, 
                                      &error)) < 0) {
            goto ER_CUBRID_FOREIGN_KEY;
        }
    }
    else if (strcmp(pk_table, "") != 0  && strcmp (fk_table, "") == 0) {
        if ((res = cci_schema_info (imp_dbh->handle, 
                                    CCI_SCH_EXPORTED_KEYS, 
                                    pk_table, 
                                    NULL, 
                                    0, 
                                    &error)) < 0) {
            goto ER_CUBRID_FOREIGN_KEY;
        }
    }
    else if (strcmp(pk_table, "") == 0  && strcmp (fk_table, "") != 0) {
        if ((res = cci_schema_info (imp_dbh->handle, 
                                    CCI_SCH_IMPORTED_KEYS, 
                                    fk_table,
                                    NULL,
                                    0,
                                    &error)) < 0) {
            goto ER_CUBRID_FOREIGN_KEY;
        }
    }

    req_handle = res;

    if (!(col_info = cci_get_result_info (req_handle, &sql_type, &col_count))) {
        handle_error (dbh, CUBRID_ER_CANNOT_GET_COLUMN_INFO, NULL);
        return Nullsv;
    }

    rows_av = newAV ();

    if ((res = _cubrid_fetch_schema (rows_av, 
                                     req_handle, 
                                     col_count, 
                                     col_info, 
                                     &error)) < 0) {
        goto ER_CUBRID_FOREIGN_KEY;
    }

    cci_close_req_handle (req_handle);
    rows_rvav = sv_2mortal(newRV_noinc((SV *)rows_av));
    return rows_rvav;

ER_CUBRID_FOREIGN_KEY:
    cci_close_req_handle (req_handle);
    if (rows_av != Nullav) {
        av_undef(rows_av);
    }
    handle_error (dbh, res, &error);
    return Nullsv;
}

static int
_cubrid_fetch_schema( AV *rows_av, 
                      int req_handle, 
                      int col_count, 
                      T_CCI_COL_INFO *col_info, 
                      T_CCI_ERROR *error )
{
    int res;

    while (1) {
        AV *copy_row, *fetch_av;
        int i = 0;

        res = cci_cursor (req_handle, 1, CCI_CURSOR_CURRENT, error);
        if (res == CCI_ER_NO_MORE_DATA) {
            break;
        }
        else if (res < 0) {
            return res;
        }

        if ((res = cci_fetch (req_handle, error)) < 0) {
            return res;
        }

        fetch_av = newAV();
        while (i < col_count) {
            av_store (fetch_av, i, newSV(0));
            i++;
        }

        if ((res = _cubrid_fetch_row (fetch_av,
                                      req_handle, 
                                      col_count, 
                                      col_info, 
                                      error)) < 0) {

            av_undef (fetch_av);
            return res;
        }

        copy_row = av_make (AvFILL(fetch_av) + 1, AvARRAY(fetch_av));
        av_push (rows_av, newRV_noinc ((SV *)copy_row));
        
        av_undef (fetch_av);
    }

    return 0;
}

static int
_cubrid_fetch_row( AV *av, 
                   int req_handle, 
                   int col_count, 
                   T_CCI_COL_INFO *col_info, 
                   T_CCI_ERROR *error )
{
    int i, res, type, num, ind;
    char *buf;
    double ddata;

    for (i = 0; i < col_count; i++) {
        SV *sv = AvARRAY(av)[i];

        type = CCI_GET_RESULT_INFO_TYPE (col_info, i+1);
        
        switch (type) {
        case CCI_U_TYPE_INT:
        case CCI_U_TYPE_SHORT:
            if ((res = cci_get_data (req_handle, 
                            i+1, CCI_A_TYPE_INT, &num, &ind)) < 0) {
                return res;
            }

            if (ind < 0) {
                (void) SvOK_off (sv);
            } else {
                sv_setiv (sv, num);
            }
            break;
        case CCI_U_TYPE_FLOAT:
        case CCI_U_TYPE_DOUBLE:
        case CCI_U_TYPE_NUMERIC:
            if ((res = cci_get_data (req_handle,
                            i+1, CCI_A_TYPE_DOUBLE, &ddata, &ind)) < 0) {
                return res;
            }

            if (ind < 0) {
                (void) SvOK_off (sv);
            } else {
                sv_setnv (sv, ddata);
            }
            break;
        default:
            if ((res = cci_get_data (req_handle,
                            i+1, CCI_A_TYPE_STR, &buf, &ind)) < 0) {
                return res;
            }
            if (ind < 0) {
                (void) SvOK_off (sv);
            } else {
                sv_setpvn (sv, buf, strlen(buf));
            }
        }
    }

    return 0;
}
