The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
/*
   engn/perldb2/dbdimp.c, engn_perldb2, db2_v6, 1.3 99/01/12 14:27:40

   Copyright (c) 1995,1996,1997,1998,1999 International Business Machines Corp.
*/

#include <stdio.h>
#include "DB2.h"

#define EOI(x)  if (x < 0 || x == 100) return (0)
#define NHENV   SQL_NULL_HENV
#define NHDBC   SQL_NULL_HDBC
#define NHSTMT  SQL_NULL_HDBC
#define ERRTYPE(x)    ((x == 1) && (x == SQL_ERROR))

DBISTATE_DECLARE;

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

void
do_error(SV *h, SQLINTEGER rc, SQLHENV h_env, SQLHDBC h_conn, SQLHSTMT h_stmt, 
     char *what)
{
    D_imp_xxh(h);
    SV *errstr = DBIc_ERRSTR(imp_xxh);
    short length;
    SV *state = DBIc_STATE(imp_xxh);
    SQLINTEGER  sqlcode;
    SQLCHAR  sqlstate[6];
    char msg[SQL_MAX_MESSAGE_LENGTH+1];
    SQLINTEGER msgsize = SQL_MAX_MESSAGE_LENGTH+1;

    msg[0]='\0';
    if (h_env != NHENV) {
        SQLError(h_env,h_conn,h_stmt, sqlstate, &sqlcode, (SQLCHAR *)msg,
                 msgsize,&length);
    } else {
        strcpy((char *)msg, (char *)what);
    }

    sv_setiv(DBIc_ERR(imp_xxh), (IV)sqlcode);
    sv_setpv(errstr, (char *)msg);
    sv_setpv(state,(char  *)sqlstate);
    if (what && (h_env == NHENV)) {
        sv_catpv(errstr, " (DBD: ");
        sv_catpv(errstr, (char  *)what);
        sv_catpv(errstr, ")");
    }
    DBIh_EVENT2(h, ERROR_event, DBIc_ERR(imp_xxh), errstr);
    if (dbis->debug >= 2)
        fprintf(DBILOGFP, "%s error %d recorded: %s\n",
                what, rc, SvPV(errstr,na));
}

int
check_error(h, rc, what)
SV *h;
IV rc;
char *what;
{
    D_imp_xxh(h);
    struct imp_dbh_st *imp_dbh = NULL;
    struct imp_sth_st *imp_sth = NULL;
    SQLHENV h_env = SQL_NULL_HENV;
    SQLHDBC h_conn = SQL_NULL_HDBC;
    SQLHSTMT h_stmt = SQL_NULL_HSTMT;

    if (rc == SQL_SUCCESS || rc == SQL_NO_DATA) {
        return(rc);
    }
    switch(DBIc_TYPE(imp_xxh)) {
        case DBIt_ST:
            imp_sth = (struct imp_sth_st *)(imp_xxh);
            imp_dbh = (struct imp_dbh_st *)(DBIc_PARENT_COM(imp_sth));
            h_stmt = imp_sth->phstmt;
            break;
        case DBIt_DB:
            imp_dbh = (struct imp_dbh_st *)(imp_xxh);
            break;
        default:
            croak("panic dbd_error on bad handle type");
    }
    h_conn = imp_dbh->hdbc;
    h_env = imp_dbh->henv;

    if (h_env == NHENV) {
        do_error(h,rc,NHENV,NHDBC,NHSTMT,what);
    } else {
        if (rc == SQL_SUCCESS_WITH_INFO) {
            if (dbis->debug > 2) {
                   do_error(h,rc,h_env,h_conn,h_stmt,what);
            }
        } else {
            do_error(h,rc,h_env,h_conn,h_stmt,what);
        }
    }
    return((rc == SQL_SUCCESS_WITH_INFO ? SQL_SUCCESS : rc));
}

void
fbh_dump(fbh, i)
    imp_fbh_t *fbh;
    int i;
{
    FILE *fp = DBILOGFP;
    fprintf(fp, "fbh %d: '%s' %s, ",
        i, fbh->cbuf, (fbh->nullok) ? "NULLable" : "");
    fprintf(fp, "type %d,  dbsize %ld, dsize %ld, p%d s%d\n",
        fbh->dbtype, (long)fbh->dbsize, (long)fbh->dsize,
        fbh->prec, fbh->scale);
    fprintf(fp, "   out: ftype %d, indp %d, bufl %d, rlen %d, rcode %d\n",
        fbh->ftype, fbh->indp, fbh->bufl, fbh->rlen, fbh->rcode);
}

static int
dbtype_is_long(dbtype)
    SQLINTEGER dbtype;
{
    /* Is it a LONG, LONG RAW, LONG VARCHAR or LONG VARRAW type?    */
    /* Return preferred type code to use if it's a long, else 0.    */
    if (dbtype == SQL_CLOB || dbtype == SQL_BLOB)    /* LONG or LONG RAW */
    return dbtype;            	                     /*     --> same     */
    if (dbtype == SQL_LONGVARCHAR)                   /* LONG VARCHAR     */
    return SQL_CLOB;                                 /*     --> LONG     */
    if (dbtype == SQL_LONGVARBINARY)                 /* LONG VARRAW      */
    return SQL_BLOB;                                 /*     --> LONG RAW */
    return 0;
}

static int
dbtype_is_string(dbtype)    /* 'can we use SvPV to pass buffer?'    */
    SQLINTEGER dbtype;
{
    switch(dbtype) {
        case SQL_VARCHAR:        /* VARCHAR2    */
        case SQL_INTEGER:            /* LONG        */
        case SQL_CHAR:            /* RAW        */
        case SQL_LONGVARBINARY:    /* LONG RAW    */
        case SQL_LONGVARCHAR:    /* LONG VARCHAR*/
        case SQL_CLOB:            /* Char blob */
        return 1;
    }
    return 0;
}


/* ================================================================== */


/* ================================================================== */

int
dbd_db_connect(dbh,imp_dbh,dbname,uid,pwd)
    SV *dbh;
    imp_dbh_t *imp_dbh; 
    SQLCHAR  *dbname,*uid,*pwd;
{
    D_imp_drh_from_dbh;
    char *msg;
    SQLINTEGER ret;
    
    ret = SQLAllocHandle(SQL_HANDLE_DBC, imp_drh->henv, 
                         &imp_dbh->hdbc);                         
    msg = (ERRTYPE(ret) ? "Connect allocation failed" :
                                "Invalid Handle");
    ret = check_error(dbh,ret,msg);
       if (ret != SQL_SUCCESS) { 
        if (imp_drh->connects == 0) {
            SQLFreeEnv(imp_drh->henv);
            imp_drh->henv = NHENV;
        }
        return(ret);        /* Must return SQL codes not perl/DBD/DBI */
    }                        /* otherwise failure is not caught  */

    if (dbis->debug >= 2)
        fprintf(DBILOGFP, "connect '%s', '%s', '%s'", dbname, uid, pwd);
                                    
    ret = SQLConnect(imp_dbh->hdbc,dbname,SQL_NTS,uid,SQL_NTS,pwd,SQL_NTS);
    msg = ( ERRTYPE(ret) ? "Connect failed" : "Invalid handle");
    ret = check_error(dbh,ret,msg);
       if (ret != SQL_SUCCESS) {    
        SQLFreeConnect(imp_dbh->hdbc);
        if (imp_drh->connects == 0) {
            SQLFreeEnv(imp_drh->henv);
            imp_drh->henv = SQL_NULL_HENV;
        }
        return(ret);            /* Must return SQL codes not perl/DBD/DBI */
    }                             /* otherwise failure is not caught  */
    /* DBI spec requires AutoCommit on */
    ret = SQLSetConnectAttr(imp_dbh->hdbc, SQL_ATTR_AUTOCOMMIT,   
                            (void *)SQL_AUTOCOMMIT_ON, 0);        
    msg = (ERRTYPE(ret) ? "SetConnectAttr Failed" : "Invalid handle");
    ret = check_error(dbh,ret,msg);
    if (ret != SQL_SUCCESS) {
        SQLFreeConnect(imp_dbh->hdbc);
        if (imp_drh->connects == 0) {
            SQLFreeEnv(imp_drh->henv);
            imp_drh->henv = NHENV;
        }
        return(ret);        /* Must return SQL codes not perl/DBD/DBI */
    }                       /* otherwise failure is not caught  */

    DBIc_set(imp_dbh,DBIcf_AutoCommit, 1);

    return 1;
}

int
dbd_db_login(dbh, imp_dbh, dbname, uid, pwd)
    SV *dbh;
    imp_dbh_t *imp_dbh;
    char  *dbname;
    char  *uid;
    char  *pwd;
{
    D_imp_drh_from_dbh;
    SQLINTEGER ret;
    char *msg;

    if (! imp_drh->connects) {
        ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE,     
	                     &imp_drh->henv);                     
        msg = (imp_drh->henv == NHENV ?
                "Total Environment allocation failure!" :
                "Environment allocation failed");
        ret = check_error(dbh,ret,msg);
        EOI(ret);
                                                                 
        /* If an application is run as an ODBC application, the         */
	/* SQL_ATTR_ODBC_VERSION environment attribute must be set;	*/
	/* otherwise, an error will be returned when an attempt is 	*/
	/* made to allocate a connection handle.			*/
	ret = SQLSetEnvAttr( imp_drh->henv, SQL_ATTR_ODBC_VERSION,
	                     (SQLPOINTER) SQL_OV_ODBC3, 0 );
        msg = "SQLSetEnvAttr failed";
	ret = check_error(dbh, ret, msg);
	EOI(ret);                                                
    } 
    imp_dbh->henv = imp_drh->henv;
    ret = dbd_db_connect(dbh,imp_dbh,dbname, uid, pwd);
    EOI(ret);
    imp_drh->connects++;

    DBIc_IMPSET_on(imp_dbh);    /* imp_dbh set up now            */
    DBIc_ACTIVE_on(imp_dbh);    /* call disconnect before freeing    */
    return 1;
}


int
dbd_db_do(dbh, statement) /* error : <=(-2), ok row count : >=0, unknown count : (-1)	*/
    SV *dbh;
    char *statement;
{
    D_imp_dbh(dbh);
    SQLINTEGER ret, rows;                                         
    char *msg;
    SQLHSTMT stmt;

    ret = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc,          
                         &stmt);                                  
    msg = "Statement allocation error";
    ret = check_error(dbh, ret, msg);
    if (ret < 0)                                                  
        return(-2);                                               

    ret = SQLExecDirect(stmt, (SQLCHAR *)statement, SQL_NTS);
    msg = "Execute immediate failed";                            
    ret = check_error(dbh, ret, msg);
    if (ret < 0)
        rows = -2;
    else {
        ret = SQLRowCount(stmt, &rows);
        msg = "SQLRowCount failed";
        ret = check_error(dbh, ret, msg);
	if (ret < 0)
	    rows = -1;
    }

    ret = SQLFreeStmt(stmt, SQL_DROP);
    msg = "Statement destruction error";
    (void) check_error(dbh, ret, msg);
        
    return rows;                                                 
}


int
dbd_db_commit(dbh,imp_dbh)
    SV *dbh;
    imp_dbh_t    *imp_dbh;
{
    SQLINTEGER ret;
    char *msg;

    ret = SQLTransact(imp_dbh->henv,imp_dbh->hdbc,SQL_COMMIT);
    msg = (ERRTYPE(ret)  ? "Commit failed" : "Invalid handle");
    ret = check_error(dbh,ret,msg);
    EOI(ret);
    return 1;
}

int
dbd_db_rollback(dbh,imp_dbh)
    SV *dbh;
    imp_dbh_t    *imp_dbh;
{
    SQLINTEGER ret;
    char *msg;

    ret = SQLTransact(imp_dbh->henv,imp_dbh->hdbc,SQL_ROLLBACK);
    msg = (ERRTYPE(ret)  ? "Rollback failed" : "Invalid handle");
    ret = check_error(dbh,ret,msg);
    EOI(ret);

    return 1;
}


int
dbd_db_disconnect(dbh,imp_dbh)
    SV *dbh;
    imp_dbh_t *imp_dbh;
{
    D_imp_drh_from_dbh;
    SQLINTEGER ret;
    char *msg;

    ret = SQLDisconnect(imp_dbh->hdbc);
    msg = (ERRTYPE(ret)  ? "Disconnect failed" : "Invalid handle");
    ret = check_error(dbh,ret,msg);
    EOI(ret);
                                                                 
    /* Only turn off the ACTIVE attribute of the database handle	*/
    /* if SQLDisconnect() was successful.  If it wasn't successful,	*/
    /* we still have a connection!					*/

    DBIc_ACTIVE_off(imp_dbh);                                    

    ret = SQLFreeConnect(imp_dbh->hdbc);
    msg = (ERRTYPE(ret)  ? "Free connect failed" : "Invalid handle");
    ret = check_error(dbh,ret,msg);
    EOI(ret);

    imp_dbh->hdbc = SQL_NULL_HDBC;
    imp_drh->connects--;
    if (imp_drh->connects == 0) {
        ret = SQLFreeEnv(imp_drh->henv);
        msg = (ERRTYPE(ret)  ? "Free henv failed" : "Invalid handle");
        ret = check_error(dbh,ret,msg);
        EOI(ret);
    }

    /* We don't free imp_dbh since a reference still exists    */
    /* The DESTROY method is the only one to 'free' memory.    */
    /* Note that statement objects may still exists for this dbh!    */
    return 1;
}


void
dbd_db_destroy(dbh)
    SV *dbh;
{
    D_imp_dbh(dbh);
    if (DBIc_ACTIVE(imp_dbh))
        dbd_db_disconnect(dbh,imp_dbh);
    /* Nothing in imp_dbh to be freed    */
    DBIc_IMPSET_off(imp_dbh);
}


int
dbd_db_STORE(dbh, keysv, valuesv)
    SV *dbh;
    SV *keysv;
    SV *valuesv;
{
    D_imp_dbh(dbh);
    STRLEN kl;
    char *key = SvPV(keysv,kl);
    SV *cachesv = NULL;
    int on = SvTRUE(valuesv);
    SQLINTEGER ret;
    char *msg;
    
    if (kl==10 && strEQ(key, "AutoCommit")) {
	ret = SQLSetConnectAttr(imp_dbh->hdbc,                   
	                        SQL_ATTR_AUTOCOMMIT,
              (on ? (void *)SQL_AUTOCOMMIT_ON : (void *)SQL_AUTOCOMMIT_OFF),
		                0);                              
        if ( ret ) {
            msg = (ERRTYPE(ret) ? "Change of AUTOCOMMIT failed" :
                                  "Invalid Handle");
            ret = check_error(dbh,ret,msg);
            cachesv = &sv_undef;
        } else {
            cachesv = (on) ? &sv_yes : &sv_no;    /* cache new state */
            DBIc_set(imp_dbh, DBIcf_AutoCommit, on);              
        }
    } else {
        return FALSE;
    }
    if (cachesv) /* cache value for later DBI 'quick' fetch? */
        hv_store((HV*)SvRV(dbh), key, kl, cachesv, 0);
    return TRUE;
}


SV *
dbd_db_FETCH(dbh, keysv)
    SV *dbh;
    SV *keysv;
{
    /* D_imp_dbh(dbh); */
    STRLEN kl;
    char *key = SvPV(keysv,kl);
    SV *retsv = NULL;
    /* Default to caching results for DBI dispatch quick_FETCH    */
    int cacheit = TRUE;

    if (1) {        /* no attribs defined yet    */
    return Nullsv;
    }
    if (cacheit) {    /* cache for next time (via DBI quick_FETCH)    */
    hv_store((HV*)SvRV(dbh), key, kl, retsv, 0);
    (void)SvREFCNT_inc(retsv);    /* so sv_2mortal won't free it    */
    }
    return sv_2mortal(retsv);
}



/* ================================================================== */


int
dbd_st_prepare(sth, statement, attribs)
    SV *sth;
    char *statement;
    SV *attribs;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    SQLINTEGER ret =0;
    short params =0;
    char *msg;

    imp_sth->done_desc = 0;

    ret = SQLAllocHandle(SQL_HANDLE_STMT, imp_dbh->hdbc,          
                         &imp_sth->phstmt);                       
    msg = "Statement allocation error";
    ret = check_error(sth,ret,msg);

    EOI(ret);

    ret = SQLPrepare(imp_sth->phstmt,(SQLCHAR *)statement,SQL_NTS);
    msg = "Statement preparation error";
    ret = check_error(sth,ret,msg);

    EOI(ret);

    if (dbis->debug >= 2)                                        
        fprintf(DBILOGFP, "    dbd_st_prepare'd sql f%d\n\t%s\n",
	        imp_sth->phstmt, statement);                     

    ret = SQLNumParams(imp_sth->phstmt,&params);
    msg = "Unable to determine number of parameters";
    ret = check_error(sth,ret,msg);
    EOI(ret);
    
    DBIc_NUM_PARAMS(imp_sth) = params;

    if (params > 0 ){
        /* scan statement for '?', ':1' and/or ':foo' style placeholders*/    
        dbd_preparse(imp_sth, statement); 
    } else {    /* assumming a parameterless select */
        dbd_describe(sth,imp_sth );
    }

    /* initialize sth pointers */
    imp_sth->RowCount = -1;                                       
	imp_sth->has_inouts = 0;

    DBIc_IMPSET_on(imp_sth);
    return 1;
}


void
dbd_preparse(imp_sth, statement)
    imp_sth_t *imp_sth;
    char *statement;
{
    bool in_literal = FALSE;
    SQLCHAR  *src, *start, *dest;
    phs_t phs_tpl;
    SV *phs_sv;
    int idx=0, style=0, laststyle=0;

    /* allocate room for copy of statement with spare capacity    */
    /* for editing ':1' SQLINTEGERo ':p1' so we can use obndrv.    */
    imp_sth->statement = (SQLCHAR *)safemalloc(strlen(statement) + 
                            (DBIc_NUM_PARAMS(imp_sth)*4));

    /* initialise phs ready to be cloned per placeholder    */
    memset(&phs_tpl, '\0',sizeof(phs_tpl));
    phs_tpl.ftype = 1;    /* VARCHAR2 */

    src  = (SQLCHAR *)statement;
    dest = imp_sth->statement;
    while(*src) {
    if (*src == '\'')
        in_literal = ~in_literal;
    if ((*src != ':' && *src != '?') || in_literal) {
        *dest++ = *src++;
        continue;
    }
    start = dest;            /* save name inc colon    */ 
    *dest++ = *src++;
    if (*start == '?') {        /* X/Open standard    */
        sprintf((char *)start,":p%d", ++idx); /* '?' -> ':1' (etc)*/
        dest = start+strlen((char *)start);
        style = 3;

    } else if (isDIGIT(*src)) {    /* ':1'        */
        idx = atoi((char *)src);
        *dest++ = 'p';        /* ':1'->':p1'    */
        if (idx > MAX_BIND_VARS || idx <= 0)
        croak("Placeholder :%d index out of range", idx);
        while(isDIGIT(*src))
        *dest++ = *src++;
        style = 1;

    } else if (isALNUM(*src)) {    /* ':foo'    */
        while(isALNUM(*src))    /* includes '_'    */
        *dest++ = *src++;
        style = 2;
    } else {            /* perhaps ':=' PL/SQL construct */
        continue;
    }
    *dest = '\0';            /* handy for debugging    */
    if (laststyle && style != laststyle)
        croak("Can't mix placeholder styles (%d/%d)",style,laststyle);
    laststyle = style;
    if (imp_sth->bind_names == NULL)
        imp_sth->bind_names = newHV();
    phs_tpl.sv = &sv_undef;
	phs_tpl.is_inout = 0;
    phs_sv = newSVpv((char *)&phs_tpl, sizeof(phs_tpl));
    hv_store(imp_sth->bind_names, (char *)start, 
                             (STRLEN)(dest-start), phs_sv, 0);
    /* warn("bind_names: '%s'\n", start);    */
    }
    *dest = '\0';
    if (imp_sth->bind_names) {
    DBIc_NUM_PARAMS(imp_sth) = (SQLINTEGER)HvKEYS(imp_sth->bind_names);
    if (dbis->debug >= 2)
        fprintf(DBILOGFP, "scanned %d distinct placeholders\n",
        (SQLINTEGER)DBIc_NUM_PARAMS(imp_sth));
    }
}

int
dbd_bind_ph(sth, ph_namesv, newvalue, attribs)
    SV *sth;
    SV *ph_namesv;		/* index of parameter 1..n */
    SV *newvalue;
    SV *attribs;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    SV **svp;
    STRLEN name_len;
    SQLCHAR  *name;
    phs_t *phs;

    STRLEN value_len;
    void  *value_ptr;
    SQLINTEGER ret;
    char *msg;
    short param = SQL_PARAM_INPUT,
          ctype = SQL_C_DEFAULT, stype = SQL_CHAR, scale = 0;
    unsigned prec=0;
    SQLINTEGER nullok = SQL_NTS;

    if (SvNIOK(ph_namesv) ) {    /* passed as a number    */
        SQLCHAR  buf[90];
        name = buf;
        sprintf((char *)name, ":p%d", (int)SvIV(ph_namesv));
        name_len = strlen((char *)name);
    } else {
        name = (SQLCHAR *)SvPV(ph_namesv, name_len);
    }

    if (dbis->debug >= 2)
        fprintf(DBILOGFP, "bind %s <== '%s' (attribs: %s)\n",
            name, SvPV(newvalue,na), attribs ? SvPV(attribs,na) : "<no attribs>" );

    svp = hv_fetch(imp_sth->bind_names, (char *)name, name_len, 0);
    if (svp == NULL)
        croak("Can't bind unknown parameter marker '%s'", name);  
    phs = (phs_t*)((void*)SvPVX(*svp));        /* placeholder struct    */

    if (phs->sv == &sv_undef) {     /* first bind for this placeholder    */
    phs->ftype = 1;
    }

    if (attribs) {
    /* Setup / Clear attributes as defined by attribs.        */
    /* If attribs is EMPTY then attribs are defaulted.        */
    if ( (svp =hv_fetch((HV*)SvRV(attribs), "ParamT",6, 0)) != NULL) 
            param = phs->ftype = SvIV(*svp);
    if ( (svp =hv_fetch((HV*)SvRV(attribs), "Stype",5, 0)) != NULL) 
            stype = phs->ftype = SvIV(*svp);
        if ( (svp=hv_fetch((HV*)SvRV(attribs), "Ctype",5, 0)) != NULL) 
            ctype = SvIV(*svp);
        if ( (svp=hv_fetch((HV*)SvRV(attribs), "Prec",4, 0)) != NULL) 
            prec = SvIV(*svp);
        if ( (svp=hv_fetch((HV*)SvRV(attribs), "Scale",5, 0)) != NULL) 
            scale = SvIV(*svp);
        if ( (svp=hv_fetch((HV*)SvRV(attribs), "Nullok",6, 0)) != NULL) 
            nullok = SvIV(*svp);


    } /* else if NULL / UNDEF then default to values assigned at top */
    /* This approach allows maximum performance when    */
    /* rebinding parameters often (for multiple executes).    */

    /* At the moment we always do sv_setsv() and rebind.    */
    /* Later we may optimise this so that more often we can    */
    /* just copy the value & length over and not rebind.    */

	{
	  bool is_inout = !(param == SQL_PARAM_INPUT);

	  if ((!is_inout && phs->is_inout) ||
		  (is_inout && !phs->is_inout && phs->sv != &sv_undef)) {
		(void)SvREFCNT_dec(phs->sv);
		phs->sv = &sv_undef;
	  }
	  phs->is_inout = is_inout;

	  if (phs->sv == &sv_undef && !is_inout) {     /* first bind for this placeholder    */
		phs->sv = newSV(0);
	  }

	  if (is_inout) {
		int newlen;

		imp_sth->has_inouts = 1;

        if (SvREADONLY(newvalue))
		  croak("Cannot modify readonly SV");
        (void)SvUPGRADE(newvalue, SVt_PVNV);
		newlen = (prec < 28) ? 28 : prec+1;
        SvGROW(newvalue, newlen);

		if (SvOK(newvalue)) {
		  phs->sv = newvalue;
		  SvREFCNT_inc(phs->sv);
		  value_ptr = SvPV(phs->sv, value_len);
		  value_len = newlen;
		  phs->indp = 0;
		} else {
		  croak("Can't bind non-scalar in inout position");
		}
	  } else {
		if (SvOK(newvalue)) {
		  sv_setsv(phs->sv, newvalue);
		  value_ptr = SvPV(phs->sv, value_len);
		  phs->indp = 0;
		} else {
		  sv_setsv(phs->sv,0); 
		  value_ptr = "";
		  value_len = 0;
		  phs->indp = SQL_NULL_DATA;
		}
	  }
	}

    if (!nullok && !SvOK(phs->sv)) {
        fprintf(stderr,"phs->sv is not OK\n");
    }    

    if (dbis->debug >= 2)                                        
        fprintf(DBILOGFP,
	        "  bind %s: ParmType=%d, Ctype=%d, SQLtype=%d, Prec=%d, Scale=%d\n",
		name, param, ctype, stype, prec, scale);         

	{
	  char *cptr = (phs->indp == 0)?SvPVX(phs->sv):NULL;

    ret = SQLBindParameter(imp_sth->phstmt,(SQLINTEGER)SvIV(ph_namesv),
                           param,ctype,stype,
						   (prec > 0 ? prec : value_len),
						   scale,  
                           cptr,  
						   value_len,                             
	                   (phs->is_inout || (phs->indp != 0 && nullok))?&phs->indp:NULL);
	}
    msg = ( ERRTYPE(ret) ? "Bind failed" : "Invalid Handle");
    ret = check_error(sth,ret,msg);
    EOI(ret);

    return 1;
}

int
dbd_describe(h, imp_sth)
    SV *h;
    imp_sth_t *imp_sth;
{
    D_imp_dbh_from_sth;
    SQLCHAR  *cbuf_ptr;
    SQLINTEGER t_cbufl=0;
    short f_cbufl[MAX_COLS];
    short num_fields;
    SQLINTEGER i, ret;
    char *msg;

    if (imp_sth->done_desc)
        return 1;    /* success, already done it */
    imp_sth->done_desc = 1;

    ret = SQLNumResultCols(imp_sth->phstmt,&num_fields);
    msg = ( ERRTYPE(ret) ? "SQLNumResultCols failed" : "Invalid Handle");
    ret = check_error(h,ret,msg);
    EOI(ret);
    DBIc_NUM_FIELDS(imp_sth) = num_fields;

    /* allocate field buffers                */
    Newz(42, imp_sth->fbh,      num_fields, imp_fbh_t);
    /* allocate a buffer to hold all the column names    */
    Newz(42, imp_sth->fbh_cbuf,
        (num_fields * (MAX_COL_NAME_LEN+1)), SQLCHAR );
    cbuf_ptr = imp_sth->fbh_cbuf;

    /* Get number of fields and space needed for field names    */
    for(i=0; i < num_fields; ++i ) {
        SQLCHAR  cbuf[MAX_COL_NAME_LEN+1];                        
        imp_fbh_t *fbh = &imp_sth->fbh[i];
        f_cbufl[i] = sizeof(cbuf);

        ret = SQLDescribeCol(imp_sth->phstmt,i+1,cbuf_ptr,MAX_COL_NAME_LEN,
                &f_cbufl[i],&fbh->dbtype,&fbh->prec,&fbh->scale,&fbh->nullok);
        msg    = (ERRTYPE(ret) ? "DescribeCol failed" : "Invalid Handle");
        ret = check_error(h,ret,msg);
        EOI(ret);
        ret = SQLColAttribute(imp_sth->phstmt,i+1,                
	                      SQL_DESC_OCTET_LENGTH,              
                              NULL, 0, NULL ,&fbh->dbsize);
        msg    = (ERRTYPE(ret) ? "ColAttribute failed" : "Invalid Handle");
        ret = check_error(h,ret,msg);
        EOI(ret);
        ret = SQLColAttribute(imp_sth->phstmt,i+1,                
	                      SQL_DESC_DISPLAY_SIZE,              
                              NULL, 0, NULL ,&fbh->dsize);
        msg    = (ERRTYPE(ret) ? "ColAttribute failed" : "Invalid Handle");
        ret = check_error(h,ret,msg);
        EOI(ret);

        fbh->imp_sth = imp_sth;
        fbh->cbuf    = cbuf_ptr;
        fbh->cbufl   = f_cbufl[i];
        fbh->cbuf[fbh->cbufl] = '\0';     /* ensure null terminated    */
        cbuf_ptr += fbh->cbufl + 1;     /* increment name poSQLINTEGERer    */

        /* Now define the storage for this field data.            */
    
        fbh->ftype = SQL_C_CHAR ;
        fbh->rlen = fbh->bufl  = fbh->dsize+1;/* +1: STRING null terminator    */

        /* currently we use an sv, later we'll use an array    */
        fbh->sv = newSV((STRLEN)fbh->bufl);
        (void)SvUPGRADE(fbh->sv, SVt_PV);
        SvREADONLY_on(fbh->sv);
        (void)SvPOK_only(fbh->sv);
        fbh->buf = (SQLCHAR  *)SvPVX(fbh->sv);
    
        /* BIND */
        ret = SQLBindCol(imp_sth->phstmt,i+1,SQL_C_CHAR,fbh->buf,
                    fbh->bufl,&fbh->rlen);
        if (ret == SQL_SUCCESS_WITH_INFO ) {
            warn("BindCol error on %s: %d", fbh->cbuf);
        } else {
            msg = (ERRTYPE(ret) ? "BindCol failed" : "Invalid Handle");
            ret = check_error(h,ret,msg);
            EOI(ret);
        }

        if (dbis->debug >= 2)
            fbh_dump(fbh, i);
    }
    return 1;
}

int
dbd_conn_opt(sth, opt, value)
    SV *sth;
    IV  opt;
    IV  value;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    char *msg;
    SQLINTEGER ret ;

    ret = SQLSetConnectOption(imp_dbh->hdbc,opt, value);
    msg = "SQLSetConnectOption failed";
    ret = check_error(sth,ret,msg);
    EOI(ret);

    return 1;
}

int
dbd_st_opts(sth, opt, value)
    SV *sth;
    IV    opt;
    IV    value;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    char *msg;
    SQLINTEGER ret ;

    ret = SQLSetStmtOption(imp_sth->phstmt,opt, value);
    msg = "SQLSetStmtOption failed";
    ret = check_error(sth,ret,msg);
    EOI(ret);

    return 1;
}

int
dbd_st_execute(sth)	/* error : <=(-2), ok row count : >=0, unknown count : (-1) 	*/
    SV *sth;
{
    D_imp_sth(sth);
    char *msg;
    SQLINTEGER ret;
 
    ret = SQLExecute(imp_sth->phstmt);
    msg = "SQLExecute failed";
    ret = check_error(sth,ret,msg);
    if (ret < 0)                                                 
    	return(-2);

    if (imp_sth->bind_names && imp_sth->has_inouts) {
	  HV *hv = imp_sth->bind_names;
	  SV *sv;
	  char *key;
	  I32 retlen;
	  hv_iterinit(hv);
	  while( (sv = hv_iternextsv(hv, &key, &retlen)) != NULL ) {
        phs_t *phs;
        if (sv != &sv_undef) {
		  phs = (phs_t*)SvPVX(sv);
		  if (phs->is_inout) {
			if (phs->indp == SQL_NULL_DATA) {
			  SvCUR_set(phs->sv,0);
			} else if (phs->indp == SQL_NO_TOTAL) {
			  warn("inout parameter length was not determinable after statement execution");
			} else if (phs->indp <= SvLEN(phs->sv)) {
			  SvCUR_set(phs->sv,phs->indp);
			} else {
			  warn("inout parameter length was too long -- possible memory corruption");
			}
		  }
        }
	  }
    }

    /* describe and allocate storage for results        */
    if (!imp_sth->done_desc && !dbd_describe(sth, imp_sth)) {
        /* dbd_describe has already called check_error()        */
        return -2;                                                
    }

    ret = SQLRowCount(imp_sth->phstmt, &imp_sth->RowCount);
    msg = "SQLRowCount failed";
    ret = check_error(sth, ret, msg);                            
    
    DBIc_ACTIVE_on(imp_sth);
    return imp_sth->RowCount;                                     
}



AV *
dbd_st_fetch(sth)
    SV *    sth;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    SQLINTEGER debug = dbis->debug;
    SQLINTEGER num_fields;
    SQLINTEGER ChopBlanks;
    SQLINTEGER i,ret;
    AV *av;
    char *msg;

    /* Check that execute() was executed sucessfuly. This also implies    */
    /* that dbd_describe() executed sucessfuly so the memory buffers    */
    /* are allocated and bound.                        */
    if ( !DBIc_ACTIVE(imp_sth) ) {
        check_error(sth, -3,"no statement executing");
        return Nullav;
    }

	/*
	if (imp_sth->RowCount == -1) {
	  warn("no row-set from preceding statement");
	  return Nullav;
	}
    */
    ret = SQLFetch(imp_sth->phstmt);
    if (ret != SQL_SUCCESS && ret != SQL_SUCCESS_WITH_INFO) {
        if (ret != SQL_NO_DATA_FOUND) {    /* was not just end-of-fetch    */
            if (debug >= 3)                                                
                fprintf(DBILOGFP, "    dbd_st_fetch failed, rc=%d", ret);  
            msg = (ERRTYPE(ret) ? "Fetch failed" : "Invalid Handle");
            check_error(sth,ret,msg);
        }
        dbd_st_finish(sth, imp_sth);                              
        return Nullav;
    }

    av = DBIS->get_fbav(imp_sth);
    num_fields = AvFILL(av)+1;

    if (debug >= 3)
    fprintf(DBILOGFP, "    dbd_st_fetch %d fields\n", num_fields);

    ChopBlanks = DBIc_has(imp_sth, DBIcf_ChopBlanks);
    for(i=0; i < num_fields; ++i) {
        imp_fbh_t *fbh = &imp_sth->fbh[i];
        SV *sv = AvARRAY(av)[i]; /* Note: we reuse the supplied SV    */

        if (fbh->rlen > -1) {    /* normal case - column is not null */
            SvCUR(fbh->sv) = fbh->rlen;
            sv_setsv(sv,fbh->sv);
        } else {                /*  column contains a null value */
               fbh->indp = fbh->rlen;
            fbh->rlen = 0; 
            (void)SvOK_off(sv);
        } 


        if (debug >= 2)
            fprintf(DBILOGFP, "\t%d: rc=%d '%s'\n", i, ret, SvPV(sv,na));
    }
    return av;
}

int
dbd_st_blob_read(sth, field, offset, len, destrv, destoffset)
    SV *sth;
    int field;
    long offset;
    long len;
    SV *destrv;
    long destoffset;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    SQLINTEGER retl=0;
    SV *bufsv;
    SQLINTEGER rtval=0;
    char *msg;
    
    bufsv = SvRV(destrv);
    sv_setpvn(bufsv,"",0);    /* ensure it's writable string    */
    SvGROW(bufsv, len+destoffset+1);    /* SvGROW doesn't do +1    */

    rtval = SQLGetData(imp_sth->phstmt,field,SQL_C_BINARY,
                 SvPVX(bufsv)+destoffset,len,&retl);

    if (rtval == SQL_SUCCESS_WITH_INFO) {    /* XXX should check for 01004 */
    retl = len;
    }

    if (retl == SQL_NULL_DATA) {    /* field is null    */
    (void)SvOK_off(bufsv);
    return 1;
    }
    msg = (ERRTYPE(rtval) ? "GetData failed to read blob":"Invalid Handle");
    rtval = check_error(sth,rtval,msg);
    EOI(rtval);
    
    SvCUR_set(bufsv, destoffset+retl );
    *SvEND(bufsv) = '\0'; /* consistent with perl sv_setpvn etc    */

    return 1;
}


int
dbd_st_rows(sth, imp_sth)
    SV *sth;
    imp_sth_t *imp_sth;
{
    return imp_sth->RowCount;                                     
}


int
dbd_st_finish(sth,imp_sth)
    SV *sth;
    imp_sth_t *imp_sth;
{
    D_imp_dbh_from_sth; 
    SQLINTEGER ret;
    char *msg;

    /* Cancel further fetches from this cursor.  We don't        */ 
    /* close the cursor (SQL_DROP) 'til DESTROY (dbd_st_destroy).*/
    /* The application may call execute(...) again on the same   */
    /* statement handle.                                         */ 

    if (DBIc_ACTIVE(imp_sth) ) {
        ret = SQLFreeStmt(imp_sth->phstmt,SQL_CLOSE);
        msg = (ERRTYPE(ret) ? "SQLFreeStmt failed" : "Invalid Handle");
        ret = check_error(sth,ret,msg);
        EOI(ret);
    }
    DBIc_ACTIVE_off(imp_sth);
    return 1;
}


int
dbd_st_destroy(sth)
    SV *sth;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    SQLINTEGER i;

    /* Free off contents of imp_sth    */

    for(i=0; i < DBIc_NUM_FIELDS(imp_sth); ++i) {
        imp_fbh_t *fbh = &imp_sth->fbh[i];
        sv_free(fbh->sv);
    }
    Safefree(imp_sth->fbh);
    Safefree(imp_sth->fbh_cbuf);
    Safefree(imp_sth->statement);

    if (imp_sth->bind_names) {
    HV *hv = imp_sth->bind_names;
    SV *sv;
    char *key;
    I32 retlen;
    hv_iterinit(hv);
    while( (sv = hv_iternextsv(hv, &key, &retlen)) != NULL ) {
        phs_t *phs_tpl;
        if (sv != &sv_undef) {
        phs_tpl = (phs_t*)SvPVX(sv);
        sv_free(phs_tpl->sv);
        }
    }
    sv_free((SV*)imp_sth->bind_names);
    }
/*
 * Chet Murthy's patch for DB2 heap overflow problems
*/
    /* Check if an explicit disconnect() or global destruction has	*/ 
    /* disconnected us from the database before attempting to drop	*/
    /* the CLI statement handle.  If we don't do this, a coredump can	*/
    /* potentially occur.						*/
    if (DBIc_ACTIVE(imp_dbh)) {                                            
        i = SQLFreeStmt (imp_sth->phstmt, SQL_DROP);
        if (i != SQL_SUCCESS && i != SQL_INVALID_HANDLE) {
            i = check_error(NULL,i, "Statement destruction error");
        }
    }
/* End Chet */

    DBIc_IMPSET_off(imp_sth);        /*  let DBI know we've done it    */
    return 1;
}


int
dbd_st_STORE(sth, keysv, valuesv)
    SV *sth;
    SV *keysv;
    SV *valuesv;
{
    D_imp_sth(sth);
    STRLEN kl;
    char *key = SvPV(keysv,kl);
    SV *cachesv = NULL;
    int on = SvTRUE(valuesv);

    if (kl==4 && strEQ(key, "long")) {
        imp_sth->long_buflen = SvIV(valuesv);

    } else if (kl==5 && strEQ(key, "trunc")) {
        imp_sth->long_trunc_ok = on;

    } else {
        return FALSE;
    }
    if (cachesv) /* cache value for later DBI 'quick' fetch? */
        hv_store((HV*)SvRV(sth), key, kl, cachesv, 0);
    return TRUE;
}


SV *
dbd_st_FETCH(sth, keysv)
    SV *sth;
    SV *keysv;
{
    D_imp_sth(sth);
    STRLEN kl;
    char *key = SvPV(keysv,kl);
    int i;
    SV *retsv = NULL;
    char cursor_name[256];                                       
    SQLSMALLINT cursor_name_len;
    SQLINTEGER ret;
    char *msg;                                                   

    /* Default to caching results for DBI dispatch quick_FETCH	*/
    int cacheit = TRUE;

    if (!imp_sth->done_desc && !dbd_describe(sth, imp_sth)) {
        /* dbd_describe has already called check_error()	*/ 
	/* We can't return Nullsv here because the xs code will	*/
	/* then just pass the attribute name to DBI for FETCH.	*/
	croak("Describe failed during %s->FETCH(%s)",
	       SvPV(sth,na), key);                                 
    }

    i = DBIc_NUM_FIELDS(imp_sth);

    if (kl==7 && strEQ(key, "lengths")) {
        AV *av = newAV();
        retsv = newRV((SV*)av);
        while(--i >= 0)
            av_store(av, i, newSViv((IV)imp_sth->fbh[i].dsize));
    } else  {
         if (kl==5 && strEQ(key, "types")) {
            AV *av = newAV();
            retsv = newRV((SV*)av);
            while(--i >= 0)
                av_store(av, i, newSViv(imp_sth->fbh[i].dbtype));

        } else  {
            if (kl==13 && strEQ(key, "NUM_OF_PARAMS")) {
                HV *bn = imp_sth->bind_names;
                retsv = newSViv( (bn) ? HvKEYS(bn) : 0 );

            } else if (kl==4 && strEQ(key, "NAME")) {
                AV *av = newAV();
                retsv = newRV((SV*)av);
                while(--i >= 0)
                    av_store(av, i, newSVpv((char *)imp_sth->fbh[i].cbuf,0));

            } else if (kl==8 && strEQ(key, "NULLABLE")) {
                AV *av = newAV();
                retsv = newRV(sv_2mortal((SV*)av));
                while(--i >= 0)
                    av_store(av, i,                                  
		             (imp_sth->fbh[i].nullok == 1) ? &sv_yes : &sv_no);

            } else if (kl==10 && strEQ(key, "CursorName")) {
                ret = SQLGetCursorName(imp_sth->phstmt, (SQLCHAR *)cursor_name,
		                       sizeof(cursor_name), &cursor_name_len);
	        msg = "SQLGetCursorName failed";
		ret = check_error(sth, ret, msg);
                if (ret < 0)
		    return Nullsv;
		else
		    retsv = newSVpv(cursor_name, cursor_name_len);
	    
            } else if (kl==4 && strEQ(key, "TYPE")) {
                AV *av = newAV();
		retsv = newRV(sv_2mortal((SV*)av));
                while(--i >= 0)
		    av_store(av, i, newSViv(imp_sth->fbh[i].dbtype));

            } else if (kl==9 && strEQ(key, "PRECISION")) {
                AV *av = newAV();
		retsv = newRV(sv_2mortal((SV*)av));
                while(--i >= 0)
		    av_store(av, i, newSViv(imp_sth->fbh[i].prec));

            } else if (kl==5 && strEQ(key, "SCALE")) {
                AV *av = newAV();
		retsv = newRV(sv_2mortal((SV*)av));
                while(--i >= 0)
                    av_store(av, i, newSViv(imp_sth->fbh[i].scale)); 

            } else {
                return Nullsv;
            }
        }
    }
    if (cacheit) { /* cache for next time (via DBI quick_FETCH)    */
         SV **svp = hv_fetch((HV*)SvRV(sth), key, kl, 1);
         sv_free(*svp);
         *svp = retsv;
        (void)SvREFCNT_inc(retsv);    /* so sv_2mortal won't free it    */
    }
    return sv_2mortal(retsv);
}