/*
 * $Header: /san01/cvs/ashpool/csrc/modoracle/Attic/ora.c,v 1.7 2004/04/02 16:05:45 aleigh Exp $
 *
 * Copyright (c) 1994, 2001 Alex Leigh, All Rights Reserved
 * 
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 * 
 * Disclosure, copying, distribution, or transmission of of this code without
 * express permission is prohibited.
 */

/* TODO:
   Checkout needs to eval checkin_time and test the connection if it's old
   Various oracle functions need to mark the connection as bad if there is an error
 */

/* Define this if you want connection pooling to be in effect. */
#define ORA_POOLING

#include <assert.h>
#include <continuity.h>
#include "db.h"
#include <oci.h>

#define ORA_CONN_AUTOCOMMIT 0
#define ORA_CONN_TRANSACTION 1
#define ORA_DML 1
#define ORA_ROWS 2

/* o/~ Words are very unescessary */

struct oraSfetch {
    struct oraSconn *conn;
    /* Oracle will tell us what type this column is */
    OCITypeCode type;
    /* will be NULL unless this column happens to be one of the LOB types */
    OCILobLocator *lob;
    /* generally used for INSERT and UPDATE; seldom used */
    OCIBind *bind;
    /* generally used for SELECT; seldom used except for getting LOBs */
    OCIDefine *def;
    /* how many bytes Oracle thinks we need to allocate */
    ub2 size;
    /* how many bytes we allocated */
    unsigned buf_size;
    /* the stuff above does not change as the rows are fetched */
    /* here's where the actual value from a particular row is kept */
    char *buf;
    /* support for array DML: the array of values for this bind variable. */
    int array_count;
    char **array_values;
    /* 2-byte signed integer indicating null-ness; if null, value will be -1 */
    sb2 is_null;
    /* how many bytes are in the buffer above, 0 would mean empty string */
    ub2 fetch_length;
    /* these are only used for LONGs; the length of one piece */
    ub4 piecewise_fetch_length;
    /*
     * in order to implement the clob_dml API call, we need 1 LOB for every
     * row/column intersection inserted.  I.e., if we do an   insert that
     * results in 4 rows going into the db, with 3 CLOB columns then we need
     * 12 LOBs.  This struct is for one column only so we just need one array
     * of lobs.
     */
    OCILobLocator **lobs;
    /* this tells us how many lobs we have above (i.e., only for clob_dml) */
    ub4 n_rows;
    /* Whether we determined that this column is a LOB during processing. */
    int is_lob;
};
typedef struct oraSfetch oraTfetch;

struct oraSconn {
    OCIEnv *envhp;
    OCIError *errhp;
    OCIServer *srvhp;
    OCISvcCtx *svchp;
    OCISession *usrhp;

    /* Statement that we are executing. */
    OCIStmt *stmthp;
    int mode;
    sb4 n_columns;
    char padding[10];
    oraTfetch *fetch;

    /* These are used by the pooling functionality
       and not otherwise needed. */
    char *name;                 /* do not free */
    char *service;              /* do not free */
    unsigned int checkin_time;
    int bad;                    /* If this is 1, the connection will be closed rather than checked in */
};
typedef struct oraSconn oraTconn;

struct oraSpool {
    lstTlist *conns;
    char *user;
    char *service;
    int checked_in;
    int checked_out;
    struct oraSpool *next;
};
typedef struct oraSpool oraTpool;

oraTpool *oraGpools = NULL;
pthread_mutex_t oraLpools = PTHREAD_MUTEX_INITIALIZER;

dvoid *tmp;

static int lob_buffer_size = 16384;

static int oraFerror(oraTconn * conn, sword status);
static void oraFalloc_fetch_bufs(oraTconn * conn);
lstTset *oraFbindrow(oraTconn * conn);
int oraFexec(oraTconn * conn, char *sql);
oraTconn *oraFconnect(char *user, char *password, char *service);
oraTconn *oraFpool_checkout(const char *name, const char *password, const char *service);
int oraFclose(oraTconn * conn);
lstTset *oraFselect(oraTconn * conn, const char *sql);
int oraFgetrow(oraTconn * conn, lstTset * row);
static void oraFfree_fetch_bufs(oraTconn * conn);
int oraFflush(oraTconn * conn);
oraTpool *oraFpool_get(char *name, char *service);

/*
 * Return an oracle connection pool entry. This always succeeds.
 * because it will create an entry if one does not exist. The caller
 * needs to hold oraLpools.
 */
oraTpool *oraFpool_get(char *name, char *service)
{
    oraTpool *pool;

    assert(name != NULL);
    assert(service != NULL);

    for (pool = oraGpools; pool != NULL; pool = pool->next) {
        if (strcmp((char *) pool->user, (char *) name) == 0
            && strcmp(pool->service, service) == 0)
            return pool;
    }

    pool = (oraTpool *) malloc(sizeof(oraTpool));
    assert(pool != NULL);

    bzero(pool, sizeof(oraTpool));

    pool->user = strFcopy(name);
    pool->service = strFcopy(service);

    assert(pool->user != NULL);
    assert(pool->service != NULL);

    /* We are going to routinely remove elements from the list
       when we check them out, so we don't want a desctructor. */

/*  logFmsg(3,"mod/ora: Creating pool for %s / %s", name, service); */

    pool->conns = lstFlist_create((void (*)(void *)) NULL);

    pool->next = oraGpools;
    oraGpools = pool;

    return pool;
}

void oraFpool_checkin(oraTconn * conn)
{
    oraTpool *pool;

    pthread_mutex_lock(&oraLpools);

    pool = oraFpool_get(conn->name, conn->service);

    assert(pool != NULL);

    if (conn->bad == 1) {
        logFmsg(1, "ora: Connection to %s/%s was marked bad, destroying.",
                pool->user, pool->service);
        oraFclose(conn);
        pool->checked_out--;
        pthread_mutex_unlock(&oraLpools);
        return;
    }

    oraFflush(conn);
    /* Set the checkin time. This will later be used when the connection
     * is checked out to see if it needs to be tested or not. */
    conn->checkin_time = utlFtime();

    lstFlist_add_next(pool->conns, lstFlist_tail(pool->conns), conn);

    pool->checked_out--;
    pool->checked_in++;

    pthread_mutex_unlock(&oraLpools);

/* logFmsg(0,"ora: Checked in connection to %s/%s.", pool->user, pool->service); */
}

oraTconn *oraFpool_checkout(const char *name, const char *password, const char *service)
{
    oraTpool *pool;
    oraTconn *conn;
    lstTlist_elem *el;

    assert(name != NULL);
    assert(service != NULL);

    pthread_mutex_lock(&oraLpools);

    pool = oraFpool_get(name, service);

    assert(pool != NULL);

/* logFmsg(3,"ora: checked in: %d for %s/%s ", pool->checked_in, pool->user, pool->service); */

    if (pool->checked_in == 0) {
        conn = oraFconnect(name, password, service);
        if (conn == NULL) {
/*       logFmsg(3,"oraFpool_checkout: oraFconnect() failed."); */
            pthread_mutex_unlock(&oraLpools);
            return NULL;
        }

/*     logFmsg(3,"ora: oraFpool_checkout: Created new connection to %s/%s.", pool->user, pool->service);  */

        conn->name = pool->user;
        conn->service = pool->service;

        pool->checked_out++;

        pthread_mutex_unlock(&oraLpools);
        return conn;
    }

    el = lstFlist_head(pool->conns);
    conn = lstFlist_data(el);
    lstFlist_remove(pool->conns, el, 0);
    pool->checked_in--;

    pthread_mutex_unlock(&oraLpools);

/* logFmsg(3,"ora: oraFpool_checkout: Checked out connection to %s/%s.", pool->user, pool->service); */
    return conn;
}

int oraFinit(void *p, lstTset * opts)
{
    dbTvector *vector;

#ifdef ORA_POOLING
    logFmsg(0, "mod/oracle: Oracle RDBMS Interface + Pooling (module)");
#else
    logFmsg(0, "mod/oracle: Oracle RDBMS Interface (module)");
#endif

    logFmsg(0, "mod/oracle: Copyright (c) 2002, Alex Leigh");

    OCIInitialize((ub4) OCI_THREADED | OCI_OBJECT, (dvoid *) 0,
                  (dvoid * (*)())0, (dvoid * (*)())0, (void (*)()) 0);

    vector = (dbTvector *) malloc(sizeof(dbTvector));

    vector->init_fn = (void (*)(void)) oraFinit;

#ifndef ORA_POOLING
    vector->open_fn = (void *(*)(char *, char *, char *)) oraFconnect;
    vector->close_fn = (int (*)(void *)) oraFclose;
#else
    vector->open_fn =
        (void *(*)(char *, char *, char *)) oraFpool_checkout;
    vector->close_fn = (int (*)(void *)) oraFpool_checkin;
#endif

    vector->select_fn = (lstTset * (*)(void *, const char *)) oraFselect;
    vector->getrow_fn = (int (*)(void *, lstTset *)) oraFgetrow;
    vector->dml_fn = (int (*)(void *, char *)) oraFexec;
    dbFvector_register(vector);

    return 0;
}

/*
 * -1 error
 * 0 data
 * 2 no data
 */
int oraFgetrow(oraTconn * conn, lstTset * row)
{
    sword status;
    int i;
    ub4 ret_len = 0;

    assert(conn != NULL);
    assert(row != NULL);
    assert(conn->stmthp != NULL);
    assert(conn->errhp != NULL);

    status =
        OCIStmtFetch(conn->stmthp, conn->errhp, 1, OCI_FETCH_NEXT,
                     OCI_DEFAULT);

    if (status == OCI_NO_DATA) {
        oraFflush(conn);
        return 2;
    }
    if (oraFerror(conn, status) != 0) {
        oraFflush(conn);
        return -1;
    }
    for (i = 0; i < conn->n_columns; i++) {
        oraTfetch *fetchbuf = &conn->fetch[i];

        switch (fetchbuf->type) {
        default:
            /* Add null termination and then add it to the set */
            if (fetchbuf->is_null == -1) {
                fetchbuf->buf[0] = NULL;
            } else if (fetchbuf->is_null != 0) {
                oraFflush(conn);
                return -1;
            }
            fetchbuf->buf[fetchbuf->fetch_length] = NULL;

            lstFset_add_value(row, i, fetchbuf->buf);
/* 	 logFmsg(3, "value: %s", fetchbuf->buf); */
            break;
        }
    }
    return 0;
}

/*
 * In cases where a "bad" error have occured, this function also will
 * now mark the connection as bad. This means that it will be destroyed
 * rather than be returned to the pool. 
 */
static int oraFerror(oraTconn * conn, sword status)
{
    text errbuf[512];
    ub4 buflen;
    ub4 errcode;

    switch (status) {
    case OCI_SUCCESS:
        return 0;
    case OCI_SUCCESS_WITH_INFO:
        logFmsg(1, "Error - OCI_SUCCESS_WITH_INFO");
        break;
    case OCI_NEED_DATA:
        logFmsg(1, "Error - OCI_NEED_DATA");
        break;
    case OCI_NO_DATA:
        logFmsg(1, "Error - OCI_NO_DATA");
        break;
    case OCI_ERROR:
        OCIErrorGet((dvoid *) conn->errhp, (ub4) 1, (text *) NULL,
                    (dvoid *) & errcode, errbuf, (ub4) sizeof(errbuf),
                    (ub4) OCI_HTYPE_ERROR);
        strFstrip_crlf((char *) errbuf, strlen((const char *) errbuf));
        logFmsg(1, "%s", errbuf);
        conn->bad = 1;
        break;
    case OCI_INVALID_HANDLE:
        logFmsg(1, "Error - OCI_INVALID_HANDLE");
        conn->bad = 1;
        break;
    case OCI_STILL_EXECUTING:
        logFmsg(1, "Error - OCI_STILL_EXECUTE");
        conn->bad = 1;
        break;
    case OCI_CONTINUE:
        logFmsg(1, "Error - OCI_CONTINUE");
        break;
    default:
        break;
    }

    return 1;
}

static void oraFfree_fetch_bufs(oraTconn * conn)
{
    if (conn != NULL && conn->fetch != NULL) {
        int i;
        sword oci_status;

        for (i = 0; i < conn->n_columns; i++) {
            /*
             * oraTfetch *fetchbuf = (oraTfetch *) conn->fetch +
             * (sizeof(oraTfetch) * i);
             */
            oraTfetch *fetchbuf = &conn->fetch[i];

            /* oraTfetch *fetchbuf = (oraTfetch *) & conn->fetch[i]; */
            /* logFmsg(3,"fetchbuf set to %d 0x%x", i, fetchbuf); */


            /*
             * if (fetchbuf->lob != NULL) { oraFerror(conn,
             * OCIDescriptorFree(fetchbuf->lob, OCI_DTYPE_LOB)); fetchbuf->lob =
             * NULL; }
             */
            /*
             * fetchbuf->bind is automatically deallocated when its statement is
             * deallocated.
             * 
             * Same for fetchbuf->def, I believe, though the manual doesn't say.
             */

            if (fetchbuf->buf != NULL) {
                free(fetchbuf->buf);
                fetchbuf->buf = NULL;
                fetchbuf->buf_size = 0;
            }
            /*
             * if (fetchbuf->lobs != 0) { for (i = 0; i < fetchbuf->n_rows; i++)
             * { oraFerror(conn, OCIDescriptorFree(fetchbuf->lobs[i],
             * OCI_DTYPE_LOB)); } free(fetchbuf->lobs); fetchbuf->lobs = NULL;
             * fetchbuf->n_rows = 0; }
             */
        }

        free(conn->fetch);
        conn->fetch = NULL;
    }
}


static void oraFalloc_fetch_bufs(oraTconn * conn)
{
    int i;

    /*
     * logFmsg(3, "alloc: allocating fetch buffers %d", conn->n_columns *
     * sizeof(oraTfetch));
     */

    conn->fetch = malloc(conn->n_columns * sizeof(struct oraSfetch));
    assert(conn->fetch != NULL);

    /* logFmsg(3, "Allocated fetchbuffers at 0x%x", conn->fetch); */

    for (i = 0; i < conn->n_columns; i++) {
        oraTfetch *fetchbuf = &conn->fetch[i];
        /*
         * oraTfetch *fetchbuf = (oraTfetch *) conn->fetch + (sizeof(oraTfetch) *
         * i);
         */

        bzero(fetchbuf, sizeof(oraTfetch));
        fetchbuf->conn = conn;
    }
}

lstTset *oraFbindrow(oraTconn * conn)
{
    sword status;
    lstTset *set;
    int i;

    assert(conn != NULL);

    set = lstFset_create("row");

    if (conn->stmthp == NULL) {
        logFmsg(1, "oraFbindrow: No active query statment.");
        return NULL;
    }
    if (conn->fetch != NULL) {
        logFmsg(1, "oraFbindrow: Query already bound.");
        return NULL;
    }
    if (oraFerror(conn, OCIAttrGet(conn->stmthp, OCI_HTYPE_STMT,
                                   (dvoid *) & conn->n_columns, NULL,
                                   OCI_ATTR_PARAM_COUNT,
                                   conn->errhp)) != 0)
        return NULL;

    oraFalloc_fetch_bufs(conn);

    for (i = 0; i < conn->n_columns; i++) {
        OCIParam *param;
        char name[512];
        char *name1 = 0;
        ub4 name1_size = 0;

        /* Set the current fetch buffer */
        oraTfetch *fetchbuf = &conn->fetch[i];
        /*
         * oraTfetch *fetchbuf = (oraTfetch *) conn->fetch + (sizeof(oraTfetch) *
         * i);
         */
        /* logFmsg(3,"fetchbuf set to %d 0x%x", i, fetchbuf); */

        /* fetchbuf = (oraTfetch *) & conn->fetch[i]; */

        if (oraFerror(conn, OCIParamGet(conn->stmthp, OCI_HTYPE_STMT,
                                        conn->errhp, (dvoid *) & param,
                                        i + 1)) != 0)
            return NULL;

        if (oraFerror
            (conn,
             OCIAttrGet(param, OCI_DTYPE_PARAM, (dvoid *) & name1,
                        &name1_size, OCI_ATTR_NAME, conn->errhp)) != 0)
            return NULL;

        /*
         * * Oracle gives us back a pointer to a string that is not
         * null-terminated so we copy it into our local var and add a 0 at the
         * end
         */
        memcpy(name, name1, name1_size);
        name[name1_size] = 0;

        /* logFmsg(3, "lstFset_add '%s' %s", name, "null"); */
        lstFset_add(set, name, "null");

        /* Get the column type. */

        if (oraFerror
            (conn,
             OCIAttrGet(param, OCI_DTYPE_PARAM, &fetchbuf->type, NULL,
                        OCI_ATTR_DATA_TYPE, conn->errhp)) != 0)
            return NULL;


        /* logFmsg(3, "type is %d", fetchbuf->type); */

        switch (fetchbuf->type) {
            /* we handle LOBs in the loop below */
        case OCI_TYPECODE_CLOB:
        case OCI_TYPECODE_BLOB:
            break;

            /*
             * RDD is Oracle's happy fun name for ROWID (18 chars long but if
             * you ask Oracle the usual way, it will give you a  number that is
             * too small)
             */
        case SQLT_RDD:
            fetchbuf->size = 18;
            fetchbuf->buf_size = fetchbuf->size + 32;
            fetchbuf->buf = malloc(fetchbuf->buf_size);
            break;

        case SQLT_NUM:
            /*
             * OCI reports that all NUMBER values has a size of 22, the size of
             * its internal storage format for numbers. We are fetching all
             * values out as strings, so we need more space. Empirically, it
             * seems to return 41 characters when it does the NUMBER to STRING
             * conversion.
             */
            fetchbuf->size = 41;
            fetchbuf->buf_size = fetchbuf->size + 8;
            fetchbuf->buf = malloc(fetchbuf->buf_size);
            break;

            /* this might work if the rest of our LONG stuff worked */
        case SQLT_LNG:
            fetchbuf->buf_size = lob_buffer_size;
            fetchbuf->buf = malloc(fetchbuf->buf_size);
            break;
        default:
            /* get the size */

            if (oraFerror(conn, OCIAttrGet(param,
                                           OCI_DTYPE_PARAM,
                                           (dword *) & fetchbuf->size,
                                           NULL,
                                           OCI_ATTR_DATA_SIZE,
                                           conn->errhp)) != 0)
                return NULL;

            /* logFmsg(3, "column '%s' size '%d'", name, fetchbuf->size); */

            /*
             * this is the important part, we allocate buf to be 8 bytes more
             * than Oracle says are necessary (for null termination)
             */
            fetchbuf->buf_size = fetchbuf->size + 32;
            fetchbuf->buf = malloc(fetchbuf->buf_size);
            /*
             * logFmsg(0, "Fetchbuf %d 0x%x -> buf = 0x%x", i, &fetchbuf->buf,
             * fetchbuf->buf);
             */

            break;
        }
    }

    /*
     * loop over the columns again; this could now be in the loop above but we
     * originally did things this way to permit resizing of buffers
     * 
     * Now we're telling Oracle to associate the buffers we just allocated with
     * their respective columns
     */
    for (i = 0; i < conn->n_columns; i++) {
        oraTfetch *fetchbuf = &conn->fetch[i];
        /*
         * oraTfetch *fetchbuf = (oraTfetch *) conn->fetch + (sizeof(oraTfetch) *
         * i);
         */
        /* logFmsg(3,"fetchbuf set to %d 0x%x", i, fetchbuf); */


        /*
         * logFmsg(3, "2nd pass: conn->fetch[%d]->buf is 0x%x", i,
         * fetchbuf->buf); logFmsg(3, "2nd pass: LOB is 0x%x", fetchbuf->lob);
         */

        switch (fetchbuf->type) {
        case OCI_TYPECODE_CLOB:
        case OCI_TYPECODE_BLOB:
            /*
             * we allocate descriptors for CLOBs; these are essentially
             * pointers.  We will not allocate any buffers for them until we're
             * actually fetching data from individual rows.
             */
            if (oraFerror(conn, OCIDescriptorAlloc(conn->envhp,
                                                   (void *) &fetchbuf->lob,
                                                   OCI_DTYPE_LOB,
                                                   0, 0)) != 0)
                return NULL;

            if (oraFerror(conn, OCIDefineByPos(conn->stmthp,
                                               &fetchbuf->def,
                                               conn->errhp,
                                               i + 1,
                                               (void *) &fetchbuf->lob,
                                               -1, fetchbuf->type,
                                               &fetchbuf->is_null,
                                               0, 0, OCI_DEFAULT)) != 0)
                return NULL;
            break;

        case SQLT_LNG:
            if (oraFerror(conn, OCIDefineByPos(conn->stmthp,
                                               &fetchbuf->def,
                                               conn->errhp,
                                               i + 1,
                                               0,
                                               (sb4) SB4MAXVAL,
                                               fetchbuf->type,
                                               &fetchbuf->is_null,
                                               &fetchbuf->fetch_length,
                                               0, OCI_DYNAMIC_FETCH)) != 0)
                return NULL;
            break;

        default:

            /* logFmsg(0, "definebypos %d 0x%x", i, fetchbuf->buf); */

            if (oraFerror(conn, OCIDefineByPos(conn->stmthp,
                                               &fetchbuf->def,
                                               conn->errhp,
                                               i + 1,
                                               fetchbuf->buf,
                                               fetchbuf->buf_size,
                                               SQLT_STR,
                                               &fetchbuf->is_null,
                                               &fetchbuf->fetch_length,
                                               NULL, OCI_DEFAULT)) != 0)
                return NULL;
            break;
        }
    }

    return set;
}


/*
 * Returns 1 for select or 2 for DML
 */
int oraFexec(oraTconn * conn, char *sql)
{
    ub4 iters = 0;
    ub2 type;
    sword status;

    assert(conn != NULL);
    assert(sql != NULL);

    status =
        OCIHandleAlloc(conn->envhp, (dvoid **) & conn->stmthp,
                       OCI_HTYPE_STMT, 0, NULL);
    if (oraFerror(conn, status) != 0)
        return -1;

    status =
        OCIStmtPrepare(conn->stmthp, conn->errhp, (text *) sql,
                       strlen(sql), OCI_NTV_SYNTAX, OCI_DEFAULT);
    if (oraFerror(conn, status) != 0)
        return -1;

    status =
        OCIAttrGet(conn->stmthp, OCI_HTYPE_STMT, (dvoid *) & type, NULL,
                   OCI_ATTR_STMT_TYPE, conn->errhp);
    if (oraFerror(conn, status) != 0)
        return -1;

    /*
     * Values returned for AttrGet OCI_HTYPE_STMT OCI_STMT_SELECT
     * OCI_STMT_UPDATE OCI_STMT_DELETE OCI_STMT_INSERT OCI_STMT_CREATE
     * OCI_STMT_DROP OCI_STMT_ALTER  OCI_STMT_BEGIN OCI_STMT_DECLARE
     */

    if (type != OCI_STMT_SELECT)
        iters = 1;

    status =
        OCIStmtExecute(conn->svchp, conn->stmthp, conn->errhp, iters, 0,
                       NULL, NULL, OCI_DEFAULT);
    if (oraFerror(conn, status) != 0)
        return -1;

    if (type == OCI_STMT_SELECT)
        return ORA_ROWS;
    else {
        OCITransCommit(conn->svchp, conn->errhp, OCI_DEFAULT);
        return ORA_DML;
    }
}

/*
 * TODO: Under error conditions thos code leaks any of the handles previously
 * allocated when it frees conn.
 */
oraTconn *oraFconnect(char *user, char *password, char *service)
{
    oraTconn *conn = malloc(sizeof(oraTconn));

    assert(user != NULL);
    assert(password != NULL);
    assert(service != NULL);

    assert(conn != NULL);

/*   logFmsg(3,"mod/oracle: oraFconnect() called. [u: %s] [s: %s]",user, service); */

    bzero(conn, sizeof(oraTconn));

    OCIHandleAlloc((dvoid *) NULL, (dvoid **) & conn->envhp,
                   (ub4) OCI_HTYPE_ENV, 52, (dvoid **) & tmp);

    OCIEnvInit(&conn->envhp, (ub4) OCI_DEFAULT, 21, (dvoid **) & tmp);

    OCIHandleAlloc((dvoid *) conn->envhp, (dvoid **) & conn->errhp,
                   (ub4) OCI_HTYPE_ERROR, 52, (dvoid **) & tmp);

    OCIHandleAlloc((dvoid *) conn->envhp, (dvoid **) & conn->srvhp,
                   (ub4) OCI_HTYPE_SERVER, 52, (dvoid **) & tmp);

    if (oraFerror
        (conn,
         OCIServerAttach(conn->srvhp, conn->errhp, (text *) service,
                         (sb4) strlen(service), (ub4) OCI_DEFAULT)) != 0) {
        free(conn);
        return NULL;
    }
    OCIHandleAlloc((dvoid *) conn->envhp, (dvoid **) & conn->svchp,
                   (ub4) OCI_HTYPE_SVCCTX, 52, (dvoid **) & tmp);

    /* set attribute server context in the service context */
    OCIAttrSet((dvoid *) conn->svchp, (ub4) OCI_HTYPE_SVCCTX,
               (dvoid *) conn->srvhp, (ub4) 0,
               (ub4) OCI_ATTR_SERVER, (OCIError *) conn->errhp);

    /* allocate a user context handle */
    OCIHandleAlloc((dvoid *) conn->envhp, (dvoid **) & conn->usrhp,
                   (ub4) OCI_HTYPE_SESSION, (size_t) 0, (dvoid **) 0);

    OCIAttrSet((dvoid *) conn->usrhp, (ub4) OCI_HTYPE_SESSION,
               (dvoid *) user, (ub4) strlen(user),
               OCI_ATTR_USERNAME, conn->errhp);

    OCIAttrSet((dvoid *) conn->usrhp, (ub4) OCI_HTYPE_SESSION,
               (dvoid *) password, (ub4) strlen(password),
               OCI_ATTR_PASSWORD, conn->errhp);

    if (oraFerror
        (conn,
         OCISessionBegin(conn->svchp, conn->errhp, conn->usrhp,
                         OCI_CRED_RDBMS, OCI_DEFAULT)) != 0) {
        free(conn);
        return NULL;
    }
    OCIAttrSet((dvoid *) conn->svchp, (ub4) OCI_HTYPE_SVCCTX,
               (dvoid *) conn->usrhp, (ub4) 0,
               OCI_ATTR_SESSION, conn->errhp);

    return conn;
}

int oraFclose(oraTconn * conn)
{

    /* these are basically redundant now, I think */
    oraFflush(conn);

    OCISessionEnd(conn->svchp, conn->errhp, conn->usrhp,
                  (ub4) OCI_DEFAULT);
    OCIServerDetach(conn->srvhp, conn->errhp, (ub4) OCI_DEFAULT);
    oraFerror(conn,
              OCIHandleFree((dvoid *) conn->srvhp,
                            (ub4) OCI_HTYPE_SERVER));
    oraFerror(conn,
              OCIHandleFree((dvoid *) conn->svchp,
                            (ub4) OCI_HTYPE_SVCCTX));
    oraFerror(conn,
              OCIHandleFree((dvoid *) conn->errhp, (ub4) OCI_HTYPE_ERROR));

    oraFerror(conn,
              OCIHandleFree((dvoid *) conn->envhp, (ub4) OCI_HTYPE_ENV));

    free(conn);

    return 0;
}

lstTset *oraFselect(oraTconn * conn, const char *sql)
{

    assert(conn != NULL);
    assert(sql != NULL);

    oraFflush(conn);

    if (oraFexec(conn, sql) == ORA_ROWS) {
        return oraFbindrow(conn);
    } else {
        return NULL;
    }
}

int oraFflush(oraTconn * conn)
{
    int i;

    assert(conn != NULL);

    if (conn->stmthp != NULL) {
        if (oraFerror(conn, OCIHandleFree(conn->stmthp, OCI_HTYPE_STMT)) !=
            0)
            return -1;
        conn->stmthp = NULL;
    }
    if (conn->fetch != NULL) {
        for (i = 0; i < conn->n_columns; i++) {
            oraTfetch *fetchbuf = &conn->fetch[i];
            /*
             * oraTfetch *fetchbuf = (oraTfetch *) conn->fetch +
             * (sizeof(oraTfetch) * i);
             */

            if (fetchbuf->lob != NULL) {
                if (oraFerror
                    (conn,
                     OCIDescriptorFree(fetchbuf->lob, OCI_DTYPE_LOB)) != 0)
                    return -1;
                fetchbuf->lob = NULL;
            }
            if (fetchbuf->buf != NULL)
                free(fetchbuf->buf);
            if (fetchbuf->array_values != NULL)
                free(fetchbuf->array_values);

            if (fetchbuf->lobs != 0) {
                int k;
                for (k = 0; k < fetchbuf->n_rows; k++) {
                    if (oraFerror
                        (conn,
                         OCIDescriptorFree(fetchbuf->lobs[k],
                                           OCI_DTYPE_LOB)) != 0)
                        return -1;
                }
                free(fetchbuf->lobs);
                fetchbuf->lobs = NULL;
                fetchbuf->n_rows = 0;
            }
        }
        free(conn->fetch);
        conn->fetch = NULL;
    }
    return 0;
}
