The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* $Id: dbdimp.c,v 1.14 1998/02/24 00:34:39 tom Exp $
 * 
 * Copyright (c) 1997  Thomas K. Wenrich
 * portions Copyright (c) 1994,1995,1996  Tim Bunce
 *
 * You may distribute under the terms of either the GNU General Public
 * License or the Artistic License, as specified in the Perl README file.
 *
 * Autocommit note:
 *   Solid Server versions prior to 2.2.0017 have a broken AUTOCOMMIT
 *   (rollback in disconnect() _MAY_ undo inserts done from within a 
 *   solid procedure), so we handle AutoCommit *additional* to the 
 *   ODBC connection attribute (this is controlled by SOL22_AUTOCOMMIT_BUG
 *   definition within Makefile.PL).
 */

#include "Solid.h"


#define DESCRIBE_IN_PREPARE 1 
	/* Fixes problem with bind_columns immediate after prepare,
	 * but breaks $sth->{blob_size} attribute.
 	 */


typedef struct {
    const char *str;
    UWORD fOption;
    UDWORD true;
    UDWORD false;
} db_params;

typedef struct {
    const char *str;
    unsigned len:8;
    unsigned array:1;
    unsigned filler:23;
} T_st_params;

static const char *
S_SqlTypeToString (
    SWORD sqltype);
static const char *
S_SqlCTypeToString (
    SWORD sqltype);
static int 
S_IsFetchError(
	SV *sth, 
	RETCODE rc, 
        char *sqlstate,
	const void *par);

DBISTATE_DECLARE;

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

void
dbd_db_destroy(dbh)
    SV *dbh;
{
    D_imp_dbh(dbh);

    if (DBIc_ACTIVE(imp_dbh))
	{
	dbd_db_disconnect(dbh);
	}
    /* Nothing in imp_dbh to be freed	*/

    DBIc_IMPSET_off(imp_dbh);
}

/*------------------------------------------------------------
  connecting to a data source.
  Allocates henv and hdbc.
------------------------------------------------------------*/
int
dbd_db_login(dbh, dbname, uid, pwd)
    SV *dbh;
    char *dbname;
    char *uid;
    char *pwd;
{
    D_imp_dbh(dbh);
    D_imp_drh_from_dbh;
    int ret;

    RETCODE rc;
    static int s_first = 1;

    if (dbis->debug >= 2)
	fprintf(DBILOGFP, "%s connect '%s', '%s', '%s'\n",
		s_first ? "FIRST" : "not first", 
		dbname, uid, pwd);


    if (s_first)
	{
	s_first = 0;
	imp_drh->connects = 0;
	imp_drh->henv = SQL_NULL_HENV;
	}

    if (!imp_drh->connects)
	{
	rc = SQLAllocEnv(&imp_drh->henv);
	solid_error(dbh, rc, "db_login/SQLAllocEnv");
	if (rc != SQL_SUCCESS)
	    {
	    return 0;
	    }
	}

    rc = SQLAllocConnect(imp_drh->henv, &imp_dbh->hdbc);
    solid_error(dbh, rc, "db_login/SQLAllocConnect");
    if (rc != SQL_SUCCESS)
	{
	if (imp_drh->connects == 0)
	    {
	    SQLFreeEnv(imp_drh->henv);
	    imp_drh->henv = SQL_NULL_HENV;
	    }
	return 0;
	}

    if (dbis->debug >= 2)
	fprintf(DBILOGFP, "connect '%s', '%s', '%s'",
		dbname, uid, pwd);
    rc = SQLConnect(imp_dbh->hdbc,
		    dbname, strlen(dbname),
		    uid, strlen(uid),
		    pwd, strlen(pwd));

    solid_error(dbh, rc, "db_login/SQLConnect");
    if (rc != SQL_SUCCESS)
	{
	SQLFreeConnect(imp_dbh->hdbc);
	if (imp_drh->connects == 0)
	    {
	    SQLFreeEnv(imp_drh->henv);
	    imp_drh->henv = SQL_NULL_HENV;
	    }
	return 0;
	}

    /* DBI spec requires AutoCommit on
     */
    rc = SQLSetConnectOption(imp_dbh->hdbc, 
    			     SQL_AUTOCOMMIT, 
			     SQL_AUTOCOMMIT_ON);
    solid_error(dbh, rc, "dbd_db_login/SQLSetConnectOption");
    if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
    	{
	DBIc_on(imp_dbh, DBIcf_AutoCommit);
	}

    /* set DBI spec (0.87) defaults 
     */
    DBIc_LongReadLen(imp_dbh) = 80;
    DBIc_set(imp_dbh, DBIcf_LongTruncOk, 1);
    
    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_disconnect(dbh)
    SV *dbh;
{
    RETCODE rc;
    D_imp_dbh(dbh);
    D_imp_drh_from_dbh;

    /* We assume that disconnect will always work	*/
    /* since most errors imply already disconnected.	*/
    DBIc_ACTIVE_off(imp_dbh);

    /* DBI spec: rolling back or committing depends
     * on AutoCommit attribute
     */
#ifdef SOL22_AUTOCOMMIT_BUG
    rc = SQLTransact(imp_drh->henv, 
		     imp_dbh->hdbc,
		     DBIc_is(imp_dbh, DBIcf_AutoCommit) 
		     	? SQL_COMMIT : SQL_ROLLBACK);
    solid_error(dbh, rc, "db_disconnect/SQLTransact");
#else
    rc = SQLTransact(imp_drh->henv, 
		     imp_dbh->hdbc,
		     SQL_ROLLBACK);
    solid_error(dbh, rc, "db_disconnect/SQLTransact");
#endif
    rc = SQLDisconnect(imp_dbh->hdbc);
    solid_error(dbh, rc, "db_disconnect/SQLDisconnect");
    if (rc != SQL_SUCCESS)
	{
	return 0;
	}
    SQLFreeConnect(imp_dbh->hdbc);
    imp_dbh->hdbc = SQL_NULL_HDBC;
    imp_drh->connects--;
    if (imp_drh->connects == 0)
	{
	SQLFreeEnv(imp_drh->henv);
	}
    /* 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;
    }

int
dbd_db_commit(dbh)
    SV *dbh;
{
    D_imp_dbh(dbh);
    D_imp_drh_from_dbh;
    RETCODE rc;

    rc = SQLTransact(imp_drh->henv, 
		     imp_dbh->hdbc,
		     SQL_COMMIT);
    solid_error(dbh, rc, "db_commit/SQLTransact");
    if (rc != SQL_SUCCESS)
	{
	return 0;
	}
    return 1;
}

int
dbd_db_rollback(dbh)
    SV *dbh;
{
    D_imp_dbh(dbh);
    D_imp_drh_from_dbh;
    RETCODE rc;

    rc = SQLTransact(imp_drh->henv, 
		     imp_dbh->hdbc,
		     SQL_ROLLBACK);
    solid_error(dbh, rc, "db_rollback/SQLTransact");
    if (rc != SQL_SUCCESS)
	{
	return 0;
	}
    return 1;
}

/*------------------------------------------------------------
  replacement for ora_error.
  empties entire ODBC error queue.
------------------------------------------------------------*/
const char *
solid_error5(
    SV *h, 
    RETCODE badrc, 
    char *what, 
    T_IsAnError func, 
    const void *par)
{
    D_imp_xxh(h);

    struct imp_drh_st *drh = NULL;
    struct imp_dbh_st *dbh = NULL;
    struct imp_sth_st *sth = NULL;
    HENV henv = SQL_NULL_HENV;
    HDBC hdbc = SQL_NULL_HDBC;
    HSTMT hstmt = SQL_NULL_HSTMT;

    int i = 2;			/* 2..0 hstmt..henv */

    SDWORD NativeError;
    UCHAR ErrorMsg[SQL_MAX_MESSAGE_LENGTH];
    SWORD ErrorMsgMax = sizeof(ErrorMsg)-1;
    SWORD ErrorMsgLen;
    UCHAR sqlstate[10];
    STRLEN len;

    SV *errstr = DBIc_ERRSTR(imp_xxh);

    sv_setpvn(errstr, ErrorMsg, 0);
    sv_setiv(DBIc_ERR(imp_xxh), (IV)badrc);
    /* 
     * sqlstate isn't set for SQL_NO_DATA returns.
     */
    strcpy(sqlstate, "00000");
    sv_setpvn(DBIc_STATE(imp_xxh), sqlstate, 5);
    
    switch(DBIc_TYPE(imp_xxh))
	{
	case DBIt_DR:
	    drh = (struct imp_drh_st *)(imp_xxh);
	    break;
	case DBIt_DB:
	    dbh = (struct imp_dbh_st *)(imp_xxh);
	    drh = (struct imp_drh_st *)(DBIc_PARENT_COM(dbh));
	    break;
	case DBIt_ST:
	    sth = (struct imp_sth_st *)(imp_xxh);
	    dbh = (struct imp_dbh_st *)(DBIc_PARENT_COM(sth));
	    drh = (struct imp_drh_st *)(DBIc_PARENT_COM(dbh));
	    break;
	}

    if (sth != NULL) hstmt = sth->hstmt;
    if (dbh != NULL) hdbc = dbh->hdbc;
    if (drh != NULL) henv = drh->henv;

    while (i >= 0)
	{
	RETCODE rc = 0;
	if (dbis->debug >= 3)
	    fprintf(DBILOGFP, "solid_error: badrc=%d rc=%d i=%d hstmt %d hdbc %d henv %d\n", 
	       badrc, rc, i,
	       hstmt, hdbc, henv);
	switch(i--)
	    {
	    case 2:
		if (hstmt == SQL_NULL_HSTMT)
		    continue;
		break;
	    case 1:
		hstmt = SQL_NULL_HSTMT;
		if (hdbc == SQL_NULL_HDBC)
		    continue;
		break;
	    case 0:
		hdbc = SQL_NULL_HDBC;
		if (henv == SQL_NULL_HENV)
		    continue;
		break;
	    }
	do  
	    {
	    rc = SQLError(henv, hdbc, hstmt,
			  sqlstate,
			  &NativeError,
			  ErrorMsg,
			  ErrorMsgMax,
			  &ErrorMsgLen);
	    if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
		{
		sv_catpvn(errstr, ErrorMsg, ErrorMsgLen);
		sv_catpv(errstr, "\n");

		sv_catpv(errstr, "(SQL-");
		sv_catpv(errstr, sqlstate);
		sv_catpv(errstr, ")\n");
		sv_setpvn(DBIc_STATE(imp_xxh), sqlstate, 5);
		if (dbis->debug >= 3)
	    	    fprintf(DBILOGFP, 
		        "solid_error values: sqlstate %0.5s %u\n",
				sqlstate, NativeError);
	        if (NativeError != 0)	/* set to real error */
	            sv_setiv(DBIc_ERR(imp_xxh), (IV)NativeError);
		}
	    }
	while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO);
	}

    if (badrc != SQL_SUCCESS && what) 
	{
	sv_catpv(errstr, "(DBD: ");
	sv_catpv(errstr, what);
	sprintf(ErrorMsg, " rc=%d", badrc);
	sv_catpv(errstr, ErrorMsg);
	sv_catpv(errstr, ")");
	}
    if (badrc != SQL_SUCCESS)
	{
	if (func == NULL 
	    || (*func)(h, badrc, SvPV(DBIc_STATE(imp_xxh), len), par) != 0)
	    {
	    DBIh_EVENT2(h, ERROR_event, DBIc_ERR(imp_xxh), errstr);
	    if (dbis->debug >= 2)
	        fprintf(DBILOGFP, 
		    "%s badrc %d recorded: %s\n",
		    what, badrc, SvPV(errstr,na));
	    }
        else
	    {
    	    sv_setiv(DBIc_ERR(imp_xxh), (IV)0);
	    }
	}
    return SvPV(DBIc_STATE(imp_xxh), len);
    }

/*-------------------------------------------------------------------------
  dbd_preparse: 
     - scan for placeholders (? and :xx style) and convert them to ?.
     - builds translation table to convert positional parameters of the 
       execute() call to :nn type placeholders.
  We need two data structures to translate this stuff:
     - a hash to convert positional parameters to placeholders
     - an array, representing the actual '?' query parameters.
     %param = (name1=>plh1, name2=>plh2, ..., name_n=>plh_n)   #
     @qm_param = (\$param{'name1'}, \$param{'name2'}, ...) 
-------------------------------------------------------------------------*/
void
dbd_preparse(imp_sth, statement)
    imp_sth_t *imp_sth;
    char *statement;
{
    bool in_literal = FALSE;
    char *src, *start, *dest;
    phs_t phs_tpl;
    SV *phs_sv;
    int idx=0, style=0, laststyle=0;
    int param = 0;
    STRLEN namelen;
    char name[256];
    SV **svpp;
    SV *svref;
    char ch;
    /* allocate room for copy of statement with spare capacity	*/
    /* for editing '?' or ':1' into ':p1' so we can use obndrv.	*/
    imp_sth->statement = (char*)safemalloc(strlen(statement)+1);

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

    src  = 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	*/ 
	ch = *src++;
	if (ch == '?')                /* X/Open standard	*/ 
	    {		
	    idx++;
	    sprintf(name, "%d", idx);
	    *dest++ = ch;
	    style = 3;
	    }
	else if (isDIGIT(*src))         /* ':1'		*/
	    {
	    char *p = name;
	    *dest++ = '?';
	    idx = atoi(src);
	    if (idx <= 0)
		croak("Placeholder :%d must be a positive number", idx);
	    while(isDIGIT(*src))
		*p++ = *src++;
	    *p = 0;
	    style = 1;
	    } 
	else if (isALNUM(*src))         /* ':foo'	*/
	    {
	    char *p = name;
	    *dest++ = '?';

	    while(isALNUM(*src))	/* includes '_'	*/
		*p++ = *src++;
	    *p = 0;
	    style = 2;
	    } 
	else 
	    {			/* perhaps ':=' PL/SQL construct */
	    *dest++ = ch;
	    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->params_hv == NULL)
	    imp_sth->params_hv = newHV();
	namelen = strlen(name);

	svpp = hv_fetch(imp_sth->params_hv, name, namelen, 0);
	if (svpp == NULL)
	    {
	    /* create SV holding the placeholder 
	     */
	    phs_tpl.sv = &sv_undef;
	    phs_sv = newSVpv((char*)&phs_tpl, sizeof(phs_tpl)+namelen+1);
	    strcpy( ((phs_t*)SvPVX(phs_sv))->name, name);

	    /* store placeholder to params_hv
	     */
	    svpp = hv_store(imp_sth->params_hv, name, namelen, phs_sv, 0);
	    }

	svref = newRV(*svpp);

	/* store reference to placeholder to params_av
	 */
	if (imp_sth->params_av == NULL)
	    imp_sth->params_av = newAV();
	av_push(imp_sth->params_av, svref);

	}
    *dest = '\0';
    if (imp_sth->params_hv) 
	{
	DBIc_NUM_PARAMS(imp_sth) = (int)HvKEYS(imp_sth->params_hv);
	if (dbis->debug >= 2)
	    fprintf(DBILOGFP, "    dbd_preparse scanned %d distinct placeholders\n",
		(int)DBIc_NUM_PARAMS(imp_sth));
	}
    }

int
dbd_st_prepare(sth, statement, attribs)
    SV *sth;
    char *statement;
    SV *attribs;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    RETCODE rc;
    SV **svp;
    char cname[128];		/* cursorname */

    imp_sth->done_desc = 0;

    rc = SQLAllocStmt(imp_dbh->hdbc, &imp_sth->hstmt);
    solid_error(sth, rc, "st_prepare/SQLAllocStmt");
    if (rc != SQL_SUCCESS)
	{
	return 0;
	}

    /* scan statement for '?', ':1' and/or ':foo' style placeholders	*/
    dbd_preparse(imp_sth, statement);

    /* parse the (possibly edited) SQL statement */

    rc = SQLPrepare(imp_sth->hstmt, 
		    imp_sth->statement,
		    strlen(imp_sth->statement));
    solid_error(sth, rc, "st_prepare/SQLPrepare");
    if (rc != SQL_SUCCESS)
        {
	SQLFreeStmt(imp_sth->hstmt, SQL_DROP);
	imp_sth->hstmt = SQL_NULL_HSTMT;
	return 0;
	}

#if 0 /* use DBIc macros */
    imp_sth->long_buflen   = 80; /* typical  default	*/
    imp_sth->long_trunc_ok = 0;	/* can use blob_read()		*/
#endif 
    
    if (dbis->debug >= 2)
	fprintf(DBILOGFP, "    dbd_st_prepare'd sql f%d\n\t%s\n",
		imp_sth->hstmt, imp_sth->statement);

    /* init sth pointers */
    imp_sth->fbh = NULL;
    imp_sth->ColNames = NULL;
    imp_sth->RowBuffer = NULL;
    imp_sth->n_result_cols = -1;
    imp_sth->RowCount = -1;
    imp_sth->eod = -1;

    /* @@@ DBI Bug ??? */
    DBIc_set(imp_sth, DBIcf_LongTruncOk,
    	     DBIc_is(imp_dbh, DBIcf_LongTruncOk));
    DBIc_LongReadLen(imp_sth) = DBIc_LongReadLen(imp_dbh);

    sprintf(cname, "dbd_cursor_%X", imp_sth->hstmt);
    rc = SQLSetCursorName(imp_sth->hstmt, cname, strlen(cname));
    if (rc != SQL_SUCCESS)
	warn("dbd_prepare: can't set cursor name, rc = %d", rc);
    if (dbis->debug >= 2)
	fprintf(DBILOGFP, "    CursorName is '%s', rc=%d\n", 
		cname, rc);

    if (attribs)
	{
	if ((svp=hv_fetch((HV*)SvRV(attribs), "blob_size",9, 0)) != NULL)
	    {
	    int len = SvIV(*svp);
	    DBIc_LongReadLen(imp_sth) = len;
	    if (DBIc_WARN(imp_sth))
	    	warn("depreciated feature: blob_size will be replaced by LongReadLen\n");
	    }
	if ((svp=hv_fetch((HV*)SvRV(attribs), "solid_blob_size",15, 0)) != NULL)
	    {
	    int len = SvIV(*svp);
	    DBIc_LongReadLen(imp_sth) = len;
	    if (DBIc_WARN(imp_sth))
	    	warn("depreciated feature: solid_blob_size will be replaced by LongReadLen\n");
	    }
	if ((svp=hv_fetch((HV*)SvRV(attribs), "LongReadLen",11, 0)) != NULL)
	    {
	    int len = SvIV(*svp);
	    DBIc_LongReadLen(imp_sth) = len;
	    }
	
#if YET_NOT_IMPLEMENTED
	if ((svp=hv_fetch((HV*)SvRV(attribs), "concurrency",11, 0)) != NULL)
	    {
	    UDWORD param = SvIV(*svp);
	    rc = SQLSetStmtOption(imp_sth->hstmt, SQL_CONCURRENCY, param);
	    if (rc != SQL_SUCCESS)
		warn("prepare: can't set concurrency, rc = %d", rc);
	    }
#endif
	}

#if DESCRIBE_IN_PREPARE
    if (dbd_describe(sth, imp_sth) <= 0)
	return 0;
#endif

    DBIc_IMPSET_on(imp_sth);
    return 1;
    }

int 
dbtype_is_string(int bind_type)
{
    switch(bind_type)
	{
	case SQL_C_CHAR:
	case SQL_C_BINARY:
	    return 1;
	}
    return 0;
    }    


static const char *
S_SqlTypeToString (SWORD sqltype)
{
    switch(sqltype)
	{
	case SQL_CHAR: return "CHAR";
	case SQL_NUMERIC: return "NUMERIC";
	case SQL_DECIMAL: return "DECIMAL";
	case SQL_INTEGER: return "INTEGER";
	case SQL_SMALLINT: return "SMALLINT";
	case SQL_FLOAT: return "FLOAT";
	case SQL_REAL: return "REAL";
	case SQL_DOUBLE: return "DOUBLE";
	case SQL_VARCHAR: return "VARCHAR";
	case SQL_DATE: return "DATE";
	case SQL_TIME: return "TIME";
	case SQL_TIMESTAMP: return "TIMESTAMP";
	case SQL_LONGVARCHAR: return "LONG VARCHAR";
	case SQL_BINARY: return "BINARY";
	case SQL_VARBINARY: return "VARBINARY";
	case SQL_LONGVARBINARY: return "LONG VARBINARY";
	case SQL_BIGINT: return "BIGINT";
	case SQL_TINYINT: return "TINYINT";
	case SQL_BIT: return "BIT";
	}
    return "unknown";
    }
static const char *
S_SqlCTypeToString (SWORD sqltype)
{
static char s_buf[100];
#define s_c(x) case x: return #x
    switch(sqltype)
	{
	s_c(SQL_C_CHAR);
	s_c(SQL_C_BIT);
	s_c(SQL_C_STINYINT);
	s_c(SQL_C_UTINYINT);
	s_c(SQL_C_SSHORT);
	s_c(SQL_C_USHORT);
	s_c(SQL_C_FLOAT);
	s_c(SQL_C_DOUBLE);
	s_c(SQL_C_BINARY);
	s_c(SQL_C_DATE);
	s_c(SQL_C_TIME);
	s_c(SQL_C_TIMESTAMP);
	}
#undef s_c
    sprintf(s_buf, "(unknown CType %d)", sqltype);
    return s_buf;
    }
/*
 * describes the output variables of a query,
 * allocates buffers for result rows,
 * and binds this buffers to the statement.
 */
int
dbd_describe(h, imp_sth)
    SV *h;
    imp_sth_t *imp_sth;
{
    RETCODE rc;

    UCHAR *cbuf_ptr;		
    UCHAR *rbuf_ptr;		

    int t_cbufl=0;		/* length of all column names */
    int i;
    imp_fbh_t *fbh;
    int t_dbsize = 0;		/* size of native type */
    int t_dsize = 0;		/* display size */

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

    rc = SQLNumResultCols(imp_sth->hstmt, &imp_sth->n_result_cols);
    solid_error(h, rc, "dbd_describe/SQLNumResultCols");
    if (rc != SQL_SUCCESS)
	{
	return 0;
	}

    if (dbis->debug >= 2)
	fprintf(DBILOGFP, "    dbd_describe sql %d: n_result_cols=%d\n",
		imp_sth->hstmt,
		imp_sth->n_result_cols);

    DBIc_NUM_FIELDS(imp_sth) = imp_sth->n_result_cols;

    if (imp_sth->n_result_cols == 0) 
	{
	if (dbis->debug >= 2)
	    fprintf(DBILOGFP, "\tdbd_describe skipped (no result cols) (sql f%d)\n",
		    imp_sth->hstmt);
	return 1;
	}

    /* allocate field buffers				*/
    Newz(42, imp_sth->fbh, imp_sth->n_result_cols, imp_fbh_t);

    /* Pass 1: Get space needed for field names, display buffer and dbuf */
    for (fbh=imp_sth->fbh, i=0; 
	 i<imp_sth->n_result_cols; 
	 i++, fbh++)
	{
	UCHAR ColName[256];

	rc = SQLDescribeCol(imp_sth->hstmt, 
			    i+1, 
			    ColName,
			    sizeof(ColName),	/* max col name length */
			    &fbh->ColNameLen,
			    &fbh->ColSqlType,
			    &fbh->ColDef,
			    &fbh->ColScale,
			    &fbh->ColNullable);
	/* long crash-me columns
 	 * get SUCCESS_WITH_INFO due to ColName truncation 
	 */
        if (rc != SQL_SUCCESS)
	    solid_error5(h, rc, "describe pass 1/SQLDescribeCol",
		     	 S_IsFetchError, &rc);
        if (rc != SQL_SUCCESS)
	    return 0;

	if (fbh->ColNameLen >= sizeof(ColName))
	    ColName[sizeof(ColName)-1] = 0;
	else
	    ColName[fbh->ColNameLen] = 0;


	t_cbufl  += fbh->ColNameLen;

	rc = SQLColAttributes(imp_sth->hstmt,i+1,SQL_COLUMN_DISPLAY_SIZE,
                                NULL, 0, NULL ,&fbh->ColDisplaySize);
        if (rc != SQL_SUCCESS)
	    {
	    solid_error(h, rc, 
			"describe pass 1/SQLColAttributes(DISPLAY_SIZE)");
	    return 0;
	    }
	fbh->ColDisplaySize += 1; /* add terminator */

	rc = SQLColAttributes(imp_sth->hstmt,i+1,SQL_COLUMN_LENGTH,
                                NULL, 0, NULL ,&fbh->ColLength);
        if (rc != SQL_SUCCESS)
	    {
	    solid_error(h, rc, 
			"describe pass 1/SQLColAttributes(COLUMN_LENGTH)");
	    return 0;
	    }

	/* change fetched size for some types
	 */
	fbh->ftype = SQL_C_CHAR;
	switch(fbh->ColSqlType)
	    {
	    case SQL_BINARY:
	    case SQL_VARBINARY:
		fbh->ColDisplaySize = fbh->ColLength;
		fbh->ftype = SQL_C_BINARY;
		break;
	    case SQL_LONGVARBINARY:
		fbh->ftype = SQL_C_BINARY;
		fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth);
		break;
	    case SQL_LONGVARCHAR:
		fbh->ColDisplaySize = DBIc_LongReadLen(imp_sth) + 1;
		break;
	    case SQL_TIMESTAMP:
		fbh->ftype = SQL_C_TIMESTAMP;
		fbh->ColDisplaySize = sizeof(TIMESTAMP_STRUCT);
		break;
	    }
	if (fbh->ftype != SQL_C_CHAR)
	    {
	    t_dbsize += t_dbsize % sizeof(int);     /* alignment */
	    }
	t_dbsize += fbh->ColDisplaySize;

	if (dbis->debug >= 2)
	    fprintf(DBILOGFP, 
		    "\tdbd_describe: col %d: %s, Length=%d"
		    "\t\tDisp=%d, Prec=%d Scale=%d\n", 
		    i+1, S_SqlTypeToString(fbh->ColSqlType),
		    fbh->ColLength, fbh->ColDisplaySize,
		    fbh->ColDef, fbh->ColScale
		    );
	}

    /* allocate a buffer to hold all the column names	*/
    Newz(42, imp_sth->ColNames, t_cbufl + imp_sth->n_result_cols, UCHAR);
    /* allocate Row memory */
    Newz(42, imp_sth->RowBuffer, t_dbsize + imp_sth->n_result_cols, UCHAR);

    /* Second pass:
       - get column names
       - bind column output
     */

    cbuf_ptr = imp_sth->ColNames;
    rbuf_ptr = imp_sth->RowBuffer;

    for(i=0, fbh = imp_sth->fbh; 
	i < imp_sth->n_result_cols 
	&& rc == SQL_SUCCESS; 
	i++, fbh++)
	{
	int dbtype;

	switch(fbh->ftype)
	    {
	    case SQL_C_BINARY:
	    case SQL_C_TIMESTAMP:
		rbuf_ptr += (rbuf_ptr - imp_sth->RowBuffer) % sizeof(int);
		break;
	    }


	rc = SQLDescribeCol(imp_sth->hstmt, 
			    i+1, 
			    cbuf_ptr,
			    fbh->ColNameLen+1,	/* max size from first call */
			    &fbh->ColNameLen,
			    &fbh->ColSqlType,
			    &fbh->ColDef,
			    &fbh->ColScale,
			    &fbh->ColNullable);
        if (rc != SQL_SUCCESS)
	    {
	    solid_error(h, rc, 
			"describe pass 2/SQLDescribeCol");
	    return 0;
	    }
	
	fbh->ColName = cbuf_ptr;
	cbuf_ptr[fbh->ColNameLen] = 0;
	cbuf_ptr += fbh->ColNameLen+1;

	fbh->data = rbuf_ptr;
	rbuf_ptr += fbh->ColDisplaySize;

	/* Bind output column variables */
	rc = SQLBindCol(imp_sth->hstmt,
			i+1,
			fbh->ftype,
			fbh->data,
			fbh->ColDisplaySize,
			&fbh->datalen);
	if (dbis->debug >= 2)
	    fprintf(DBILOGFP, 
		    "\tdescribe/BindCol: col#%d-%s:\n\t\t"
		    "sqltype=%s, ctype=%s, maxlen=%d\n",
		    i+1, fbh->ColName,
		    S_SqlTypeToString(fbh->ColSqlType),
		    S_SqlCTypeToString(fbh->ftype),
		    fbh->ColDisplaySize
		    );
	if (rc != SQL_SUCCESS)
	    {
	    solid_error(h, rc, "describe/SQLBindCol");
	    return 0;
	    }
	} /* end pass 2 */

    if (rc != SQL_SUCCESS)
	{
	warn("can't bind column %d (%s)",
	     i+1, fbh->ColName);
	return 0;
	}
    return 1;
    }

int
dbd_st_execute(sth)	/* <0 is error, >=0 is ok (row count) */
    SV *sth;
{
    D_imp_sth(sth);
    RETCODE rc;
    int debug = dbis->debug;

    /* allow multiple execute() without close() 
     * for one statement
     */
    if (DBIc_ACTIVE(imp_sth))
	{
	rc = SQLFreeStmt(imp_sth->hstmt, SQL_CLOSE);
	solid_error(sth, rc, "st_execute/SQLFreeStmt(SQL_CLOSE)");
	}

    if (!imp_sth->done_desc) 
	{
	/* describe and allocate storage for results (if any needed)	*/
	if (!dbd_describe(sth, imp_sth))
	    return -1; /* dbd_describe already called ora_error()	*/
	}

    /* bind input parameters */

    if (debug >= 2)
	fprintf(DBILOGFP,
	    "    dbd_st_execute (for sql f%d after)...\n",
			imp_sth->hstmt);

    rc = SQLExecute(imp_sth->hstmt);
    solid_error(sth, rc, "st_execute/SQLExecute");
    if (rc != SQL_SUCCESS)
	{
	return -1;
	}
    imp_sth->RowCount = -1;
    rc = SQLRowCount(imp_sth->hstmt, &imp_sth->RowCount);
    solid_error(sth, rc, "st_execute/SQLRowCount");
    if (rc != SQL_SUCCESS)
	{
	return -1;
	}

    if (imp_sth->n_result_cols > 0)	
    	{
        /* @@@ assume only SELECT returns columns */
	DBIc_ACTIVE_on(imp_sth);
	}
    imp_sth->eod = SQL_SUCCESS;
    return 1;
    }

/*
 * Decide whether solid_error should set error for DBI
 * SQL_NO_DATA_FOUND is never an error.
 * SUCCESS_WITH_INFO errors depend on some other conditions.
 *	
 */
static int 
S_IsFetchError(
	SV *sth, 
	RETCODE rc, 
        char *sqlstate,
	const void *par)
    {
    D_imp_sth(sth);

    if (rc == SQL_SUCCESS_WITH_INFO)
        {
	if (strEQ(sqlstate, "01004")) /* data truncated */
	    { 
	    /* without par: error when LongTruncOk is false */
	    if (par == NULL)
	        return DBIc_is(imp_sth, DBIcf_LongTruncOk) == 0;
	    /* with par: is always OK, *par gets SQL_SUCCESS */
	    *(RETCODE *)par = SQL_SUCCESS;
	    return 0;
	    }
	}
    else if (rc == SQL_NO_DATA_FOUND)
        {
	return 0;
	}

    return 1;
    }
/*----------------------------------------
 * running $sth->fetchrow()
 *----------------------------------------
 */
AV *
dbd_st_fetch(sth)
    SV *	sth;
{
    D_imp_sth(sth);
    int debug = dbis->debug;
    int i;
    AV *av;
    RETCODE rc;
    int num_fields;
    char cvbuf[512];
    char *p;
    int LongTruncOk = DBIc_is(imp_sth, DBIcf_LongTruncOk);
    int warn_flag = DBIc_is(imp_sth, DBIcf_WARN);
    const char *sqlstate = NULL;

    /* Check that execute() was executed sucessfully. This also implies	*/
    /* that dbd_describe() executed sucessfuly so the memory buffers	*/
    /* are allocated and bound.						*/
    if ( !DBIc_ACTIVE(imp_sth) ) 
	{
	solid_error(sth, 0, "no statement executing");
	return Nullav;
	}
    
    rc = SQLFetch(imp_sth->hstmt);
    if (dbis->debug >= 2)
	fprintf(DBILOGFP, "SQLFetch() returns %d\n",
		rc);
    switch(rc)
	{
	case SQL_SUCCESS:
	    imp_sth->eod = rc;
	    break;
	case SQL_SUCCESS_WITH_INFO:
            sqlstate = solid_error5(sth, rc, "st_fetch/SQLFetch", 
				    S_IsFetchError, NULL);
	    imp_sth->eod = SQL_SUCCESS;
	    break;
	case SQL_NO_DATA_FOUND:
	    imp_sth->eod = rc;
            sqlstate = solid_error5(sth, rc, "st_fetch/SQLFetch",
				    S_IsFetchError, NULL);
	    return Nullav;
	default:
            solid_error(sth, rc, "st_fetch/SQLFetch");
	    return Nullav;
	}

    if (imp_sth->RowCount == -1)
	imp_sth->RowCount = 0;

    imp_sth->RowCount++;

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

    for(i=0; i < num_fields; ++i) 
	{
	imp_fbh_t *fbh = &imp_sth->fbh[i];
	SV *sv = AvARRAY(av)[i]; /* Note: we (re)use the SV in the AV	*/

        if (dbis->debug >= 4)
	    fprintf(DBILOGFP, "fetch col#%d %s datalen=%d displ=%d\n",
		i, fbh->ColName, fbh->datalen, fbh->ColDisplaySize);
	if (fbh->datalen != SQL_NULL_DATA) 
	    {			/* the normal case		*/
	    TIMESTAMP_STRUCT *ts = (TIMESTAMP_STRUCT *)fbh->data;

	    if (fbh->datalen > fbh->ColDisplaySize)
	    	{ 
		/* truncated LONG ??? */
	        sv_setpvn(sv, (char*)fbh->data, fbh->ColDisplaySize);
		if (!LongTruncOk && warn_flag)
		    {
		    warn("column %d: value truncated", i+1);
		    }
	    	}
	    else switch(fbh->ftype)
	    	{
		case SQL_C_TIMESTAMP:
		    sprintf(cvbuf, "%04d-%02d-%02d %02d:%02d:%02d",
			    ts->year, ts->month, ts->day, 
			    ts->hour, ts->minute, ts->second,
			    ts->fraction);
		    sv_setpv(sv, cvbuf);
		    break;
		default:
		    if (fbh->ColSqlType == SQL_CHAR
		        && DBIc_is(imp_sth, DBIcf_ChopBlanks)
		        && fbh->datalen > 0)
		    	{
			int len = fbh->datalen;
			char *p0  = (char *)(fbh->data);
			char *p   = (char *)(fbh->data) + len;

			while (p-- != p0)
			    {
			    if (*p != ' ')
			    	break;
			    len--;
			    }
		        sv_setpvn(sv, p0, len);
			break;
			}
		    /* no ChopBlank */
		    sv_setpvn(sv, (char*)fbh->data, fbh->datalen);
		    break;
		}
	    }
	else 
	    {
	    SvOK_off(sv);
	    }
	}
    return av;
    }

int
dbd_st_finish(sth)
    SV *sth;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    D_imp_drh_from_dbh;
    RETCODE rc;
    int ret = 1;

    /* Cancel further fetches from this cursor.                 */
    /* We don't close the cursor till DESTROY (dbd_st_destroy). */
    /* The application may re execute(...) it.                  */

    if (DBIc_ACTIVE(imp_sth) && imp_dbh->hdbc != SQL_NULL_HDBC)
	{
	rc = SQLFreeStmt(imp_sth->hstmt, SQL_CLOSE);
	solid_error(sth, rc, "st_finish/SQLFreeStmt(SQL_CLOSE)");

	if (rc != SQL_SUCCESS)
	    ret = 0;
#ifdef SOL22_AUTOCOMMIT_BUG
	if (DBIc_is(imp_dbh, DBIcf_AutoCommit))
	    {
    	    rc = SQLTransact(imp_drh->henv, 
		             imp_dbh->hdbc,
		             SQL_COMMIT);
	    }
#endif
	}
    DBIc_ACTIVE_off(imp_sth);

    return ret;
    }

void
dbd_st_destroy(sth)
    SV *sth;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    D_imp_drh_from_dbh;
    RETCODE rc;

    /* SQLxxx functions dump core when no connection exists. This happens
     * when the db was disconnected before perl ending.
     */
    if (imp_dbh->hdbc != SQL_NULL_HDBC)
	{
	rc = SQLFreeStmt(imp_sth->hstmt, SQL_DROP);
	if (rc != SQL_SUCCESS)
	    {
	    warn("warning: DBD::Solid SQLFreeStmt(SQL_DROP) returns %d\n",
		 rc);
	    }
	}

    /* Free contents of imp_sth	*/

    Safefree(imp_sth->fbh);
    Safefree(imp_sth->ColNames);
    Safefree(imp_sth->RowBuffer);
    Safefree(imp_sth->statement);

    if (imp_sth->params_av)
	{
	av_undef(imp_sth->params_av);
	imp_sth->params_av = NULL;
	}

    if (imp_sth->params_hv)
	{
	HV *hv = imp_sth->params_hv;
	SV *sv;
	char *key;
	I32 retlen;

	/* free SV allocated inside the placeholder structs
	 */
	hv_iterinit(hv);
	while( (sv = hv_iternextsv(hv, &key, &retlen)) != NULL ) 
	    {
	    if (sv != &sv_undef) 
	         {
		 phs_t *phs_tpl = (phs_t*)(void*)SvPVX(sv);
		 sv_free(phs_tpl->sv);
		 }
	    }
        hv_undef(imp_sth->params_hv);
	imp_sth->params_hv = NULL;
	}

    DBIc_IMPSET_off(imp_sth);		/* let DBI know we've done it	*/
    }
/*------------------------------------------------------------
 * bind placeholder.
 *  Is called from Solid.xs execute()
 *  AND from Solid.xs bind_param()
 */
int
dbd_bind_ph(sth, ph_namesv, newvalue, attribs, is_inout, maxlen)
    SV *sth;
    SV *ph_namesv;		/* index of execute() parameter 1..n */
    SV *newvalue;
    SV *attribs;		/* may be set by Solid.xs bind_param call */
    int is_inout;		/* inout for procedure calls only */
    IV maxlen;			/* ??? */
{
    D_imp_sth(sth);
    SV **phs_svp;
    STRLEN name_len;
    char *name;
    char namebuf[30];
    phs_t *phs;

    if (SvNIOK(ph_namesv) ) 
	{	/* passed as a number	*/
	name = namebuf;
	sprintf(name, "%d", (int)SvIV(ph_namesv));
	name_len = strlen(name);
	} 
    else 
	{
	name = SvPV(ph_namesv, name_len);
	}

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

    phs_svp = hv_fetch(imp_sth->params_hv, name, name_len, 0);
    if (phs_svp == NULL)
	croak("Can't bind unknown placeholder '%s'", name);
    phs = (phs_t*)SvPVX(*phs_svp);	/* placeholder struct	*/

    if (phs->sv == &sv_undef)	/* first bind for this placeholder	*/
	{	
	phs->ftype = SQL_C_CHAR;  /* our default type VARCHAR2	*/
	phs->sv = newSV(0);
	}

    if (attribs) 
	{  /* only look for ora_type on first bind of var  */
	SV **svp;
	/* Setup / Clear attributes as defined by attribs.          */
	/* XXX If attribs is EMPTY then reset attribs to default?   */
	if ( (svp=hv_fetch((HV*)SvRV(attribs), "sol_type",8, 0)) != NULL) 
	    {
	    int dbd_type = SvIV(*svp);
	    if (!dbtype_is_string(dbd_type))        /* mean but safe
						     */
		croak("Can't bind %s, sol_type %d not a simple string type",                            phs->name, dbd_type);
	    phs->ftype = dbd_type;
            }
	}
 
    /* 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.	*/

    if (!SvOK(newvalue)) 
	{	/* undef == NULL		*/
	phs->isnull = 1;
	}
    else
	{
	phs->isnull = 0;
	sv_setsv(phs->sv, newvalue);
	}
    return _dbd_rebind_ph(sth, imp_sth, phs, maxlen);
    }

/* walks through param_av and binds each plh found
 */
int 
_dbd_rebind_ph(sth, imp_sth, phs, maxlen) 
    SV *sth;
    imp_sth_t *imp_sth;
    phs_t *phs;
    int maxlen;
{
    int n_qm;			/* number of '?' parameters */
    int avi;

    RETCODE rc;

    /* args of SQLBindParameter() call */
    SWORD fParamType;
    SWORD fCType;
    SWORD fSqlType;
    UDWORD cbColDef;
    SWORD ibScale;
    UCHAR *rgbValue;
    SDWORD cbValueMax;
    SDWORD *pcbValue;
    SWORD fNullable;

    n_qm = av_len(imp_sth->params_av) + 1;

    for (avi = 0; avi < n_qm; avi++)
	{
	STRLEN len;
	SV **ref = av_fetch(imp_sth->params_av, avi, 0);
	SV *refd;
	phs_t *phs_refd;

	refd = SvRV(*ref);
	phs_refd = (phs_t*)SvPVX(refd);	/* placeholder struct	*/

	if (phs_refd != phs)
	    continue;

	rc = SQLDescribeParam(imp_sth->hstmt,
			      avi+1,
			      &fSqlType,
			      &cbColDef,
			      &ibScale,
			      &fNullable);
	solid_error(sth, rc, "_rebind_ph/SQLDescribeParam");
	if (rc != SQL_SUCCESS)
	    return 0;

	fParamType = SQL_PARAM_INPUT;
	fCType = phs->ftype;

	/* When we fill a LONGVARBINARY, the CTYPE must be set 
	 * to SQL_C_BINARY.
	 */

	if (fCType == SQL_C_CHAR)	/* could be changed by bind_plh */
	    {
	    switch(fSqlType)
		{
		case SQL_BINARY:
		case SQL_VARBINARY:
		case SQL_LONGVARBINARY:
		    fCType = SQL_C_BINARY;
		    break;
		case SQL_LONGVARCHAR:
		    break;
	        case SQL_TIMESTAMP:
                case SQL_DATE:
                case SQL_TIME:
		    fSqlType = SQL_VARCHAR;
		    break;
		default:
		    break;
	        }
	    }
	pcbValue = &phs->cbValue;
	if (phs->isnull)
	    {
	    *pcbValue = SQL_NULL_DATA;
	    rgbValue = NULL;
	    }
	else
	    {
	    rgbValue = (UCHAR *)SvPV(phs->sv, len);
	    *pcbValue = (UDWORD) len;
	    }
	cbValueMax = 0;

	if (dbis->debug >=2)
	    fprintf(DBILOGFP,
		    "Bind: %d, CType=%d, SqlType=%s, ColDef=%d\n",
		    avi+1, fCType, 
		    S_SqlTypeToString(fSqlType), 
		    cbColDef);

	rc = SQLBindParameter(imp_sth->hstmt,
			      avi+1,
			      fParamType,
			      fCType,
			      fSqlType,
			      cbColDef,
			      ibScale,
			      rgbValue,
			      cbValueMax,
			      pcbValue);
	solid_error(sth, rc, "_rebind_ph/SQLBindParameter");
	if (rc != SQL_SUCCESS)
	    {
	    return 0;
	    }
	}


    return 1;
    }

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

/*------------------------------------------------------------
 * blob_read:
 * read part of a BLOB from a table.
 */
static int 
S_IsBlobReadError(
	SV *sth, 
	RETCODE rc, 
        char *sqlstate,
	const void *par)
    {
    D_imp_sth(sth);

    if (rc == SQL_SUCCESS_WITH_INFO)
        {
	if (strEQ(sqlstate, "01004")) /* data truncated */
	    {
	    /* Data truncated is NORMAL during blob_read
	     */
	    return 0;
	    }
        }
    else if (rc == SQL_NO_DATA_FOUND)
    	return 0;
	
    return 1;
    }
	
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);
    SDWORD retl;
    SV *bufsv;
    RETCODE rc;

    bufsv = SvRV(destrv);
    sv_setpvn(bufsv,"",0);      /* ensure it's writable string  */
    SvGROW(bufsv, len+destoffset+1);    /* SvGROW doesn't do +1 */

    rc = SQLGetData(imp_sth->hstmt, (UWORD)field+1,
		    SQL_C_BINARY,
		    ((UCHAR *)SvPVX(bufsv)) + destoffset,
		    (SDWORD) len,
		    &retl);
    solid_error5(sth, rc, "dbd_st_blob_read/SQLGetData",
    		S_IsBlobReadError, NULL);

    if (dbis->debug >= 2)
	fprintf(DBILOGFP, "SQLGetData(...,off=%d, len=%d)->rc=%d,len=%d SvCUR=%d\n",
		destoffset, len,
		rc, retl, SvCUR(bufsv));


    if (rc != SQL_SUCCESS)
	{
        if (SvIV(DBIc_ERR(imp_sth)))	
	     {
	     /* IsBlobReadError thinks it's an error */
    	     return 0;
	     }
        if (rc == SQL_NO_DATA_FOUND)
	    return 0;

	retl = len;
        }

    SvCUR_set(bufsv, destoffset+retl);
    if (dbis->debug >= 2)
	fprintf(DBILOGFP, "blob_read: SvCUR=%d\n",
		SvCUR(bufsv));

    *SvEND(bufsv) = '\0'; /* consistent with perl sv_setpvn etc */
 
    return 1;
    }

/*----------------------------------------
 * db level interface
 * set connection attributes.
 *----------------------------------------
 */

static db_params S_db_storeOptions[] =  {
    { "AutoCommit", SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_ON, SQL_AUTOCOMMIT_OFF },
    { "solid_characterset", SQL_TRANSLATE_OPTION },
#if 0 /* not defined by DBI/DBD specification */
    { "TRANSACTION", 
                 SQL_ACCESS_MODE, SQL_MODE_READ_ONLY, SQL_MODE_READ_WRITE },
    { "solid_trace", SQL_OPT_TRACE, SQL_OPT_TRACE_ON, SQL_OPT_TRACE_OFF },
    { "solid_timeout", SQL_LOGIN_TIMEOUT },
    { "ISOLATION", SQL_TXN_ISOLATION },
    { "solid_tracefile", SQL_OPT_TRACEFILE },
#endif
    { NULL },
};

static const db_params *
S_dbOption(const db_params *pars, char *key, STRLEN len)
{
    /* search option to set */
    while (pars->str != NULL)
	{
	if (strncmp(pars->str, key, len) == 0
	    && len == strlen(pars->str))
	    break;
        pars++;
	}
    if (pars->str == NULL)
	return NULL;
    return pars;
    }
 
int
dbd_db_STORE(dbh, keysv, valuesv)
    SV *dbh;
    SV *keysv;
    SV *valuesv;
{
    D_imp_dbh(dbh);
    D_imp_drh_from_dbh;
    RETCODE rc;
    STRLEN kl;
    STRLEN plen;
    char *key = SvPV(keysv,kl);
    SV *cachesv = NULL;
    int on;
    UDWORD vParam;
    const db_params *pars;
    int parind;

    if ((pars = S_dbOption(S_db_storeOptions, key, kl)) == NULL)
	return FALSE;

    parind = pars - S_db_storeOptions;

    switch(pars->fOption)
	{
	case SQL_LOGIN_TIMEOUT:
	case SQL_TXN_ISOLATION:
	    vParam = SvIV(valuesv);
	    break;
	case SQL_OPT_TRACEFILE:
	    vParam = (UDWORD) SvPV(valuesv, plen);
	    break;
	case SQL_TRANSLATE_OPTION:
	    key = SvPV(valuesv, kl);
	    if (kl == 7 && !strncmp(key, "default", kl))
	    	vParam = SQL_SOLID_XLATOPT_DEFAULT;
	    else if (kl == 5 && !strncmp(key, "nocnv", kl))
	    	vParam = SQL_SOLID_XLATOPT_NOCNV;
	    else if (kl == 4 && !strncmp(key, "ansi", kl))
	    	vParam = SQL_SOLID_XLATOPT_ANSI;
	    else if (kl == 5 && !strncmp(key, "pcoem", kl))
	    	vParam = SQL_SOLID_XLATOPT_PCOEM;
	    else if (kl == 9 && !strncmp(key, "7bitscand", kl))
	    	vParam = SQL_SOLID_XLATOPT_7BITSCAND;
	    else
	        {
	    	warn("solid_characterset: invalid value '%.*s'\n", kl, key);
	        return FALSE;
		}
	    break;
	case SQL_AUTOCOMMIT:
	    on = SvTRUE(valuesv);
	    vParam = on ? pars->true : pars->false;
	    break;
	}

    rc = SQLSetConnectOption(imp_dbh->hdbc, pars->fOption, vParam);
    solid_error(dbh, rc, "db_STORE/SQLSetConnectOption");
    if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
	{
	return FALSE;
	}
    if (pars->fOption == SQL_AUTOCOMMIT)
    	{
	if (on) DBIc_set(imp_dbh, DBIcf_AutoCommit, 1);
	else    DBIc_set(imp_dbh, DBIcf_AutoCommit, 0);
	}
    return TRUE;
    }


static db_params S_db_fetchOptions[] =  {
    { "AutoCommit", SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_ON, SQL_AUTOCOMMIT_OFF },
#if 0 /* seems not supported by SOLID */
    { "sol_readonly", 
                 SQL_ACCESS_MODE, SQL_MODE_READ_ONLY, SQL_MODE_READ_WRITE },
    { "sol_trace", SQL_OPT_TRACE, SQL_OPT_TRACE_ON, SQL_OPT_TRACE_OFF },
    { "sol_timeout", SQL_LOGIN_TIMEOUT },
    { "sol_isolation", SQL_TXN_ISOLATION },
    { "sol_tracefile", SQL_OPT_TRACEFILE },
#endif
    { NULL }
};

SV *
dbd_db_FETCH(dbh, keysv)
    SV *dbh;
    SV *keysv;
{
    D_imp_dbh(dbh);
    D_imp_drh_from_dbh;
    RETCODE rc;
    STRLEN kl;
    STRLEN plen;
    char *key = SvPV(keysv,kl);
    int on;
    UDWORD vParam = 0;
    const db_params *pars;
    SV *retsv = NULL;

    if ((pars = S_dbOption(S_db_fetchOptions, key, kl)) == NULL)
	return Nullsv;

    /*
     * readonly, tracefile etc. isn't working yet. only AutoCommit supported.
     */

    if (pars->fOption == 0xffff)
    	{
	}
    else
    	{
        rc = SQLGetConnectOption(imp_dbh->hdbc, pars->fOption, &vParam);
        solid_error(dbh, rc, "db_FETCH/SQLGetConnectOption");
        if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
	    {
	    if (dbis->debug >= 1)
	        fprintf(DBILOGFP,
		    "SQLGetConnectOption returned %d in dbd_db_FETCH\n", rc);
	    return Nullsv;
	    }
	}
    switch(pars->fOption)
	{
	case SQL_LOGIN_TIMEOUT:
	case SQL_TXN_ISOLATION:
	    newSViv(vParam);
	    break;
	case SQL_OPT_TRACEFILE:
	    retsv = newSVpv((char *)vParam, 0);
	    break;
	default:
	    if (vParam == pars->true)
		retsv = newSViv(1);
	    else
		retsv = newSViv(0);
	    break;
	} /* switch */
    return sv_2mortal(retsv);
    }

#define s_A(str) { str, sizeof(str)-1 }
static T_st_params S_st_fetch_params[] = 
{
    s_A("NUM_OF_PARAMS"),	/* 0 */
    s_A("NUM_OF_FIELDS"),	/* 1 */
    s_A("NAME"),		/* 2 */
    s_A("NULLABLE"),		/* 3 */
    s_A("TYPE"),		/* 4 */
    s_A("PRECISION"),		/* 5 */
    s_A("SCALE"),		/* 6 */
    s_A("sol_type"),		/* 7 */
    s_A("sol_length"),		/* 8 */
    s_A("CursorName"),		/* 9 */
    s_A("blob_size"),		/* 10 */
    s_A("__handled_by_dbi__"),	/* 11 */	/* ChopBlanks */
    s_A("solid_blob_size"),	/* 12 */
    s_A("solid_type"),		/* 13 */
    s_A("solid_length"),	/* 14 */
    s_A("LongReadLen"),		/* 15 */
    s_A(""),			/* END */
};

static T_st_params S_st_store_params[] = 
{
    s_A("blob_size"),		/* 0 */
    s_A("solid_blob_size"),	/* 1 */
    s_A(""),			/* END */
};
#undef s_A

/*----------------------------------------
 * dummy routines st_XXXX
 *----------------------------------------
 */
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;
    T_st_params *par;
    int n_fields;
    imp_fbh_t *fbh;
    char cursor_name[256];
    SWORD cursor_name_len;
    RETCODE rc;
    int par_index;

    for (par = S_st_fetch_params; 
	 par->len > 0;
	 par++)
	if (par->len == kl && strEQ(key, par->str))
	    break;

    if (par->len <= 0)
	return Nullsv;

    if (!imp_sth->done_desc && !dbd_describe(sth, imp_sth)) 
	{
	/* dbd_describe has already called ora_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);
 
    switch(par_index = par - S_st_fetch_params)
	{
	AV *av;

	case 0:			/* NUM_OF_PARAMS */
	    return Nullsv;	/* handled by DBI */
        case 1:			/* NUM_OF_FIELDS */
	    retsv = newSViv(i);
	    break;
	case 2: 			/* NAME */
	    av = newAV();
	    retsv = newRV(sv_2mortal((SV*)av));
	    while(--i >= 0)
		av_store(av, i, newSVpv(imp_sth->fbh[i].ColName, 0));
	    break;
	case 3:			/* NULLABLE */
	    av = newAV();
	    retsv = newRV(sv_2mortal((SV*)av));
	    while(--i >= 0) switch(imp_sth->fbh[i].ColNullable)
		{
		case SQL_NULLABLE:
		    av_store(av, i, &sv_yes);
		    break;
		case SQL_NO_NULLS:
		    av_store(av, i, &sv_no);
		    break;
		case SQL_NULLABLE_UNKNOWN:
		    av_store(av, i, &sv_undef);
		    break;
		}
	    break;
	case 4:			/* TYPE */
	    av = newAV();
	    retsv = newRV(sv_2mortal((SV*)av));
	    while(--i >= 0) 
		{
		int type = imp_sth->fbh[i].ColSqlType;
		av_store(av, i, newSViv(type));
		}
	    break;
        case 5:			/* PRECISION */
	    av = newAV();
	    retsv = newRV(sv_2mortal((SV*)av));
	    while(--i >= 0) 
		{
		av_store(av, i, newSViv(imp_sth->fbh[i].ColDef));
		}
	    break;
	case 6:			/* SCALE */
	    av = newAV();
	    retsv = newRV(sv_2mortal((SV*)av));
	    while(--i >= 0) 
		{
		av_store(av, i, newSViv(imp_sth->fbh[i].ColScale));
		}
	    break;
	case 7:			/* dbd_type */
	    if (DBIc_WARN(imp_sth))
	    	warn("Depreciated feature 'sol_type'. "
		     "Please use 'solid_type' instead.");
	    /* fall through */
	case 13:		/* solid_type */
	    av = newAV();
	    retsv = newRV(sv_2mortal((SV*)av));
	    while(--i >= 0) 
		{
		av_store(av, i, newSViv(imp_sth->fbh[i].ColSqlType));
		}
	    break;
	case 8:			/* dbd_length */
	    if (DBIc_WARN(imp_sth))
	    	warn("Depreciated feature 'sol_length'. "
		     "Please use 'solid_length' instead.");
	    /* fall through */
	case 14:		/* solid_length */
	    av = newAV();
	    retsv = newRV(sv_2mortal((SV*)av));
	    while(--i >= 0) 
		{
		av_store(av, i, newSViv(imp_sth->fbh[i].ColLength));
		}
	    break;
	case 9:			/* CursorName */
	    rc = SQLGetCursorName(imp_sth->hstmt,
				  cursor_name,
				  sizeof(cursor_name),
				  &cursor_name_len);
	    solid_error(sth, rc, "st_FETCH/SQLGetCursorName");
	    if (rc != SQL_SUCCESS)
		{
		if (dbis->debug >= 1)
		    {
		    fprintf(DBILOGFP,
			    "SQLGetCursorName returned %d in dbd_st_FETCH\n", 
			    rc);
		    }
		return Nullsv;
		}
	    retsv = newSVpv(cursor_name, cursor_name_len);
	    break;
	case 10:		/* blob_size */
	    if (DBIc_WARN(imp_sth))
	    	warn("Depreciated feature 'blob_size'. "
		     "Please use 'solid_blob_size' instead.");
	    /* fall through */
	case 12:		/* solid_blob_size */
	case 15: 		/* LongReadLen */
	    retsv = newSViv(DBIc_LongReadLen(imp_sth));
	    break;
	default:
	    return Nullsv;
	}

    return sv_2mortal(retsv);
    }

int
dbd_st_STORE(sth, keysv, valuesv)
    SV *sth;
    SV *keysv;
    SV *valuesv;
{
    D_imp_sth(sth);
    D_imp_dbh_from_sth;
    STRLEN kl;
    STRLEN vl;
    char *key = SvPV(keysv,kl);
    char *value = SvPV(valuesv, vl);
    T_st_params *par;
    RETCODE rc;
 
    for (par = S_st_store_params; 
	 par->len > 0;
	 par++)
	if (par->len == kl && strEQ(key, par->str))
	    break;

    if (par->len <= 0)
	return FALSE;

    switch(par - S_st_store_params)
	{
	case 0:/* blob_size */
	case 1:/* solid_blob_size */
#if DESCRIBE_IN_PREPARE
	    warn("$sth->{blob_size} isn't longer supported.\n"
	         "You may either use the 'LongReadLen' "
		 "attribute to prepare()\nor the blob_read() "
		 "function.\n");
	    return FALSE;
#endif
	    DBIc_LongReadLen(imp_sth) = SvIV(valuesv);
	    return TRUE;
	}
    return FALSE;
    }