SQLite4
Artifact Content
Not logged in

Artifact a2df73026844917427c82cc817209d1fee669599:


/*
** 2001 September 15
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** Code for testing all sorts of SQLite interfaces.  This code
** is not included in the SQLite library.  It is used for automated
** testing of the SQLite library.
*/
#include "sqliteInt.h"
#include "vdbeInt.h"
#include "tcl.h"
#include "testInt.h"
#include <stdlib.h>
#include <string.h>

/*
** This is a copy of the first part of the SqliteDb structure in 
** tclsqlite.c.  We need it here so that the get_sqlite_pointer routine
** can extract the sqlite4* pointer from an existing Tcl SQLite
** connection.
*/
struct SqliteDb {
  sqlite4 *db;
};

/*
** Convert text generated by the "%p" conversion format back into
** a pointer.
*/
static int testHexToInt(int h){
  if( h>='0' && h<='9' ){
    return h - '0';
  }else if( h>='a' && h<='f' ){
    return h - 'a' + 10;
  }else{
    assert( h>='A' && h<='F' );
    return h - 'A' + 10;
  }
}
void *sqlite4TestTextToPtr(const char *z){
  void *p;
  u64 v;
  u32 v2;
  if( z[0]=='0' && z[1]=='x' ){
    z += 2;
  }
  v = 0;
  while( *z ){
    v = (v<<4) + testHexToInt(*z);
    z++;
  }
  if( sizeof(p)==sizeof(v) ){
    memcpy(&p, &v, sizeof(p));
  }else{
    assert( sizeof(p)==sizeof(v2) );
    v2 = (u32)v;
    memcpy(&p, &v2, sizeof(p));
  }
  return p;
}


/*
** A TCL command that returns the address of the sqlite* pointer
** for an sqlite connection instance.  Bad things happen if the
** input is not an sqlite connection.
*/
static int get_sqlite_pointer(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  struct SqliteDb *p;
  Tcl_CmdInfo cmdInfo;
  char zBuf[100];
  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "SQLITE-CONNECTION");
    return TCL_ERROR;
  }
  if( !Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
    Tcl_AppendResult(interp, "command not found: ",
           Tcl_GetString(objv[1]), (char*)0);
    return TCL_ERROR;
  }
  p = (struct SqliteDb*)cmdInfo.objClientData;
  sprintf(zBuf, "%p", p->db);
  if( strncmp(zBuf,"0x",2) ){
    sprintf(zBuf, "0x%p", p->db);
  }
  Tcl_AppendResult(interp, zBuf, 0);
  return TCL_OK;
}

/*
** Decode a pointer to an sqlite4 object.
*/
int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite4 **ppDb){
  struct SqliteDb *p;
  Tcl_CmdInfo cmdInfo;
  if( Tcl_GetCommandInfo(interp, zA, &cmdInfo) ){
    p = (struct SqliteDb*)cmdInfo.objClientData;
    *ppDb = p->db;
  }else{
    *ppDb = (sqlite4*)sqlite4TestTextToPtr(zA);
  }
  return TCL_OK;
}

int sqlite4TestDbHandle(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite4 **ppDb){
  return getDbPointer(interp, Tcl_GetString(pObj), ppDb);
}

const char *sqlite4TestErrorName(int rc){
  const char *zName = 0;
  switch( rc ){
    case SQLITE4_OK:                 zName = "SQLITE4_OK";                break;
    case SQLITE4_ERROR:              zName = "SQLITE4_ERROR";             break;
    case SQLITE4_INTERNAL:           zName = "SQLITE4_INTERNAL";          break;
    case SQLITE4_PERM:               zName = "SQLITE4_PERM";              break;
    case SQLITE4_ABORT:              zName = "SQLITE4_ABORT";             break;
    case SQLITE4_BUSY:               zName = "SQLITE4_BUSY";              break;
    case SQLITE4_LOCKED:             zName = "SQLITE4_LOCKED";            break;
    case SQLITE4_LOCKED_SHAREDCACHE: zName = "SQLITE4_LOCKED_SHAREDCACHE";break;
    case SQLITE4_NOMEM:              zName = "SQLITE4_NOMEM";             break;
    case SQLITE4_READONLY:           zName = "SQLITE4_READONLY";          break;
    case SQLITE4_INTERRUPT:          zName = "SQLITE4_INTERRUPT";         break;
    case SQLITE4_IOERR:              zName = "SQLITE4_IOERR";             break;
    case SQLITE4_CORRUPT:            zName = "SQLITE4_CORRUPT";           break;
    case SQLITE4_NOTFOUND:           zName = "SQLITE4_NOTFOUND";          break;
    case SQLITE4_FULL:               zName = "SQLITE4_FULL";              break;
    case SQLITE4_CANTOPEN:           zName = "SQLITE4_CANTOPEN";          break;
    case SQLITE4_PROTOCOL:           zName = "SQLITE4_PROTOCOL";          break;
    case SQLITE4_EMPTY:              zName = "SQLITE4_EMPTY";             break;
    case SQLITE4_SCHEMA:             zName = "SQLITE4_SCHEMA";            break;
    case SQLITE4_TOOBIG:             zName = "SQLITE4_TOOBIG";            break;
    case SQLITE4_CONSTRAINT:         zName = "SQLITE4_CONSTRAINT";        break;
    case SQLITE4_MISMATCH:           zName = "SQLITE4_MISMATCH";          break;
    case SQLITE4_MISUSE:             zName = "SQLITE4_MISUSE";            break;
    case SQLITE4_NOLFS:              zName = "SQLITE4_NOLFS";             break;
    case SQLITE4_AUTH:               zName = "SQLITE4_AUTH";              break;
    case SQLITE4_FORMAT:             zName = "SQLITE4_FORMAT";            break;
    case SQLITE4_RANGE:              zName = "SQLITE4_RANGE";             break;
    case SQLITE4_NOTADB:             zName = "SQLITE4_NOTADB";            break;
    case SQLITE4_ROW:                zName = "SQLITE4_ROW";               break;
    case SQLITE4_DONE:               zName = "SQLITE4_DONE";              break;
    case SQLITE4_INEXACT:            zName = "SQLITE4_INEXACT";           break;
    case SQLITE4_IOERR_READ:         zName = "SQLITE4_IOERR_READ";        break;
    case SQLITE4_IOERR_SHORT_READ:   zName = "SQLITE4_IOERR_SHORT_READ";  break;
    case SQLITE4_IOERR_WRITE:        zName = "SQLITE4_IOERR_WRITE";       break;
    case SQLITE4_IOERR_FSYNC:        zName = "SQLITE4_IOERR_FSYNC";       break;
    case SQLITE4_IOERR_DIR_FSYNC:    zName = "SQLITE4_IOERR_DIR_FSYNC";   break;
    case SQLITE4_IOERR_TRUNCATE:     zName = "SQLITE4_IOERR_TRUNCATE";    break;
    case SQLITE4_IOERR_FSTAT:        zName = "SQLITE4_IOERR_FSTAT";       break;
    case SQLITE4_IOERR_UNLOCK:       zName = "SQLITE4_IOERR_UNLOCK";      break;
    case SQLITE4_IOERR_RDLOCK:       zName = "SQLITE4_IOERR_RDLOCK";      break;
    case SQLITE4_IOERR_DELETE:       zName = "SQLITE4_IOERR_DELETE";      break;
    case SQLITE4_IOERR_BLOCKED:      zName = "SQLITE4_IOERR_BLOCKED";     break;
    case SQLITE4_IOERR_NOMEM:        zName = "SQLITE4_IOERR_NOMEM";       break;
    case SQLITE4_IOERR_ACCESS:       zName = "SQLITE4_IOERR_ACCESS";      break;
    case SQLITE4_IOERR_CHECKRESERVEDLOCK:
                               zName = "SQLITE4_IOERR_CHECKRESERVEDLOCK"; break;
    case SQLITE4_IOERR_LOCK:         zName = "SQLITE4_IOERR_LOCK";        break;
    case SQLITE4_CORRUPT_VTAB:       zName = "SQLITE4_CORRUPT_VTAB";      break;
    case SQLITE4_READONLY_RECOVERY:  zName = "SQLITE4_READONLY_RECOVERY"; break;
    case SQLITE4_READONLY_CANTLOCK:  zName = "SQLITE4_READONLY_CANTLOCK"; break;
    default:                         zName = "SQLITE4_Unknown";           break;
  }
  return zName;
}
#define t1ErrorName sqlite4TestErrorName

int sqlite4TestSetResult(Tcl_Interp *interp, int rc){
  const char *z = sqlite4TestErrorName(rc);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(z, -1));
  return TCL_OK;
}

/*
** Convert an sqlite4_stmt* into an sqlite4*.  This depends on the
** fact that the sqlite4* is the first field in the Vdbe structure.
*/
#define StmtToDb(X)   sqlite4_db_handle(X)

/*
** Check a return value to make sure it agrees with the results
** from sqlite4_errcode.
*/
int sqlite4TestErrCode(Tcl_Interp *interp, sqlite4 *db, int rc){
  if( rc!=SQLITE4_MISUSE && rc!=SQLITE4_OK && sqlite4_errcode(db)!=rc ){
    char zBuf[200];
    int r2 = sqlite4_errcode(db);
    sprintf(zBuf, "error code %s (%d) does not match sqlite4_errcode %s (%d)",
       t1ErrorName(rc), rc, t1ErrorName(r2), r2);
    Tcl_ResetResult(interp);
    Tcl_AppendResult(interp, zBuf, 0);
    return 1;
  }
  return 0;
}

/*
** Decode a pointer to an sqlite4_stmt object.
*/
static int getStmtPointer(
  Tcl_Interp *interp, 
  const char *zArg,  
  sqlite4_stmt **ppStmt
){
  *ppStmt = (sqlite4_stmt*)sqlite4TestTextToPtr(zArg);
  return TCL_OK;
}

/*
** Generate a text representation of a pointer that can be understood
** by the getDbPointer and getVmPointer routines above.
**
** The problem is, on some machines (Solaris) if you do a printf with
** "%p" you cannot turn around and do a scanf with the same "%p" and
** get your pointer back.  You have to prepend a "0x" before it will
** work.  Or at least that is what is reported to me (drh).  But this
** behavior varies from machine to machine.  The solution used her is
** to test the string right after it is generated to see if it can be
** understood by scanf, and if not, try prepending an "0x" to see if
** that helps.  If nothing works, a fatal error is generated.
*/
int sqlite4TestMakePointerStr(Tcl_Interp *interp, char *zPtr, void *p){
  sqlite4_snprintf(zPtr, 100, "%p", p);
  return TCL_OK;
}

/*
** The callback routine for sqlite4_exec_printf().
*/
static int exec_printf_cb(
  void *pArg, 
  int nVal, 
  sqlite4_value **apVal, 
  const char **azCol
){
  Tcl_DString *str = (Tcl_DString*)pArg;
  int i;

  if( Tcl_DStringLength(str)==0 ){
    for(i=0; i<nVal; i++){
      Tcl_DStringAppendElement(str, azCol[i] ? azCol[i] : "NULL");
    }
  }
  for(i=0; i<nVal; i++){
    const char *z = sqlite4_value_text(apVal[i], 0);
    Tcl_DStringAppendElement(str, z ? z : "NULL");
  }
  return 0;
}

/*
** The I/O tracing callback.
*/
#if !defined(SQLITE4_OMIT_TRACE) && defined(SQLITE4_ENABLE_IOTRACE)
static FILE *iotrace_file = 0;
static void io_trace_callback(const char *zFormat, ...){
  va_list ap;
  va_start(ap, zFormat);
  vfprintf(iotrace_file, zFormat, ap);
  va_end(ap);
  fflush(iotrace_file);
}
#endif

/*
** Usage:  io_trace FILENAME
**
** Turn I/O tracing on or off.  If FILENAME is not an empty string,
** I/O tracing begins going into FILENAME. If FILENAME is an empty
** string, I/O tracing is turned off.
*/
static int test_io_trace(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
#if !defined(SQLITE4_OMIT_TRACE) && defined(SQLITE4_ENABLE_IOTRACE)
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
          " FILENAME\"", 0);
    return TCL_ERROR;
  }
  if( iotrace_file ){
    if( iotrace_file!=stdout && iotrace_file!=stderr ){
      fclose(iotrace_file);
    }
    iotrace_file = 0;
    sqlite4IoTrace = 0;
  }
  if( argv[1][0] ){
    if( strcmp(argv[1],"stdout")==0 ){
      iotrace_file = stdout;
    }else if( strcmp(argv[1],"stderr")==0 ){
      iotrace_file = stderr;
    }else{
      iotrace_file = fopen(argv[1], "w");
    }
    sqlite4IoTrace = io_trace_callback;
  }
#endif
  return TCL_OK;
}


/*
** Usage:  sqlite4_exec_printf  DB  FORMAT  STRING
**
** Invoke the sqlite4_exec_printf() interface using the open database
** DB.  The SQL is the string FORMAT.  The format string should contain
** one %s or %q.  STRING is the value inserted into %s or %q.
*/
static int test_exec_printf(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  sqlite4 *db;
  Tcl_DString str;
  int rc;
  char *zSql;
  char zBuf[30];
  if( argc!=4 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
       " DB FORMAT STRING", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  Tcl_DStringInit(&str);
  zSql = sqlite4_mprintf(0, argv[2], argv[3]);
  rc = sqlite4_exec(db, zSql, exec_printf_cb, &str);
  sqlite4_free(0, zSql);
  sprintf(zBuf, "%d", rc);
  Tcl_AppendElement(interp, zBuf);
  Tcl_AppendElement(interp, !rc ? Tcl_DStringValue(&str) : sqlite4_errmsg(db));
  Tcl_DStringFree(&str);
  if( sqlite4TestErrCode(interp, db, rc) ) return TCL_ERROR;
  return TCL_OK;
}

/*
** Usage:  sqlite4_exec_hex  DB  HEX
**
** Invoke the sqlite4_exec() on a string that is obtained by translating
** HEX into ASCII.  Most characters are translated as is.  %HH becomes
** a hex character.
*/
static int test_exec_hex(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  sqlite4 *db;
  Tcl_DString str;
  int rc, i, j;
  char *zHex;
  char zSql[500];
  char zBuf[30];
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
       " DB HEX", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  zHex = argv[2];
  for(i=j=0; i<sizeof(zSql) && zHex[j]; i++, j++){
    if( zHex[j]=='%' && zHex[j+2] && zHex[j+2] ){
      zSql[i] = (testHexToInt(zHex[j+1])<<4) + testHexToInt(zHex[j+2]);
      j += 2;
    }else{
      zSql[i] = zHex[j];
    }
  }
  zSql[i] = 0;
  Tcl_DStringInit(&str);
  rc = sqlite4_exec(db, zSql, exec_printf_cb, &str);
  sprintf(zBuf, "%d", rc);
  Tcl_AppendElement(interp, zBuf);
  Tcl_AppendElement(interp, !rc ? Tcl_DStringValue(&str) : sqlite4_errmsg(db));
  Tcl_DStringFree(&str);
  if( sqlite4TestErrCode(interp, db, rc) ) return TCL_ERROR;
  return TCL_OK;
}

/*
** Usage:  db_enter DB
**         db_leave DB
**
** Enter or leave the mutex on a database connection.
*/
static int db_enter(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  sqlite4 *db;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
       " DB", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  sqlite4_mutex_enter(db->mutex);
  return TCL_OK;
}
static int db_leave(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  sqlite4 *db;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
       " DB", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  sqlite4_mutex_leave(db->mutex);
  return TCL_OK;
}

/*
** Usage:  sqlite4_exec  DB  SQL
**
** Invoke the sqlite4_exec interface using the open database DB
*/
static int test_exec(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  sqlite4 *db;
  Tcl_DString str;
  int rc;
  char *zSql;
  int i, j;
  char zBuf[30];
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
       " DB SQL", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  Tcl_DStringInit(&str);
  zSql = sqlite4_mprintf(0, "%s", argv[2]);
  for(i=j=0; zSql[i];){
    if( zSql[i]=='%' ){
      zSql[j++] = (testHexToInt(zSql[i+1])<<4) + testHexToInt(zSql[i+2]);
      i += 3;
    }else{
      zSql[j++] = zSql[i++];
    }
  }
  zSql[j] = 0;
  rc = sqlite4_exec(db, zSql, exec_printf_cb, &str);
  sqlite4_free(0, zSql);
  sprintf(zBuf, "%d", rc);
  Tcl_AppendElement(interp, zBuf);
  Tcl_AppendElement(interp, !rc ? Tcl_DStringValue(&str) : sqlite4_errmsg(db));
  Tcl_DStringFree(&str);
  if( sqlite4TestErrCode(interp, db, rc) ) return TCL_ERROR;
  return TCL_OK;
}

/*
** Usage:  sqlite4_exec_nr  DB  SQL
**
** Invoke the sqlite4_exec interface using the open database DB.  Discard
** all results
*/
static int test_exec_nr(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  sqlite4 *db;
  int rc;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
       " DB SQL", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  rc = sqlite4_exec(db, argv[2], 0, 0);
  if( sqlite4TestErrCode(interp, db, rc) ) return TCL_ERROR;
  return TCL_OK;
}

/*
** Usage:  sqlite4_mprintf_z_test  SEPARATOR  ARG0  ARG1 ...
**
** Test the %z format of sqlite_mprintf().  Use multiple mprintf() calls to 
** concatenate arg0 through argn using separator as the separator.
** Return the result.
*/
static int test_mprintf_z(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  char *zResult = 0;
  int i;

  for(i=2; i<argc && (i==2 || zResult); i++){
    zResult = sqlite4_mprintf(0, "%z%s%s", zResult, argv[1], argv[i]);
  }
  Tcl_AppendResult(interp, zResult, 0);
  sqlite4_free(0, zResult);
  return TCL_OK;
}

/*
** Usage:  sqlite4_mprintf_n_test  STRING
**
** Test the %n format of sqlite_mprintf().  Return the length of the
** input string.
*/
static int test_mprintf_n(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  char *zStr;
  int n = 0;
  zStr = sqlite4_mprintf(0, "%s%n", argv[1], &n);
  sqlite4_free(0, zStr);
  Tcl_SetObjResult(interp, Tcl_NewIntObj(n));
  return TCL_OK;
}

/*
** Usage:  sqlite4_snprintf_int  SIZE FORMAT  INT
**
** Test the of sqlite4_snprintf() routine.  SIZE is the size of the
** output buffer in bytes.  The maximum size is 100.  FORMAT is the
** format string.  INT is a single integer argument.  The FORMAT
** string must require no more than this one integer argument.  If
** You pass in a format string that requires more than one argument,
** bad things will happen.
*/
static int test_snprintf_int(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  char zStr[100];
  int n = atoi(argv[1]);
  const char *zFormat = argv[2];
  int a1 = atoi(argv[3]);
  if( n>sizeof(zStr) ) n = sizeof(zStr);
  sqlite4_snprintf(zStr, sizeof(zStr), "abcdefghijklmnopqrstuvwxyz");
  sqlite4_snprintf(zStr, n, zFormat, a1);
  Tcl_AppendResult(interp, zStr, 0);
  return TCL_OK;
}


/*
** Usage:  sqlite4_key DB KEY
**
** Set the codec key.
*/
static int test_key(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
#ifdef SQLITE4_HAS_CODEC
  sqlite4 *db;
  const char *zKey;
  int nKey;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FILENAME\"", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  zKey = argv[2];
  nKey = strlen(zKey);
  sqlite4_key(db, zKey, nKey);
#endif
  return TCL_OK;
}

/*
** Usage:  sqlite4_rekey DB KEY
**
** Change the codec key.
*/
static int test_rekey(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
#ifdef SQLITE4_HAS_CODEC
  sqlite4 *db;
  const char *zKey;
  int nKey;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FILENAME\"", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  zKey = argv[2];
  nKey = strlen(zKey);
  sqlite4_rekey(db, zKey, nKey);
#endif
  return TCL_OK;
}

/*
** Usage:  sqlite4_close DB
**
** Closes the database opened by sqlite4_open.
*/
static int sqlite_test_close(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  sqlite4 *db;
  int rc;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FILENAME\"", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  rc = sqlite4_close(db, 0);
  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
  return TCL_OK;
}

/*
** Implementation of the x_coalesce() function.
** Return the first argument non-NULL argument.
*/
static void t1_ifnullFunc(
  sqlite4_context *context,
  int argc,
  sqlite4_value **argv
){
  int i;
  for(i=0; i<argc; i++){
    if( SQLITE4_NULL!=sqlite4_value_type(argv[i]) ){
      int n;
      const char *z = sqlite4_value_text(argv[i], &n);
      sqlite4_result_text(context, z, n, SQLITE4_TRANSIENT, 0);
      break;
    }
  }
}

/*
** These are test functions.    hex8() interprets its argument as
** UTF8 and returns a hex encoding.  hex16le() interprets its argument
** as UTF16le and returns a hex encoding.
*/
static void hex8Func(sqlite4_context *p, int argc, sqlite4_value **argv){
  const char *z;
  int i;
  char zBuf[200];
  z = sqlite4_value_text(argv[0], 0);
  for(i=0; i<sizeof(zBuf)/2 - 2 && z[i]; i++){
    sprintf(&zBuf[i*2], "%02x", z[i]&0xff);
  }
  zBuf[i*2] = 0;
  sqlite4_result_text(p, zBuf, -1, SQLITE4_TRANSIENT, 0);
}
#ifndef SQLITE4_OMIT_UTF16
static void hex16Func(sqlite4_context *p, int argc, sqlite4_value **argv){
  const unsigned short int *z;
  int i;
  char zBuf[400];
  z = sqlite4_value_text16(argv[0], 0);
  for(i=0; i<sizeof(zBuf)/4 - 4 && z[i]; i++){
    sprintf(&zBuf[i*4], "%04x", z[i]&0xff);
  }
  zBuf[i*4] = 0;
  sqlite4_result_text(p, (char*)zBuf, -1, SQLITE4_TRANSIENT, 0);
}
#endif

/*
** A structure into which to accumulate text.
*/
struct dstr {
  int nAlloc;  /* Space allocated */
  int nUsed;   /* Space used */
  char *z;     /* The space */
};

/*
** Append text to a dstr
*/
static void dstrAppend(struct dstr *p, const char *z, int divider){
  int n = strlen(z);
  if( p->nUsed + n + 2 > p->nAlloc ){
    char *zNew;
    p->nAlloc = p->nAlloc*2 + n + 200;
    zNew = sqlite4_realloc(0, p->z, p->nAlloc);
    if( zNew==0 ){
      sqlite4_free(0, p->z);
      memset(p, 0, sizeof(*p));
      return;
    }
    p->z = zNew;
  }
  if( divider && p->nUsed>0 ){
    p->z[p->nUsed++] = divider;
  }
  memcpy(&p->z[p->nUsed], z, n+1);
  p->nUsed += n;
}

/*
** Invoked for each callback from sqlite4ExecFunc
*/
static int execFuncCallback(
  void *pData, 
  int nVal, 
  sqlite4_value **apVal,
  const char **azCol
){
  struct dstr *p = (struct dstr*)pData;
  int i;
  for(i=0; i<nVal; i++){
    if( sqlite4_value_type(apVal[i])==SQLITE4_NULL ){
      dstrAppend(p, "NULL", ' ');
    }else{
      dstrAppend(p, sqlite4_value_text(apVal[i], 0), ' ');
    }
  }
  return 0;
}

/*
** Implementation of the x_sqlite_exec() function.  This function takes
** a single argument and attempts to execute that argument as SQL code.
** This is illegal and should set the SQLITE4_MISUSE flag on the database.
**
** 2004-Jan-07:  We have changed this to make it legal to call sqlite4_exec()
** from within a function call.  
** 
** This routine simulates the effect of having two threads attempt to
** use the same database at the same time.
*/
static void sqlite4ExecFunc(
  sqlite4_context *context, 
  int argc,  
  sqlite4_value **argv
){
  struct dstr x;
  sqlite4 *db = (sqlite4 *)sqlite4_context_appdata(context);
  const char *zSql = sqlite4_value_text(argv[0], 0);
  memset(&x, 0, sizeof(x));
  (void)sqlite4_exec(db, zSql, execFuncCallback, &x);
  sqlite4_result_text(context, x.z, x.nUsed, SQLITE4_TRANSIENT, 0);
  sqlite4_free(0, x.z);
}

/*
** Implementation of tkt2213func(), a scalar function that takes exactly
** one argument. It has two interesting features:
**
** * It calls sqlite4_value_text() 3 times on the argument sqlite4_value*.
**   If the three pointers returned are not the same an SQL error is raised.
**
** * Otherwise it returns a copy of the text representation of its 
**   argument in such a way as the VDBE representation is a Mem* cell 
**   with the MEM_Term flag clear. 
**
** Ticket #2213 can therefore be tested by evaluating the following
** SQL expression:
**
**   tkt2213func(tkt2213func('a string'));
*/
static void tkt2213Function(
  sqlite4_context *context, 
  int argc,  
  sqlite4_value **argv
){
  int nText;
  char const *zText1;
  char const *zText2;
  char const *zText3;

  zText1 = sqlite4_value_text(argv[0], &nText);
  zText2 = sqlite4_value_text(argv[0], 0);
  zText3 = sqlite4_value_text(argv[0], 0);

  if( zText1!=zText2 || zText2!=zText3 ){
    sqlite4_result_error(context, "tkt2213 is not fixed", -1);
  }else{
    char *zCopy = (char *)sqlite4_malloc(sqlite4_context_env(context),nText);
    memcpy(zCopy, zText1, nText);
    sqlite4_result_text(context, zCopy, nText, SQLITE4_DYNAMIC, 0);
  }
}

/*
** Usage:  sqlite_test_create_function DB
**
** Call the sqlite4_create_function API on the given database in order
** to create a function named "x_coalesce".  This function does the same thing
** as the "coalesce" function.  This function also registers an SQL function
** named "x_sqlite_exec" that invokes sqlite4_exec().  Invoking sqlite4_exec()
** in this way is illegal recursion and should raise an SQLITE4_MISUSE error.
** The effect is similar to trying to use the same database connection from
** two threads at the same time.
**
** The original motivation for this routine was to be able to call the
** sqlite4_create_function function while a query is in progress in order
** to test the SQLITE4_MISUSE detection logic.
*/
static int test_create_function(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  int rc;
  sqlite4 *db;

  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " DB\"", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  rc = sqlite4_create_function(db, "x_coalesce", -1, 0, t1_ifnullFunc, 0, 0, 0);
  if( rc==SQLITE4_OK ){
    rc = sqlite4_create_function(db, "hex8", 1, 0, hex8Func, 0, 0, 0);
  }
#ifndef SQLITE4_OMIT_UTF16
  if( rc==SQLITE4_OK ){
    rc = sqlite4_create_function(db, "hex16", 1, 0, hex16Func, 0, 0, 0);
  }
#endif
  if( rc==SQLITE4_OK ){
    rc = sqlite4_create_function(db, "tkt2213func", 1, 0, 
          tkt2213Function, 0, 0, 0);
  }

  if( rc==SQLITE4_OK ){
    rc = sqlite4_create_function(
        db, "x_sqlite_exec", 1, db, sqlite4ExecFunc, 0, 0, 0
    );
  }

  if( sqlite4TestErrCode(interp, db, rc) ) return TCL_ERROR;
  Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0);
  return TCL_OK;
}

/*
** Routines to implement the x_count() aggregate function.
**
** x_count() counts the number of non-null arguments.  But there are
** some twists for testing purposes.
**
** If the argument to x_count() is 40 then a UTF-8 error is reported
** on the step function.  If x_count(41) is seen, then a UTF-16 error
** is reported on the step function.  If the total count is 42, then
** a UTF-8 error is reported on the finalize function.
*/
typedef struct t1CountCtx t1CountCtx;
struct t1CountCtx {
  int n;
};
static void t1CountStep(
  sqlite4_context *context,
  int argc,
  sqlite4_value **argv
){
  t1CountCtx *p;
  p = sqlite4_aggregate_context(context, sizeof(*p));
  if( (argc==0 || SQLITE4_NULL!=sqlite4_value_type(argv[0]) ) && p ){
    p->n++;
  }
  if( argc>0 ){
    int v = sqlite4_value_int(argv[0]);
    if( v==40 ){
      sqlite4_result_error(context, "value of 40 handed to x_count", -1);
#ifndef SQLITE4_OMIT_UTF16
    }else if( v==41 ){
      const char zUtf16ErrMsg[] = { 0, 0x61, 0, 0x62, 0, 0x63, 0, 0, 0};
      sqlite4_result_error16(context, &zUtf16ErrMsg[1-SQLITE4_BIGENDIAN], -1);
#endif
    }
  }
}   
static void t1CountFinalize(sqlite4_context *context){
  t1CountCtx *p;
  p = sqlite4_aggregate_context(context, sizeof(*p));
  if( p ){
    if( p->n==42 ){
      sqlite4_result_error(context, "x_count totals to 42", -1);
    }else{
      sqlite4_result_int(context, p ? p->n : 0);
    }
  }
}

/*
** Usage:  sqlite4_create_aggregate DB
**
** Call the sqlite4_create_function API on the given database in order
** to create a function named "x_count".  This function is similar
** to the built-in count() function, with a few special quirks
** for testing the sqlite4_result_error() APIs.
**
** The original motivation for this routine was to be able to call the
** sqlite4_create_aggregate function while a query is in progress in order
** to test the SQLITE4_MISUSE detection logic.  See misuse.test.
**
** This routine was later extended to test the use of sqlite4_result_error()
** within aggregate functions.
**
** Later: It is now also extended to register the aggregate function
** "legacy_count()" with the supplied database handle. This is used
** to test the deprecated sqlite4_aggregate_count() API.
*/
static int test_create_aggregate(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  sqlite4 *db;
  int rc;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FILENAME\"", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  rc = sqlite4_create_function(db, "x_count", 0, 0, 0,
      t1CountStep, t1CountFinalize, 0);
  if( rc==SQLITE4_OK ){
    rc = sqlite4_create_function(db, "x_count", 1, 0, 0,
        t1CountStep, t1CountFinalize, 0);
  }
  if( sqlite4TestErrCode(interp, db, rc) ) return TCL_ERROR;
  Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0);
  return TCL_OK;
}


/*
** Usage:  printf TEXT
**
** Send output to printf.  Use this rather than puts to merge the output
** in the correct sequence with debugging printfs inserted into C code.
** Puts uses a separate buffer and debugging statements will be out of
** sequence if it is used.
*/
static int test_printf(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " TEXT\"", 0);
    return TCL_ERROR;
  }
  printf("%s\n", argv[1]);
  return TCL_OK;
}



/*
** Usage:  sqlite4_mprintf_int FORMAT INTEGER INTEGER INTEGER
**
** Call mprintf with three integer arguments
*/
static int sqlite4_mprintf_int(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  int a[3], i;
  char *z;
  if( argc!=5 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FORMAT INT INT INT\"", 0);
    return TCL_ERROR;
  }
  for(i=2; i<5; i++){
    if( Tcl_GetInt(interp, argv[i], &a[i-2]) ) return TCL_ERROR;
  }
  z = sqlite4_mprintf(0, argv[1], a[0], a[1], a[2]);
  Tcl_AppendResult(interp, z, 0);
  sqlite4_free(0, z);
  return TCL_OK;
}

/*
** Usage:  sqlite4_mprintf_int64 FORMAT INTEGER INTEGER INTEGER
**
** Call mprintf with three 64-bit integer arguments
*/
static int sqlite4_mprintf_int64(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  int i;
  sqlite4_int64 a[3];
  char *z;
  if( argc!=5 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FORMAT INT INT INT\"", 0);
    return TCL_ERROR;
  }
  for(i=2; i<5; i++){
    if( sqlite4Atoi64(argv[i], &a[i-2], 1000000, SQLITE4_UTF8) ){
      Tcl_AppendResult(interp, "argument is not a valid 64-bit integer", 0);
      return TCL_ERROR;
    }
  }
  z = sqlite4_mprintf(0, argv[1], a[0], a[1], a[2]);
  Tcl_AppendResult(interp, z, 0);
  sqlite4_free(0, z);
  return TCL_OK;
}

/*
** Usage:  sqlite4_mprintf_long FORMAT INTEGER INTEGER INTEGER
**
** Call mprintf with three long integer arguments.   This might be the
** same as sqlite4_mprintf_int or sqlite4_mprintf_int64, depending on
** platform.
*/
static int sqlite4_mprintf_long(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  int i;
  long int a[3];
  int b[3];
  char *z;
  if( argc!=5 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FORMAT INT INT INT\"", 0);
    return TCL_ERROR;
  }
  for(i=2; i<5; i++){
    if( Tcl_GetInt(interp, argv[i], &b[i-2]) ) return TCL_ERROR;
    a[i-2] = (long int)b[i-2];
    a[i-2] &= (((u64)1)<<(sizeof(int)*8))-1;
  }
  z = sqlite4_mprintf(0, argv[1], a[0], a[1], a[2]);
  Tcl_AppendResult(interp, z, 0);
  sqlite4_free(0, z);
  return TCL_OK;
}

/*
** Usage:  sqlite4_mprintf_str FORMAT INTEGER INTEGER STRING
**
** Call mprintf with two integer arguments and one string argument
*/
static int sqlite4_mprintf_str(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  int a[3], i;
  char *z;
  if( argc<4 || argc>5 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FORMAT INT INT ?STRING?\"", 0);
    return TCL_ERROR;
  }
  for(i=2; i<4; i++){
    if( Tcl_GetInt(interp, argv[i], &a[i-2]) ) return TCL_ERROR;
  }
  z = sqlite4_mprintf(0, argv[1], a[0], a[1], argc>4 ? argv[4] : NULL);
  Tcl_AppendResult(interp, z, 0);
  sqlite4_free(0, z);
  return TCL_OK;
}

/*
** Usage:  sqlite4_snprintf_str INTEGER FORMAT INTEGER INTEGER STRING
**
** Call mprintf with two integer arguments and one string argument
*/
static int sqlite4_snprintf_str(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  int a[3], i;
  int n;
  char *z;
  if( argc<5 || argc>6 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " INT FORMAT INT INT ?STRING?\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], &n) ) return TCL_ERROR;
  if( n<0 ){
    Tcl_AppendResult(interp, "N must be non-negative", 0);
    return TCL_ERROR;
  }
  for(i=3; i<5; i++){
    if( Tcl_GetInt(interp, argv[i], &a[i-3]) ) return TCL_ERROR;
  }
  z = sqlite4_malloc(0, n+1);
  sqlite4_snprintf(z, n, argv[2], a[0], a[1], argc>4 ? argv[5] : NULL);
  Tcl_AppendResult(interp, z, 0);
  sqlite4_free(0, z);
  return TCL_OK;
}

/*
** Usage:  sqlite4_mprintf_double FORMAT INTEGER INTEGER DOUBLE
**
** Call mprintf with two integer arguments and one double argument
*/
static int sqlite4_mprintf_double(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  int a[3], i;
  double r;
  char *z;
  if( argc!=5 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FORMAT INT INT DOUBLE\"", 0);
    return TCL_ERROR;
  }
  for(i=2; i<4; i++){
    if( Tcl_GetInt(interp, argv[i], &a[i-2]) ) return TCL_ERROR;
  }
  if( Tcl_GetDouble(interp, argv[4], &r) ) return TCL_ERROR;
  z = sqlite4_mprintf(0, argv[1], a[0], a[1], r);
  Tcl_AppendResult(interp, z, 0);
  sqlite4_free(0, z);
  return TCL_OK;
}

/*
** Usage:  sqlite4_mprintf_scaled FORMAT DOUBLE DOUBLE
**
** Call mprintf with a single double argument which is the product of the
** two arguments given above.  This is used to generate overflow and underflow
** doubles to test that they are converted properly.
*/
static int sqlite4_mprintf_scaled(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  int i;
  double r[2];
  char *z;
  if( argc!=4 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FORMAT DOUBLE DOUBLE\"", 0);
    return TCL_ERROR;
  }
  for(i=2; i<4; i++){
    if( Tcl_GetDouble(interp, argv[i], &r[i-2]) ) return TCL_ERROR;
  }
  z = sqlite4_mprintf(0, argv[1], r[0]*r[1]);
  Tcl_AppendResult(interp, z, 0);
  sqlite4_free(0, z);
  return TCL_OK;
}

/*
** Usage:  sqlite4_mprintf_stronly FORMAT STRING
**
** Call mprintf with a single double argument which is the product of the
** two arguments given above.  This is used to generate overflow and underflow
** doubles to test that they are converted properly.
*/
static int sqlite4_mprintf_stronly(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  char *z;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FORMAT STRING\"", 0);
    return TCL_ERROR;
  }
  z = sqlite4_mprintf(0, argv[1], argv[2]);
  Tcl_AppendResult(interp, z, 0);
  sqlite4_free(0, z);
  return TCL_OK;
}

/*
** Usage:  sqlite4_mprintf_hexdouble FORMAT HEX
**
** Call mprintf with a single double argument which is derived from the
** hexadecimal encoding of an IEEE double.
*/
static int sqlite4_mprintf_hexdouble(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  char *z;
  double r;
  unsigned int x1, x2;
  sqlite4_uint64 d;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FORMAT STRING\"", 0);
    return TCL_ERROR;
  }
  if( sscanf(argv[2], "%08x%08x", &x2, &x1)!=2 ){
    Tcl_AppendResult(interp, "2nd argument should be 16-characters of hex", 0);
    return TCL_ERROR;
  }
  d = x2;
  d = (d<<32) + x1;
  memcpy(&r, &d, sizeof(r));
  z = sqlite4_mprintf(0, argv[1], r);
  Tcl_AppendResult(interp, z, 0);
  sqlite4_free(0, z);
  return TCL_OK;
}

/*
** Usage: sqlite4_libversion_number
**
*/
static int test_libversion_number(
  ClientData clientData, /* Pointer to sqlite4_enable_XXX function */
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int objc,              /* Number of arguments */
  Tcl_Obj *CONST objv[]  /* Command arguments */
){
  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite4_libversion_number()));
  return TCL_OK;
}

/*
** Usage: sqlite4_table_column_metadata DB dbname tblname colname
**
*/
#ifdef SQLITE4_ENABLE_COLUMN_METADATA
static int test_table_column_metadata(
  ClientData clientData, /* Pointer to sqlite4_enable_XXX function */
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int objc,              /* Number of arguments */
  Tcl_Obj *CONST objv[]  /* Command arguments */
){
  sqlite4 *db;
  const char *zDb;
  const char *zTbl;
  const char *zCol;
  int rc;
  Tcl_Obj *pRet;

  const char *zDatatype;
  const char *zCollseq;
  int notnull;
  int primarykey;
  int autoincrement;

  if( objc!=5 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB dbname tblname colname");
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  zDb = Tcl_GetString(objv[2]);
  zTbl = Tcl_GetString(objv[3]);
  zCol = Tcl_GetString(objv[4]);

  if( strlen(zDb)==0 ) zDb = 0;

  rc = sqlite4_table_column_metadata(db, zDb, zTbl, zCol, 
      &zDatatype, &zCollseq, &notnull, &primarykey, &autoincrement);

  if( rc!=SQLITE4_OK ){
    Tcl_AppendResult(interp, sqlite4_errmsg(db), 0);
    return TCL_ERROR;
  }

  pRet = Tcl_NewObj();
  Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zDatatype, -1));
  Tcl_ListObjAppendElement(0, pRet, Tcl_NewStringObj(zCollseq, -1));
  Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(notnull));
  Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(primarykey));
  Tcl_ListObjAppendElement(0, pRet, Tcl_NewIntObj(autoincrement));
  Tcl_SetObjResult(interp, pRet);

  return TCL_OK;
}
#endif


/*
** Usage: sqlite4_create_collation DB NAME CMP-PROC MKKEY-PROC DEL-PROC
*/
struct TestCollationX {
  Tcl_Interp *interp;
  Tcl_Obj *pCmp;
  Tcl_Obj *pMkkey;
  Tcl_Obj *pDel;
};
typedef struct TestCollationX TestCollationX;

static void testCreateCollationDel(void *pCtx){
  TestCollationX *p = (TestCollationX *)pCtx;

  int rc = Tcl_EvalObjEx(p->interp, p->pDel, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
  if( rc!=TCL_OK ){
    Tcl_BackgroundError(p->interp);
  }

  Tcl_DecrRefCount(p->pCmp);
  Tcl_DecrRefCount(p->pDel);
  Tcl_DecrRefCount(p->pMkkey);
  sqlite4_free(0, (void *)p);
}

static int testCreateCollationCmp(
  void *pCtx,
  sqlite4_value *pLeft,
  sqlite4_value *pRight,
  int *pRes
){
  int nLeft;
  int nRight;
  const char *zLeft;
  const char *zRight;

  TestCollationX *p = (TestCollationX *)pCtx;
  Tcl_Obj *pScript;               /* Tcl script to evaluate */
  int iRes = 0;                   /* Result of comparison */
  int rc;                         /* Tcl_Eval() return code */

  zLeft = sqlite4_value_text(pLeft, &nLeft);
  zRight = sqlite4_value_text(pRight, &nRight);
  if( !zLeft || !zRight ) return SQLITE4_NOMEM;

  pScript = Tcl_DuplicateObj(p->pCmp);
  Tcl_IncrRefCount(pScript);
  Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj(zLeft, nLeft));
  Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj(zRight,nRight));

  rc = Tcl_EvalObjEx(p->interp, pScript, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
  if( rc==TCL_OK ){
   rc = Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iRes);
  }
  Tcl_DecrRefCount(pScript);

  if( rc!=TCL_OK ){
    return SQLITE4_ERROR;
  }
  *pRes = iRes;
  return SQLITE4_OK;
}

static int testCreateCollationMkkey(
  void *pCtx,                     /* Context pointer */
  sqlite4_value *pVal,            /* Value to create sort-key for */
  int nBuf,                       /* Size of output buffer in bytes */
  void *pBuf,                     /* Output buffer */
  int *pnOut                      /* Size of sort-key in bytes */
){
  TestCollationX *p = (TestCollationX *)pCtx;
  Tcl_Obj *pScript;
  const char *zIn;
  int nIn;
  int rc;
  const char *zOut;
  int nOut;

  zIn = sqlite4_value_text(pVal, &nIn);
  if( !zIn ) return SQLITE4_NOMEM;
  
  pScript = Tcl_DuplicateObj(p->pMkkey);
  Tcl_IncrRefCount(pScript);
  Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj(zIn, nIn));

  rc = Tcl_EvalObjEx(p->interp, pScript, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
  Tcl_DecrRefCount(pScript);
  if( rc!=TCL_OK ){
    return SQLITE4_ERROR;
  }

  zOut = Tcl_GetStringFromObj(Tcl_GetObjResult(p->interp), &nOut);
  if( nOut<=nBuf ){
    memcpy(pBuf, zOut, nOut);
  }
  *pnOut = nOut;
  return SQLITE4_OK;
}

static int test_create_collation(
  ClientData clientData,          /* Not used */
  Tcl_Interp *interp,             /* The TCL interpreter */
  int objc,                       /* Number of arguments */
  Tcl_Obj *CONST objv[]           /* Command arguments */
){
  /* Callback functions */
  int(*xCompare)(void*, sqlite4_value*, sqlite4_value*, int*);
  int(*xMakeKey)(void*, sqlite4_value*, int, void*, int*);
  int nByte;                      /* Size of callback scripts in bytes */
  TestCollationX *p;              /* New context object */
  sqlite4 *db;                    /* Database handle */
  int rc;                         /* create_collation() return code */

  if( objc!=6 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB NAME CMP-PROC MKKEY-PROC DEL-PROC");
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;

  Tcl_GetStringFromObj(objv[3], &nByte);
  xCompare = (nByte>0 ? testCreateCollationCmp : 0);
  Tcl_GetStringFromObj(objv[4], &nByte);
  xMakeKey = (nByte>0 ? testCreateCollationMkkey : 0);

  if( xCompare==0 && xMakeKey==0 ){
    rc = sqlite4_create_collation(db, Tcl_GetString(objv[2]), 0, 0, 0, 0);
  }else{
    p = (TestCollationX *)sqlite4_malloc(0, sizeof(TestCollationX));
    p->pCmp = objv[3];
    p->pMkkey = objv[4];
    p->pDel = objv[5];
    p->interp = interp;
    Tcl_IncrRefCount(p->pCmp);
    Tcl_IncrRefCount(p->pMkkey);
    Tcl_IncrRefCount(p->pDel);

    rc = sqlite4_create_collation(db, Tcl_GetString(objv[2]), (void *)p,
        testCreateCollationCmp, testCreateCollationMkkey, testCreateCollationDel
    );
  }

  if( rc!=SQLITE4_OK ){
    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite4TestErrorName(rc), -1));
    return TCL_ERROR;
  }
  Tcl_ResetResult(interp);
  return TCL_OK;
}
/* End of [sqlite4_create_collation] implementation.
********************************************************************/

/*
** Usage: sqlite4_collation_needed DB CALLBACK-PROC DEL-PROC
*/
struct TestNeededX {
  Tcl_Interp *interp;
  Tcl_Obj *pNeeded;
  Tcl_Obj *pDel;
};
typedef struct TestNeededX TestNeededX;

static void testCollationNeeded(void *pCtx, sqlite4 *db, const char *zReq){
  TestNeededX *p = (TestNeededX *)pCtx;
  Tcl_Obj *pScript;
  int rc;

  pScript = Tcl_DuplicateObj(p->pNeeded);
  Tcl_IncrRefCount(pScript);
  Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj(zReq, -1));
  rc = Tcl_EvalObjEx(p->interp, pScript, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
  Tcl_DecrRefCount(pScript);

  if( rc!=TCL_OK ){
    Tcl_BackgroundError(p->interp);
  }
}

static void testCollationNeededDel(void *pCtx){
  TestNeededX *p = (TestNeededX*)pCtx;
  int rc = Tcl_EvalObjEx(p->interp, p->pDel, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
  if( rc!=TCL_OK ){
    Tcl_BackgroundError(p->interp);
  }

  Tcl_DecrRefCount(p->pNeeded);
  Tcl_DecrRefCount(p->pDel);
  sqlite4_free(0, (void *)p);
}

static int test_collation_needed(
  ClientData clientData,          /* Not used */
  Tcl_Interp *interp,             /* The TCL interpreter */
  int objc,                       /* Number of arguments */
  Tcl_Obj *CONST objv[]           /* Command arguments */
){
  int nByte;                      /* Size of callback scripts in bytes */
  TestNeededX *p;                 /* New context object */
  sqlite4 *db;                    /* Database handle */
  int rc;                         /* create_collation() return code */

  if( objc!=4 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB CALLBACK-PROC DEL-PROC");
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;

  Tcl_GetStringFromObj(objv[2], &nByte);
  if( nByte==0 ){
    rc = sqlite4_collation_needed(db, 0, 0, 0);
  }else{
    p = (TestNeededX*)sqlite4_malloc(0, sizeof(TestNeededX));
    p->pNeeded = objv[2];
    p->pDel = objv[3];
    p->interp = interp;
    Tcl_IncrRefCount(p->pNeeded);
    Tcl_IncrRefCount(p->pDel);

    rc = sqlite4_collation_needed(
        db, (void*)p, testCollationNeeded, testCollationNeededDel
    );
  }

  if( rc!=SQLITE4_OK ){
    Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite4TestErrorName(rc), -1));
    return TCL_ERROR;
  }
  Tcl_ResetResult(interp);
  return TCL_OK;
}
/* End of [sqlite4_collation_needed] implementation.
********************************************************************/
/*
** Usage: sqlite4_profile DB PROFILE-CMD DEL-CMD
*/
struct TestProfileX {
  Tcl_Interp *interp;
  Tcl_Obj *pProfile;
  Tcl_Obj *pDel;
};
typedef struct TestProfileX TestProfileX;
static void testProfile(void *pCtx, const char *z, sqlite4_uint64 i){
  TestProfileX *p = (TestProfileX *)pCtx;
  Tcl_Obj *pScript;
  int rc;
  pScript = Tcl_DuplicateObj(p->pProfile);
  Tcl_IncrRefCount(pScript);
  Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj(z, -1));
  Tcl_ListObjAppendElement(0, pScript, Tcl_NewWideIntObj(i));
  rc = Tcl_EvalObjEx(p->interp, pScript, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
  Tcl_DecrRefCount(pScript);
  if( rc!=TCL_OK ){
    Tcl_BackgroundError(p->interp);
  }
}
static void testProfileDel(void *pCtx){
  TestProfileX *p = (TestProfileX*)pCtx;
  int rc = Tcl_EvalObjEx(p->interp, p->pDel, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
  if( rc!=TCL_OK ){
    Tcl_BackgroundError(p->interp);
  }
  Tcl_DecrRefCount(p->pProfile);
  Tcl_DecrRefCount(p->pDel);
  sqlite4_free(0, (void *)p);
}
static int test_profile(
  ClientData clientData,          /* Not used */
  Tcl_Interp *interp,             /* The TCL interpreter */
  int objc,                       /* Number of arguments */
  Tcl_Obj *CONST objv[]           /* Command arguments */
){
  int nByte;                      /* Size of callback scripts in bytes */
  TestProfileX *p;                 /* New context object */
  sqlite4 *db;                    /* Database handle */

  if( objc!=4 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB PROFILE-CMD DEL-CMD");
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;

  Tcl_GetStringFromObj(objv[2], &nByte);
  if( nByte==0 ){
    sqlite4_profile(db, 0, 0, 0);
  }else{
    p = (TestProfileX*)sqlite4_malloc(0, sizeof(TestProfileX));
    p->pProfile = objv[2];
    p->pDel = objv[3];
    p->interp = interp;
    Tcl_IncrRefCount(p->pProfile);
    Tcl_IncrRefCount(p->pDel);
    sqlite4_profile(db, (void*)p, testProfile, testProfileDel);
  }

  Tcl_ResetResult(interp);
  return TCL_OK;
}
/* End of [sqlite4_profile] implementation.
********************************************************************/

/*
** Usage: sqlite4_trace DB TRACE-CMD DEL-CMD
*/
struct TestTraceX {
  Tcl_Interp *interp;
  Tcl_Obj *pTrace;
  Tcl_Obj *pDel;
};
typedef struct TestTraceX TestTraceX;
static void testTrace(void *pCtx, const char *z){
  TestTraceX *p = (TestTraceX *)pCtx;
  Tcl_Obj *pScript;
  int rc;
  pScript = Tcl_DuplicateObj(p->pTrace);
  Tcl_IncrRefCount(pScript);
  Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj(z, -1));
  rc = Tcl_EvalObjEx(p->interp, pScript, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
  Tcl_DecrRefCount(pScript);
  if( rc!=TCL_OK ){
    Tcl_BackgroundError(p->interp);
  }
}
static void testTraceDel(void *pCtx){
  TestTraceX *p = (TestTraceX*)pCtx;
  int rc = Tcl_EvalObjEx(p->interp, p->pDel, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
  if( rc!=TCL_OK ){
    Tcl_BackgroundError(p->interp);
  }
  Tcl_DecrRefCount(p->pTrace);
  Tcl_DecrRefCount(p->pDel);
  sqlite4_free(0, (void *)p);
}
static int test_trace(
  ClientData clientData,          /* Not used */
  Tcl_Interp *interp,             /* The TCL interpreter */
  int objc,                       /* Number of arguments */
  Tcl_Obj *CONST objv[]           /* Command arguments */
){
  int nByte;                      /* Size of callback scripts in bytes */
  TestTraceX *p;                 /* New context object */
  sqlite4 *db;                    /* Database handle */

  if( objc!=4 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB PROFILE-CMD DEL-CMD");
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;

  Tcl_GetStringFromObj(objv[2], &nByte);
  if( nByte==0 ){
    sqlite4_trace(db, 0, 0, 0);
  }else{
    p = (TestTraceX*)sqlite4_malloc(0, sizeof(TestTraceX));
    p->pTrace = objv[2];
    p->pDel = objv[3];
    p->interp = interp;
    Tcl_IncrRefCount(p->pTrace);
    Tcl_IncrRefCount(p->pDel);
    sqlite4_trace(db, (void*)p, testTrace, testTraceDel);
  }

  Tcl_ResetResult(interp);
  return TCL_OK;
}
/* End of [sqlite4_profile] implementation.
********************************************************************/
/*
** USAGE: sqlite4_create_function_v2 DB NAME NARG ENC ?SWITCHES?
**
** Available switches are:
**
**   -func    SCRIPT
**   -step    SCRIPT
**   -final   SCRIPT
**   -destroy SCRIPT
*/
typedef struct CreateFunctionV2 CreateFunctionV2;
struct CreateFunctionV2 {
  Tcl_Interp *interp;
  Tcl_Obj *pFunc;                 /* Script for function invocation */
  Tcl_Obj *pStep;                 /* Script for agg. step invocation */
  Tcl_Obj *pFinal;                /* Script for agg. finalization invocation */
  Tcl_Obj *pDestroy;              /* Destructor script */
};
static void cf2Func(sqlite4_context *ctx, int nArg, sqlite4_value **aArg){
}
static void cf2Step(sqlite4_context *ctx, int nArg, sqlite4_value **aArg){
}
static void cf2Final(sqlite4_context *ctx){
}
static void cf2Destroy(void *pUser){
  CreateFunctionV2 *p = (CreateFunctionV2 *)pUser;

  if( p->interp && p->pDestroy ){
    int rc = Tcl_EvalObjEx(p->interp, p->pDestroy, 0);
    if( rc!=TCL_OK ) Tcl_BackgroundError(p->interp);
  }

  if( p->pFunc ) Tcl_DecrRefCount(p->pFunc); 
  if( p->pStep ) Tcl_DecrRefCount(p->pStep); 
  if( p->pFinal ) Tcl_DecrRefCount(p->pFinal); 
  if( p->pDestroy ) Tcl_DecrRefCount(p->pDestroy); 
  sqlite4_free(0, p);
}
static int test_create_function_v2(
  ClientData clientData,          /* Not used */
  Tcl_Interp *interp,             /* The invoking TCL interpreter */
  int objc,                       /* Number of arguments */
  Tcl_Obj *CONST objv[]           /* Command arguments */
){
  sqlite4 *db;
  const char *zFunc;
  int nArg;
  CreateFunctionV2 *p;
  int i;
  int rc;

  if( objc<4 || (objc%2) ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB NAME NARG SWITCHES...");
    return TCL_ERROR;
  }

  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  zFunc = Tcl_GetString(objv[2]);
  if( Tcl_GetIntFromObj(interp, objv[3], &nArg) ) return TCL_ERROR;

  p = sqlite4_malloc(0, sizeof(CreateFunctionV2));
  assert( p );
  memset(p, 0, sizeof(CreateFunctionV2));
  p->interp = interp;

  for(i=4; i<objc; i+=2){
    int iSwitch;
    const char *azSwitch[] = {"-func", "-step", "-final", "-destroy", 0};
    if( Tcl_GetIndexFromObj(interp, objv[i], azSwitch, "switch", 0, &iSwitch) ){
      sqlite4_free(0, p);
      return TCL_ERROR;
    }

    switch( iSwitch ){
      case 0: p->pFunc = objv[i+1];      break;
      case 1: p->pStep = objv[i+1];      break;
      case 2: p->pFinal = objv[i+1];     break;
      case 3: p->pDestroy = objv[i+1];   break;
    }
  }
  if( p->pFunc ) p->pFunc = Tcl_DuplicateObj(p->pFunc); 
  if( p->pStep ) p->pStep = Tcl_DuplicateObj(p->pStep); 
  if( p->pFinal ) p->pFinal = Tcl_DuplicateObj(p->pFinal); 
  if( p->pDestroy ) p->pDestroy = Tcl_DuplicateObj(p->pDestroy); 

  if( p->pFunc ) Tcl_IncrRefCount(p->pFunc); 
  if( p->pStep ) Tcl_IncrRefCount(p->pStep); 
  if( p->pFinal ) Tcl_IncrRefCount(p->pFinal); 
  if( p->pDestroy ) Tcl_IncrRefCount(p->pDestroy); 

  rc = sqlite4_create_function(db, zFunc, nArg, (void *)p, 
      (p->pFunc ? cf2Func : 0),
      (p->pStep ? cf2Step : 0),
      (p->pFinal ? cf2Final : 0),
      cf2Destroy
  );
  if( rc!=SQLITE4_OK ){
    Tcl_ResetResult(interp);
    Tcl_AppendResult(interp, sqlite4TestErrorName(rc), 0);
    return TCL_ERROR;
  }
  return TCL_OK;
}


/*
** Usage:  sqlite_abort
**
** Shutdown the process immediately.  This is not a clean shutdown.
** This command is used to test the recoverability of a database in
** the event of a program crash.
*/
static int sqlite_abort(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
#if defined(_MSC_VER)
  /* We do this, otherwise the test will halt with a popup message
   * that we have to click away before the test will continue.
   */
  _set_abort_behavior( 0, _CALL_REPORTFAULT );
#endif
  exit(255);
  assert( interp==0 );   /* This will always fail */
  return TCL_OK;
}

/*
** The following routine is a user-defined SQL function whose purpose
** is to test the sqlite_set_result() API.
*/
static void testFunc(sqlite4_context *context, int argc, sqlite4_value **argv){
  while( argc>=2 ){
    const char *zArg0 = (char*)sqlite4_value_text(argv[0], 0);
    if( zArg0 ){
      if( 0==sqlite4_stricmp(zArg0, "int") ){
        sqlite4_result_int(context, sqlite4_value_int(argv[1]));
      }else if( sqlite4_stricmp(zArg0,"int64")==0 ){
        sqlite4_result_int64(context, sqlite4_value_int64(argv[1]));
      }else if( sqlite4_stricmp(zArg0,"string")==0 ){
        sqlite4_result_text(context, (char*)sqlite4_value_text(argv[1], 0), -1,
            SQLITE4_TRANSIENT, 0);
      }else if( sqlite4_stricmp(zArg0,"double")==0 ){
        sqlite4_result_double(context, sqlite4_value_double(argv[1]));
      }else if( sqlite4_stricmp(zArg0,"null")==0 ){
        sqlite4_result_null(context);
      }else if( sqlite4_stricmp(zArg0,"value")==0 ){
        sqlite4_result_value(context, argv[sqlite4_value_int(argv[1])]);
      }else{
        goto error_out;
      }
    }else{
      goto error_out;
    }
    argc -= 2;
    argv += 2;
  }
  return;

error_out:
  sqlite4_result_error(context,"first argument should be one of: "
      "int int64 string double null value", -1);
}

/*
** Usage:   sqlite_register_test_function  DB  NAME
**
** Register the test SQL function on the database DB under the name NAME.
*/
static int test_register_func(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  sqlite4 *db;
  int rc;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
       " DB FUNCTION-NAME", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  rc = sqlite4_create_function(db, argv[2], -1, 0, testFunc, 0, 0, 0);
  if( rc!=0 ){
    Tcl_AppendResult(interp, sqlite4ErrStr(rc), 0);
    return TCL_ERROR;
  }
  if( sqlite4TestErrCode(interp, db, rc) ) return TCL_ERROR;
  return TCL_OK;
}

/*
** Usage:  sqlite4_finalize  STMT 
**
** Finalize a statement handle.
*/
static int test_finalize(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int rc;
  sqlite4 *db = 0;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " <STMT>", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;

  if( pStmt ){
    db = StmtToDb(pStmt);
  }
  rc = sqlite4_finalize(pStmt);
  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
  if( db && sqlite4TestErrCode(interp, db, rc) ) return TCL_ERROR;
  return TCL_OK;
}

/*
** Usage:  sqlite4_stmt_status  STMT  CODE  RESETFLAG
**
** Get the value of a status counter from a statement.
*/
static int test_stmt_status(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  int iValue;
  int i, op, resetFlag;
  const char *zOpName;
  sqlite4_stmt *pStmt;

  static const struct {
    const char *zName;
    int op;
  } aOp[] = {
    { "SQLITE4_STMTSTATUS_FULLSCAN_STEP",   SQLITE4_STMTSTATUS_FULLSCAN_STEP   },
    { "SQLITE4_STMTSTATUS_SORT",            SQLITE4_STMTSTATUS_SORT            },
    { "SQLITE4_STMTSTATUS_AUTOINDEX",       SQLITE4_STMTSTATUS_AUTOINDEX       },
  };
  if( objc!=4 ){
    Tcl_WrongNumArgs(interp, 1, objv, "STMT PARAMETER RESETFLAG");
    return TCL_ERROR;
  }
  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  zOpName = Tcl_GetString(objv[2]);
  for(i=0; i<ArraySize(aOp); i++){
    if( strcmp(aOp[i].zName, zOpName)==0 ){
      op = aOp[i].op;
      break;
    }
  }
  if( i>=ArraySize(aOp) ){
    if( Tcl_GetIntFromObj(interp, objv[2], &op) ) return TCL_ERROR;
  }
  if( Tcl_GetBooleanFromObj(interp, objv[3], &resetFlag) ) return TCL_ERROR;
  iValue = sqlite4_stmt_status(pStmt, op, resetFlag);
  Tcl_SetObjResult(interp, Tcl_NewIntObj(iValue));
  return TCL_OK;
}

/*
** Usage:  sqlite4_next_stmt  DB  STMT
**
** Return the next statment in sequence after STMT.
*/
static int test_next_stmt(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  sqlite4 *db = 0;
  char zBuf[50];

  if( objc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " DB STMT", 0);
    return TCL_ERROR;
  }

  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  if( getStmtPointer(interp, Tcl_GetString(objv[2]), &pStmt) ) return TCL_ERROR;
  pStmt = sqlite4_next_stmt(db, pStmt);
  if( pStmt ){
    if( sqlite4TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;
    Tcl_AppendResult(interp, zBuf, 0);
  }
  return TCL_OK;
}

/*
** Usage:  sqlite4_stmt_readonly  STMT
**
** Return true if STMT is a NULL pointer or a pointer to a statement
** that is guaranteed to leave the database unmodified.
*/
static int test_stmt_readonly(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int rc;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " STMT", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  rc = sqlite4_stmt_readonly(pStmt);
  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(rc));
  return TCL_OK;
}

/*
** Usage:  sqlite4_stmt_busy  STMT
**
** Return true if STMT is a non-NULL pointer to a statement
** that has been stepped but not to completion.
*/
static int test_stmt_busy(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int rc;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " STMT", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  rc = sqlite4_stmt_busy(pStmt);
  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(rc));
  return TCL_OK;
}

/*
** Usage:  uses_stmt_journal  STMT
**
** Return true if STMT uses a statement journal.
*/
static int uses_stmt_journal(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " STMT", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  sqlite4_stmt_readonly(pStmt);
  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(((Vdbe *)pStmt)->needSavepoint));
  return TCL_OK;
}


/*
** Usage:  sqlite4_reset  STMT 
**
** Reset a statement handle.
*/
static int test_reset(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int rc;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " <STMT>", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;

  rc = sqlite4_reset(pStmt);
  if( pStmt && sqlite4TestErrCode(interp, StmtToDb(pStmt), rc) ){
    return TCL_ERROR;
  }
  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
/*
  if( rc ){
    return TCL_ERROR;
  }
*/
  return TCL_OK;
}

/*
** Usage:  sqlite4_changes DB
**
** Return the number of changes made to the database by the last SQL
** execution.
*/
static int test_changes(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4 *db;
  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
       Tcl_GetString(objv[0]), " DB", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite4_changes(db)));
  return TCL_OK;
}

/*
** This is the "static_bind_value" that variables are bound to when
** the FLAG option of sqlite4_bind is "static"
*/
static char *sqlite_static_bind_value = 0;
static int sqlite_static_bind_nbyte = 0;

/*
** Usage:  sqlite4_bind  VM  IDX  VALUE  FLAGS
**
** Sets the value of the IDX-th occurance of "?" in the original SQL
** string.  VALUE is the new value.  If FLAGS=="null" then VALUE is
** ignored and the value is set to NULL.  If FLAGS=="static" then
** the value is set to the value of a static variable named
** "sqlite_static_bind_value".  If FLAGS=="normal" then a copy
** of the VALUE is made.  If FLAGS=="blob10" then a VALUE is ignored
** an a 10-byte blob "abc\000xyz\000pq" is inserted.
*/
static int test_bind(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  sqlite4_stmt *pStmt;
  int rc;
  int idx;
  if( argc!=5 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
       " VM IDX VALUE (null|static|normal)\"", 0);
    return TCL_ERROR;
  }
  if( getStmtPointer(interp, argv[1], &pStmt) ) return TCL_ERROR;
  if( Tcl_GetInt(interp, argv[2], &idx) ) return TCL_ERROR;
  if( strcmp(argv[4],"null")==0 ){
    rc = sqlite4_bind_null(pStmt, idx);
  }else if( strcmp(argv[4],"static")==0 ){
    rc = sqlite4_bind_text(pStmt, idx, sqlite_static_bind_value, -1, 0, 0);
  }else if( strcmp(argv[4],"static-nbytes")==0 ){
    rc = sqlite4_bind_text(pStmt, idx, sqlite_static_bind_value,
                                       sqlite_static_bind_nbyte, 0, 0);
  }else if( strcmp(argv[4],"normal")==0 ){
    rc = sqlite4_bind_text(pStmt, idx, argv[3], -1, SQLITE4_TRANSIENT, 0);
  }else if( strcmp(argv[4],"blob10")==0 ){
    rc = sqlite4_bind_text(pStmt, idx, "abc\000xyz\000pq", 10,SQLITE4_STATIC,0);
  }else{
    Tcl_AppendResult(interp, "4th argument should be "
        "\"null\" or \"static\" or \"normal\"", 0);
    return TCL_ERROR;
  }
  if( sqlite4TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
  if( rc ){
    char zBuf[50];
    sprintf(zBuf, "(%d) ", rc);
    Tcl_AppendResult(interp, zBuf, sqlite4ErrStr(rc), 0);
    return TCL_ERROR;
  }
  return TCL_OK;
}

/*
** Usage: add_test_function <db ptr> <utf8> <utf16le> <utf16be>
**
** This function is used to test that SQLite selects the correct user
** function callback when multiple versions (for different text encodings)
** are available.
**
** Calling this routine registers up to three versions of the user function
** "test_function" with database handle <db>.  If the second argument is
** true, then a version of test_function is registered for UTF-8, if the
** third is true, a version is registered for UTF-16le, if the fourth is
** true, a UTF-16be version is available.  Previous versions of
** test_function are deleted.
**
** The user function is implemented by calling the following TCL script:
**
**   "test_function <enc> <arg>"
**
** Where <enc> is one of UTF-8, UTF-16LE or UTF16BE, and <arg> is the
** single argument passed to the SQL function. The value returned by
** the TCL script is used as the return value of the SQL function. It
** is passed to SQLite using UTF-16BE for a UTF-8 test_function(), UTF-8
** for a UTF-16LE test_function(), and UTF-16LE for an implementation that
** prefers UTF-16BE.
*/
#ifndef SQLITE4_OMIT_UTF16
static void test_function_utf8(
  sqlite4_context *pCtx, 
  int nArg,
  sqlite4_value **argv
){
  Tcl_Interp *interp;
  Tcl_Obj *pX;
  sqlite4_value *pVal;
  interp = (Tcl_Interp *)sqlite4_context_appdata(pCtx);
  pX = Tcl_NewStringObj("test_function", -1);
  Tcl_IncrRefCount(pX);
  Tcl_ListObjAppendElement(interp, pX, Tcl_NewStringObj("UTF-8", -1));
  Tcl_ListObjAppendElement(interp, pX, 
      Tcl_NewStringObj((char*)sqlite4_value_text(argv[0], 0), -1));
  Tcl_EvalObjEx(interp, pX, 0);
  Tcl_DecrRefCount(pX);
  sqlite4_result_text(pCtx, Tcl_GetStringResult(interp), -1,
                      SQLITE4_TRANSIENT, 0);
  pVal = sqlite4ValueNew(0);
  sqlite4ValueSetStr(pVal, -1, Tcl_GetStringResult(interp), 
      SQLITE4_UTF8, SQLITE4_STATIC, 0);
  sqlite4_result_text16be(pCtx, sqlite4_value_text16be(pVal, 0),
      -1, SQLITE4_TRANSIENT, 0);
  sqlite4ValueFree(pVal);
}
static void test_function_utf16le(
  sqlite4_context *pCtx, 
  int nArg,
  sqlite4_value **argv
){
  Tcl_Interp *interp;
  Tcl_Obj *pX;
  sqlite4_value *pVal;
  interp = (Tcl_Interp *)sqlite4_context_appdata(pCtx);
  pX = Tcl_NewStringObj("test_function", -1);
  Tcl_IncrRefCount(pX);
  Tcl_ListObjAppendElement(interp, pX, Tcl_NewStringObj("UTF-16LE", -1));
  Tcl_ListObjAppendElement(interp, pX, 
      Tcl_NewStringObj(sqlite4_value_text(argv[0], 0), -1));
  Tcl_EvalObjEx(interp, pX, 0);
  Tcl_DecrRefCount(pX);
  pVal = sqlite4ValueNew(0);
  sqlite4ValueSetStr(pVal, -1, Tcl_GetStringResult(interp), 
      SQLITE4_UTF8, SQLITE4_STATIC, 0);
  sqlite4_result_text(pCtx, sqlite4_value_text(pVal, 0), -1,
                      SQLITE4_TRANSIENT, 0);
  sqlite4ValueFree(pVal);
}
static void test_function_utf16be(
  sqlite4_context *pCtx, 
  int nArg,
  sqlite4_value **argv
){
  Tcl_Interp *interp;
  Tcl_Obj *pX;
  sqlite4_value *pVal;
  interp = (Tcl_Interp *)sqlite4_context_appdata(pCtx);
  pX = Tcl_NewStringObj("test_function", -1);
  Tcl_IncrRefCount(pX);
  Tcl_ListObjAppendElement(interp, pX, Tcl_NewStringObj("UTF-16BE", -1));
  Tcl_ListObjAppendElement(interp, pX, 
      Tcl_NewStringObj(sqlite4_value_text(argv[0], 0), -1));
  Tcl_EvalObjEx(interp, pX, 0);
  Tcl_DecrRefCount(pX);
  pVal = sqlite4ValueNew(0);
  sqlite4ValueSetStr(pVal, -1, Tcl_GetStringResult(interp), 
      SQLITE4_UTF8, SQLITE4_STATIC, 0);
  sqlite4_result_text16(pCtx, sqlite4_value_text16le(pVal, 0),
      -1, SQLITE4_TRANSIENT, 0);
  sqlite4_result_text16be(pCtx, sqlite4_value_text16le(pVal, 0),
      -1, SQLITE4_TRANSIENT, 0);
  sqlite4_result_text16le(pCtx, sqlite4_value_text16le(pVal, 0),
      -1, SQLITE4_TRANSIENT, 0);
  sqlite4ValueFree(pVal);
}
#endif /* SQLITE4_OMIT_UTF16 */
static int test_function(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
#ifndef SQLITE4_OMIT_UTF16
  sqlite4 *db;
  int val;

  if( objc!=5 ) goto bad_args;
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;

  if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
  if( val ){
    sqlite4_create_function(db, "test_function", 1,
        interp, test_function_utf8, 0, 0, 0);
  }
  if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[3], &val) ) return TCL_ERROR;
  if( val ){
    sqlite4_create_function(db, "test_function", 1,
        interp, test_function_utf16le, 0, 0, 0);
  }
  if( TCL_OK!=Tcl_GetBooleanFromObj(interp, objv[4], &val) ) return TCL_ERROR;
  if( val ){
    sqlite4_create_function(db, "test_function", 1,
        interp, test_function_utf16be, 0, 0, 0);
  }

  return TCL_OK;
bad_args:
  Tcl_AppendResult(interp, "wrong # args: should be \"",
      Tcl_GetStringFromObj(objv[0], 0), " <DB> <utf8> <utf16le> <utf16be>", 0);
#endif /* SQLITE4_OMIT_UTF16 */
  return TCL_ERROR;
}

/*
** Usage:         sqlite4_test_errstr <err code>
**
** Test that the english language string equivalents for sqlite error codes
** are sane. The parameter is an integer representing an sqlite error code.
** The result is a list of two elements, the string representation of the
** error code and the english language explanation.
*/
static int test_errstr(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  char *zCode;
  int i;
  if( objc!=1 ){
    Tcl_WrongNumArgs(interp, 1, objv, "<error code>");
  }

  zCode = Tcl_GetString(objv[1]);
  for(i=0; i<200; i++){
    if( 0==strcmp(t1ErrorName(i), zCode) ) break;
  }
  Tcl_SetResult(interp, (char *)sqlite4ErrStr(i), 0);
  return TCL_OK;
}

/*
** Usage:    breakpoint
**
** This routine exists for one purpose - to provide a place to put a
** breakpoint with GDB that can be triggered using TCL code.  The use
** for this is when a particular test fails on (say) the 1485th iteration.
** In the TCL test script, we can add code like this:
**
**     if {$i==1485} breakpoint
**
** Then run testfixture in the debugger and wait for the breakpoint to
** fire.  Then additional breakpoints can be set to trace down the bug.
*/
static int test_breakpoint(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  return TCL_OK;         /* Do nothing */
}

/*
** Usage:   sqlite4_bind_int  STMT N VALUE
**
** Test the sqlite4_bind_int interface.  STMT is a prepared statement.
** N is the index of a wildcard in the prepared statement.  This command
** binds a 32-bit integer VALUE to that wildcard.
*/
static int test_bind_int(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int idx;
  int value;
  int rc;

  if( objc!=4 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " STMT N VALUE", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[3], &value) ) return TCL_ERROR;

  rc = sqlite4_bind_int(pStmt, idx, value);
  if( sqlite4TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
  if( rc!=SQLITE4_OK ){
    return TCL_ERROR;
  }

  return TCL_OK;
}


/*
** Usage:   sqlite4_bind_int64  STMT N VALUE
**
** Test the sqlite4_bind_int64 interface.  STMT is a prepared statement.
** N is the index of a wildcard in the prepared statement.  This command
** binds a 64-bit integer VALUE to that wildcard.
*/
static int test_bind_int64(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int idx;
  i64 value;
  int rc;

  if( objc!=4 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " STMT N VALUE", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
  if( Tcl_GetWideIntFromObj(interp, objv[3], &value) ) return TCL_ERROR;

  rc = sqlite4_bind_int64(pStmt, idx, value);
  if( sqlite4TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
  if( rc!=SQLITE4_OK ){
    return TCL_ERROR;
  }

  return TCL_OK;
}


/*
** Usage:   sqlite4_bind_double  STMT N VALUE
**
** Test the sqlite4_bind_double interface.  STMT is a prepared statement.
** N is the index of a wildcard in the prepared statement.  This command
** binds a 64-bit integer VALUE to that wildcard.
*/
static int test_bind_double(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int idx;
  double value;
  int rc;
  const char *zVal;
  int i;
  static const struct {
    const char *zName;     /* Name of the special floating point value */
    unsigned int iUpper;   /* Upper 32 bits */
    unsigned int iLower;   /* Lower 32 bits */
  } aSpecialFp[] = {
    {  "NaN",      0x7fffffff, 0xffffffff },
    {  "SNaN",     0x7ff7ffff, 0xffffffff },
    {  "-NaN",     0xffffffff, 0xffffffff },
    {  "-SNaN",    0xfff7ffff, 0xffffffff },
    {  "+Inf",     0x7ff00000, 0x00000000 },
    {  "-Inf",     0xfff00000, 0x00000000 },
    {  "Epsilon",  0x00000000, 0x00000001 },
    {  "-Epsilon", 0x80000000, 0x00000001 },
    {  "NaN0",     0x7ff80000, 0x00000000 },
    {  "-NaN0",    0xfff80000, 0x00000000 },
  };

  if( objc!=4 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " STMT N VALUE", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;

  /* Intercept the string "NaN" and generate a NaN value for it.
  ** All other strings are passed through to Tcl_GetDoubleFromObj().
  ** Tcl_GetDoubleFromObj() should understand "NaN" but some versions
  ** contain a bug.
  */
  zVal = Tcl_GetString(objv[3]);
  for(i=0; i<sizeof(aSpecialFp)/sizeof(aSpecialFp[0]); i++){
    if( strcmp(aSpecialFp[i].zName, zVal)==0 ){
      sqlite4_uint64 x;
      x = aSpecialFp[i].iUpper;
      x <<= 32;
      x |= aSpecialFp[i].iLower;
      assert( sizeof(value)==8 );
      assert( sizeof(x)==8 );
      memcpy(&value, &x, 8);
      break;
    }
  }
  if( i>=sizeof(aSpecialFp)/sizeof(aSpecialFp[0]) &&
         Tcl_GetDoubleFromObj(interp, objv[3], &value) ){
    return TCL_ERROR;
  }
  rc = sqlite4_bind_double(pStmt, idx, value);
  if( sqlite4TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
  if( rc!=SQLITE4_OK ){
    return TCL_ERROR;
  }

  return TCL_OK;
}

/*
** Usage:   sqlite4_bind_null  STMT N
**
** Test the sqlite4_bind_null interface.  STMT is a prepared statement.
** N is the index of a wildcard in the prepared statement.  This command
** binds a NULL to the wildcard.
*/
static int test_bind_null(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int idx;
  int rc;

  if( objc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " STMT N", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;

  rc = sqlite4_bind_null(pStmt, idx);
  if( sqlite4TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
  if( rc!=SQLITE4_OK ){
    return TCL_ERROR;
  }

  return TCL_OK;
}

/*
** Usage:   sqlite4_bind_text  STMT N STRING BYTES
**
** Test the sqlite4_bind_text interface.  STMT is a prepared statement.
** N is the index of a wildcard in the prepared statement.  This command
** binds a UTF-8 string STRING to the wildcard.  The string is BYTES bytes
** long.
*/
static int test_bind_text(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int idx;
  int bytes;
  char *value;
  int rc;

  if( objc!=5 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " STMT N VALUE BYTES", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
  value = (char*)Tcl_GetByteArrayFromObj(objv[3], &bytes);
  if( Tcl_GetIntFromObj(interp, objv[4], &bytes) ) return TCL_ERROR;

  rc = sqlite4_bind_text(pStmt, idx, value, bytes, SQLITE4_TRANSIENT, 0);
  if( sqlite4TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
  if( rc!=SQLITE4_OK ){
    Tcl_AppendResult(interp, sqlite4TestErrorName(rc), 0);
    return TCL_ERROR;
  }

  return TCL_OK;
}

/*
** Usage:   sqlite4_bind_text16 ?-static? STMT N STRING BYTES
**
** Test the sqlite4_bind_text16 interface.  STMT is a prepared statement.
** N is the index of a wildcard in the prepared statement.  This command
** binds a UTF-16 string STRING to the wildcard.  The string is BYTES bytes
** long.
*/
static int test_bind_text16(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
#ifndef SQLITE4_OMIT_UTF16
  sqlite4_stmt *pStmt;
  int idx;
  int bytes;
  char *value;
  int rc;

  void (*xDel)() = (objc==6?SQLITE4_STATIC:SQLITE4_TRANSIENT);
  Tcl_Obj *oStmt    = objv[objc-4];
  Tcl_Obj *oN       = objv[objc-3];
  Tcl_Obj *oString  = objv[objc-2];
  Tcl_Obj *oBytes   = objv[objc-1];

  if( objc!=5 && objc!=6){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " STMT N VALUE BYTES", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(oStmt), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, oN, &idx) ) return TCL_ERROR;
  value = (char*)Tcl_GetByteArrayFromObj(oString, 0);
  if( Tcl_GetIntFromObj(interp, oBytes, &bytes) ) return TCL_ERROR;

  rc = sqlite4_bind_text16(pStmt, idx, (void *)value, bytes, xDel, 0);
  if( sqlite4TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
  if( rc!=SQLITE4_OK ){
    Tcl_AppendResult(interp, sqlite4TestErrorName(rc), 0);
    return TCL_ERROR;
  }

#endif /* SQLITE4_OMIT_UTF16 */
  return TCL_OK;
}

/*
** Usage:   sqlite4_bind_blob ?-static? STMT N DATA BYTES
**
** Test the sqlite4_bind_blob interface.  STMT is a prepared statement.
** N is the index of a wildcard in the prepared statement.  This command
** binds a BLOB to the wildcard.  The BLOB is BYTES bytes in size.
*/
static int test_bind_blob(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int idx;
  int bytes;
  char *value;
  int rc;
  sqlite4_destructor_type xDestructor = SQLITE4_TRANSIENT;

  if( objc!=5 && objc!=6 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " STMT N DATA BYTES", 0);
    return TCL_ERROR;
  }

  if( objc==6 ){
    xDestructor = SQLITE4_STATIC;
    objv++;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &idx) ) return TCL_ERROR;
  value = Tcl_GetString(objv[3]);
  if( Tcl_GetIntFromObj(interp, objv[4], &bytes) ) return TCL_ERROR;

  rc = sqlite4_bind_blob(pStmt, idx, value, bytes, xDestructor, 0);
  if( sqlite4TestErrCode(interp, StmtToDb(pStmt), rc) ) return TCL_ERROR;
  if( rc!=SQLITE4_OK ){
    return TCL_ERROR;
  }

  return TCL_OK;
}

/*
** Usage:   sqlite4_bind_parameter_count  STMT
**
** Return the number of wildcards in the given statement.
*/
static int test_bind_parameter_count(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
    return TCL_ERROR;
  }
  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite4_bind_parameter_count(pStmt)));
  return TCL_OK;
}

/*
** Usage:   sqlite4_bind_parameter_name  STMT  N
**
** Return the name of the Nth wildcard.  The first wildcard is 1.
** An empty string is returned if N is out of range or if the wildcard
** is nameless.
*/
static int test_bind_parameter_name(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int i;

  if( objc!=3 ){
    Tcl_WrongNumArgs(interp, 1, objv, "STMT N");
    return TCL_ERROR;
  }
  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &i) ) return TCL_ERROR;
  Tcl_SetObjResult(interp, 
     Tcl_NewStringObj(sqlite4_bind_parameter_name(pStmt,i),-1)
  );
  return TCL_OK;
}

/*
** Usage:   sqlite4_bind_parameter_index  STMT  NAME
**
** Return the index of the wildcard called NAME.  Return 0 if there is
** no such wildcard.
*/
static int test_bind_parameter_index(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;

  if( objc!=3 ){
    Tcl_WrongNumArgs(interp, 1, objv, "STMT NAME");
    return TCL_ERROR;
  }
  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  Tcl_SetObjResult(interp, 
     Tcl_NewIntObj(
       sqlite4_bind_parameter_index(pStmt,Tcl_GetString(objv[2]))
     )
  );
  return TCL_OK;
}

/*
** Usage:   sqlite4_clear_bindings STMT
**
*/
static int test_clear_bindings(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
    return TCL_ERROR;
  }
  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite4_clear_bindings(pStmt)));
  return TCL_OK;
}

/*
** Usage:   sqlite4_sleep MILLISECONDS
*/
static int test_sleep(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  int ms;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "MILLISECONDS");
    return TCL_ERROR;
  }
  if( Tcl_GetIntFromObj(interp, objv[1], &ms) ){
    return TCL_ERROR;
  }
  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite4_sleep(ms)));
  return TCL_OK;
}


/*
** Usage: sqlite4_errcode DB
**
** Return the string representation of the most recent sqlite4_* API
** error code. e.g. "SQLITE4_ERROR".
*/
static int test_errcode(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4 *db;
  int rc;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " DB", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  rc = sqlite4_errcode(db);
  Tcl_AppendResult(interp, (char *)t1ErrorName(rc), 0);
  return TCL_OK;
}

/*
** Usage:   sqlite4_errmsg DB
**
** Returns the UTF-8 representation of the error message string for the
** most recent sqlite4_* API call.
*/
static int test_errmsg(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4 *db;
  const char *zErr;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " DB", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;

  zErr = sqlite4_errmsg(db);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
  return TCL_OK;
}

/*
** Usage: sqlite4_prepare DB sql bytes ?tailvar?
**
** Compile up to <bytes> bytes of the supplied SQL string <sql> using
** database handle <DB>. The parameter <tailval> is the name of a global
** variable that is set to the unused portion of <sql> (if any). A
** STMT handle is returned.
*/
static int test_prepare(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4 *db;
  const char *zSql;
  int bytes;
  int nUsed;
  sqlite4_stmt *pStmt = 0;
  char zBuf[50];
  int rc;

  if( objc!=5 && objc!=4 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " DB sql bytes ?tailvar?", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  zSql = Tcl_GetString(objv[2]);
  if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR;

  rc = sqlite4_prepare(db, zSql, bytes, &pStmt, objc>=5 ? &nUsed : 0);
  Tcl_ResetResult(interp);
  if( sqlite4TestErrCode(interp, db, rc) ) return TCL_ERROR;
  if( objc>=5 ){
    const char *zTail = &zSql[nUsed];
    int nTail = -1;
    if( bytes>=0 ){
      nTail = (bytes - nUsed);
      if( nTail>strlen(zTail) ) nTail = strlen(zTail);
    }
    Tcl_ObjSetVar2(interp, objv[4], 0, Tcl_NewStringObj(zTail, nTail), 0);
  }
  if( rc!=SQLITE4_OK ){
    assert( pStmt==0 );
    sprintf(zBuf, "(%d) ", rc);
    Tcl_AppendResult(interp, zBuf, sqlite4_errmsg(db), 0);
    return TCL_ERROR;
  }

  if( pStmt ){
    if( sqlite4TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;
    Tcl_AppendResult(interp, zBuf, 0);
  }
  return TCL_OK;
}

/*
** Usage: sqlite4_prepare_tkt3134 DB
**
** Generate a prepared statement for a zero-byte string as a test
** for ticket #3134.  The string should be preceeded by a zero byte.
*/
static int test_prepare_tkt3134(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4 *db;
  static const char zSql[] = "\000SELECT 1";
  sqlite4_stmt *pStmt = 0;
  char zBuf[50];
  int rc;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " DB sql bytes tailvar", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  rc = sqlite4_prepare(db, &zSql[1], 0, &pStmt, 0);
  assert(rc==SQLITE4_OK || pStmt==0);
  if( sqlite4TestErrCode(interp, db, rc) ) return TCL_ERROR;
  if( rc!=SQLITE4_OK ){
    assert( pStmt==0 );
    sprintf(zBuf, "(%d) ", rc);
    Tcl_AppendResult(interp, zBuf, sqlite4_errmsg(db), 0);
    return TCL_ERROR;
  }

  if( pStmt ){
    if( sqlite4TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR;
    Tcl_AppendResult(interp, zBuf, 0);
  }
  return TCL_OK;
}

/*
** Usage: sqlite4_open filename ?options-list?
*/
static int test_open(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  const char *zFilename;
  sqlite4 *db;
  int rc;
  char zBuf[100];

  if( objc!=3 && objc!=2 && objc!=1 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " filename options-list", 0);
    return TCL_ERROR;
  }

  zFilename = objc>1 ? Tcl_GetString(objv[1]) : 0;
  rc = sqlite4_open(0, zFilename, &db, 0);
  
  if( rc!=SQLITE4_OK 
   || sqlite4TestMakePointerStr(interp, zBuf, db) 
  ){
    return TCL_ERROR;
  }
  Tcl_AppendResult(interp, zBuf, 0);
  return TCL_OK;
}

/*
** Usage: sqlite4_open_v2 FILENAME FLAGS VFS
*/
static int test_open_v2(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  const char *zFilename;
  const char *zVfs;
  int flags = 0;
  sqlite4 *db;
  int rc;
  char zBuf[100];

  int nFlag;
  Tcl_Obj **apFlag;
  int i;

  if( objc!=4 ){
    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME FLAGS VFS");
    return TCL_ERROR;
  }
  zFilename = Tcl_GetString(objv[1]);
  zVfs = Tcl_GetString(objv[3]);
  if( zVfs[0]==0x00 ) zVfs = 0;

  rc = Tcl_ListObjGetElements(interp, objv[2], &nFlag, &apFlag);
  if( rc!=TCL_OK ) return rc;
  for(i=0; i<nFlag; i++){
    int iFlag;
    struct OpenFlag {
      const char *zFlag;
      int flag;
    } aFlag[] = {
      { "SQLITE4_OPEN_READONLY", SQLITE4_OPEN_READONLY },
      { "SQLITE4_OPEN_READWRITE", SQLITE4_OPEN_READWRITE },
      { "SQLITE4_OPEN_CREATE", SQLITE4_OPEN_CREATE },
      { 0, 0 }
    };
    rc = Tcl_GetIndexFromObjStruct(interp, apFlag[i], aFlag, sizeof(aFlag[0]), 
        "flag", 0, &iFlag
    );
    if( rc!=TCL_OK ) return rc;
    flags |= aFlag[iFlag].flag;
  }

  rc = sqlite4_open(0, zFilename, &db, 0);
  if( sqlite4TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR;
  Tcl_AppendResult(interp, zBuf, 0);
  return TCL_OK;
}

/*
** Usage: sqlite4_step STMT
**
** Advance the statement to the next row.
*/
static int test_step(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int rc;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " STMT", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  rc = sqlite4_step(pStmt);

  /* if( rc!=SQLITE4_DONE && rc!=SQLITE4_ROW ) return TCL_ERROR; */
  Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0);
  return TCL_OK;
}

static int test_stmt_sql(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  Tcl_SetResult(interp, (char *)sqlite4_stmt_sql(pStmt), TCL_VOLATILE);
  return TCL_OK;
}

/*
** Usage: sqlite4_column_count STMT 
**
** Return the number of columns returned by the sql statement STMT.
*/
static int test_column_count(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " STMT column", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;

  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite4_column_count(pStmt)));
  return TCL_OK;
}

/*
** Usage: sqlite4_column_type STMT column
**
** Return the type of the data in column 'column' of the current row.
*/
static int test_column_type(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int col;
  int tp;

  if( objc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " STMT column", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;

  tp = sqlite4_column_type(pStmt, col);
  switch( tp ){
    case SQLITE4_INTEGER: 
      Tcl_SetResult(interp, "INTEGER", TCL_STATIC); 
      break;
    case SQLITE4_NULL:
      Tcl_SetResult(interp, "NULL", TCL_STATIC); 
      break;
    case SQLITE4_FLOAT:
      Tcl_SetResult(interp, "FLOAT", TCL_STATIC); 
      break;
    case SQLITE4_TEXT:
      Tcl_SetResult(interp, "TEXT", TCL_STATIC); 
      break;
    case SQLITE4_BLOB:
      Tcl_SetResult(interp, "BLOB", TCL_STATIC); 
      break;
    default:
      assert(0);
  }

  return TCL_OK;
}

/*
** Usage: sqlite4_column_int64 STMT column
**
** Return the data in column 'column' of the current row cast as an
** wide (64-bit) integer.
*/
static int test_column_int64(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int col;
  i64 iVal;

  if( objc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " STMT column", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;

  iVal = sqlite4_column_int64(pStmt, col);
  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(iVal));
  return TCL_OK;
}

/*
** Usage: sqlite4_column_blob STMT column
*/
static int test_column_blob(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int col;

  int len;
  const void *pBlob;

  if( objc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " STMT column", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;

  pBlob = sqlite4_column_blob(pStmt, col, &len);
  Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pBlob, len));
  return TCL_OK;
}

/*
** Usage: sqlite4_column_double STMT column
**
** Return the data in column 'column' of the current row cast as a double.
*/
static int test_column_double(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int col;
  double rVal;

  if( objc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " STMT column", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;

  rVal = sqlite4_column_double(pStmt, col);
  Tcl_SetObjResult(interp, Tcl_NewDoubleObj(rVal));
  return TCL_OK;
}

/*
** Usage: sqlite4_data_count STMT 
**
** Return the number of columns returned by the sql statement STMT.
*/
static int test_data_count(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;

  if( objc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " STMT column", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;

  Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite4_data_count(pStmt)));
  return TCL_OK;
}

/*
** Usage: sqlite4_column_text STMT column
**
** Usage: sqlite4_column_decltype STMT column
**
** Usage: sqlite4_column_name STMT column
*/
static int test_stmt_utf8(
  void * clientData,        /* Pointer to SQLite API function to be invoke */
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int col;
  const char *(*xFunc)(sqlite4_stmt*, int, int *);
  const char *zRet;

  xFunc = (const char *(*)(sqlite4_stmt*, int, int*))clientData;
  if( objc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " STMT column", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;
  zRet = xFunc(pStmt, col, 0);
  if( zRet ){
    Tcl_SetResult(interp, (char *)zRet, 0);
  }
  return TCL_OK;
}

/*
** Usage: sqlite4_column_int STMT column
*/
static int test_stmt_int(
  void * clientData,    /* Pointer to SQLite API function to be invoked */
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_stmt *pStmt;
  int col;
  int (*xFunc)(sqlite4_stmt*, int);

  xFunc = (int (*)(sqlite4_stmt*, int))clientData;
  if( objc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", 
       Tcl_GetString(objv[0]), " STMT column", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;

  Tcl_SetObjResult(interp, Tcl_NewIntObj(xFunc(pStmt, col)));
  return TCL_OK;
}

/*
** Usage:  sqlite_set_magic  DB  MAGIC-NUMBER
**
** Set the db->magic value.  This is used to test error recovery logic.
*/
static int sqlite_set_magic(
  void * clientData,
  Tcl_Interp *interp,
  int argc,
  char **argv
){
  sqlite4 *db;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
         " DB MAGIC", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  if( strcmp(argv[2], "SQLITE4_MAGIC_OPEN")==0 ){
    db->magic = SQLITE4_MAGIC_OPEN;
  }else if( strcmp(argv[2], "SQLITE4_MAGIC_CLOSED")==0 ){
    db->magic = SQLITE4_MAGIC_CLOSED;
  }else if( strcmp(argv[2], "SQLITE4_MAGIC_BUSY")==0 ){
    db->magic = SQLITE4_MAGIC_BUSY;
  }else if( strcmp(argv[2], "SQLITE4_MAGIC_ERROR")==0 ){
    db->magic = SQLITE4_MAGIC_ERROR;
  }else if( Tcl_GetInt(interp, argv[2], (int*)&db->magic) ){
    return TCL_ERROR;
  }
  return TCL_OK;
}

/*
** Usage:  sqlite4_interrupt  DB 
**
** Trigger an interrupt on DB
*/
static int test_interrupt(
  void * clientData,
  Tcl_Interp *interp,
  int argc,
  char **argv
){
  sqlite4 *db;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " DB", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  sqlite4_interrupt(db);
  return TCL_OK;
}

static u8 *sqlite4_stack_baseline = 0;

/*
** Fill the stack with a known bitpattern.
*/
static void prepStack(void){
  int i;
  u32 bigBuf[65536];
  for(i=0; i<sizeof(bigBuf)/sizeof(bigBuf[0]); i++) bigBuf[i] = 0xdeadbeef;
  sqlite4_stack_baseline = (u8*)&bigBuf[65536];
}

/*
** Get the current stack depth.  Used for debugging only.
*/
u64 sqlite4StackDepth(void){
  u8 x;
  return (u64)(sqlite4_stack_baseline - &x);
}

/*
** Usage:  sqlite4_stack_used DB SQL
**
** Try to measure the amount of stack space used by a call to sqlite4_exec
*/
static int test_stack_used(
  void * clientData,
  Tcl_Interp *interp,
  int argc,
  char **argv
){
  sqlite4 *db;
  int i;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
        " DB SQL", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  prepStack();
  (void)sqlite4_exec(db, argv[2], 0, 0);
  for(i=65535; i>=0 && ((u32*)sqlite4_stack_baseline)[-i]==0xdeadbeef; i--){}
  Tcl_SetObjResult(interp, Tcl_NewIntObj(i*4));
  return TCL_OK;
}

/*
** Usage: sqlite_delete_function DB function-name
**
** Delete the user function 'function-name' from database handle DB. It
** is assumed that the user function was created as UTF8, any number of
** arguments (the way the TCL interface does it).
*/
static int delete_function(
  void * clientData,
  Tcl_Interp *interp,
  int argc,
  char **argv
){
  int rc;
  sqlite4 *db;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
        " DB function-name", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  rc = sqlite4_create_function(db, argv[2], -1, 0, 0, 0, 0, 0);
  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
  return TCL_OK;
}

/*
** Usage: sqlite_delete_collation DB collation-name
**
** Delete the collation sequence 'collation-name' from database handle DB. 
*/
static int delete_collation(
  void * clientData,
  Tcl_Interp *interp,
  int argc,
  char **argv
){
  int rc;
  sqlite4 *db;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
        " DB function-name", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  rc = sqlite4_create_collation(db, argv[2], 0, 0, 0, 0);
  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
  return TCL_OK;
}

/*
** Usage: sqlite4_db_transaction_status DB
**
** Return true if the database DB currently has an open transaction.
** Return false if not.
*/
static int db_transaction_status(
  void * clientData,
  Tcl_Interp *interp,
  int argc,
  char **argv
){
  char zBuf[30];
  sqlite4 *db;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 
        " DB", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR;
  sprintf(zBuf, "%d", sqlite4_db_transaction_status(db));
  Tcl_AppendResult(interp, zBuf, 0);
  return TCL_OK;
}

/*
** Usage:  tcl_variable_type VARIABLENAME
**
** Return the name of the internal representation for the
** value of the given variable.
*/
static int tcl_variable_type(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  Tcl_Obj *pVar;
  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "VARIABLE");
    return TCL_ERROR;
  }
  pVar = Tcl_GetVar2Ex(interp, Tcl_GetString(objv[1]), 0, TCL_LEAVE_ERR_MSG);
  if( pVar==0 ) return TCL_ERROR;
  if( pVar->typePtr ){
    Tcl_SetObjResult(interp, Tcl_NewStringObj(pVar->typePtr->name, -1));
  }
  return TCL_OK;
}

/*
** Usage:  sqlite4_db_release_memory DB
**
** Attempt to release memory currently held by database DB.  Return the
** result code (which in the current implementation is always zero).
*/
static int test_db_release_memory(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4 *db;
  int rc;
  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB");
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  rc = sqlite4_db_release_memory(db);
  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
  return TCL_OK;
}


/*
** tclcmd:   working_64bit_int
**
** Some TCL builds (ex: cygwin) do not support 64-bit integers.  This
** leads to a number of test failures.  The present command checks the
** TCL build to see whether or not it supports 64-bit integers.  It
** returns TRUE if it does and FALSE if not.
**
** This command is used to warn users that their TCL build is defective
** and that the errors they are seeing in the test scripts might be
** a result of their defective TCL rather than problems in SQLite.
*/
static int working_64bit_int(
  ClientData clientData, /* Pointer to sqlite4_enable_XXX function */
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int objc,              /* Number of arguments */
  Tcl_Obj *CONST objv[]  /* Command arguments */
){
  Tcl_Obj *pTestObj;
  int working = 0;

  pTestObj = Tcl_NewWideIntObj(1000000*(i64)1234567890);
  working = strcmp(Tcl_GetString(pTestObj), "1234567890000000")==0;
  Tcl_DecrRefCount(pTestObj);
  Tcl_SetObjResult(interp, Tcl_NewBooleanObj(working));
  return TCL_OK;
}


/*
** tclcmd:   sqlite4_limit DB ID VALUE
**
** This TCL command runs the sqlite4_limit interface and
** verifies correct operation of the same.
*/
static int test_limit(
  ClientData clientData, /* Pointer to sqlite4_enable_XXX function */
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int objc,              /* Number of arguments */
  Tcl_Obj *CONST objv[]  /* Command arguments */
){
  sqlite4 *db;
  int rc;
  static const struct {
     char *zName;
     int id;
  } aId[] = {
    { "SQLITE4_LIMIT_LENGTH",              SQLITE4_LIMIT_LENGTH               },
    { "SQLITE4_LIMIT_SQL_LENGTH",          SQLITE4_LIMIT_SQL_LENGTH           },
    { "SQLITE4_LIMIT_COLUMN",              SQLITE4_LIMIT_COLUMN               },
    { "SQLITE4_LIMIT_EXPR_DEPTH",          SQLITE4_LIMIT_EXPR_DEPTH           },
    { "SQLITE4_LIMIT_COMPOUND_SELECT",     SQLITE4_LIMIT_COMPOUND_SELECT      },
    { "SQLITE4_LIMIT_VDBE_OP",             SQLITE4_LIMIT_VDBE_OP              },
    { "SQLITE4_LIMIT_FUNCTION_ARG",        SQLITE4_LIMIT_FUNCTION_ARG         },
    { "SQLITE4_LIMIT_ATTACHED",            SQLITE4_LIMIT_ATTACHED             },
    { "SQLITE4_LIMIT_LIKE_PATTERN_LENGTH", SQLITE4_LIMIT_LIKE_PATTERN_LENGTH  },
    { "SQLITE4_LIMIT_VARIABLE_NUMBER",     SQLITE4_LIMIT_VARIABLE_NUMBER      },
    { "SQLITE4_LIMIT_TRIGGER_DEPTH",       SQLITE4_LIMIT_TRIGGER_DEPTH        },
    
    /* Out of range test cases */
    { "SQLITE4_LIMIT_TOOSMALL",            -1,                               },
    { "SQLITE4_LIMIT_TOOBIG",              SQLITE4_LIMIT_TRIGGER_DEPTH+1      },
  };
  int i, id;
  int val;
  const char *zId;

  if( objc!=4 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
        Tcl_GetStringFromObj(objv[0], 0), " DB ID VALUE", 0);
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  zId = Tcl_GetString(objv[2]);
  for(i=0; i<sizeof(aId)/sizeof(aId[0]); i++){
    if( strcmp(zId, aId[i].zName)==0 ){
      id = aId[i].id;
      break;
    }
  }
  if( i>=sizeof(aId)/sizeof(aId[0]) ){
    Tcl_AppendResult(interp, "unknown limit type: ", zId, (char*)0);
    return TCL_ERROR;
  }
  if( Tcl_GetIntFromObj(interp, objv[3], &val) ) return TCL_ERROR;
  rc = sqlite4_limit(db, id, val);
  Tcl_SetObjResult(interp, Tcl_NewIntObj(rc));
  return TCL_OK;  
}


#ifdef SQLITE4_ENABLE_UNLOCK_NOTIFY
static void test_unlock_notify_cb(void **aArg, int nArg){
  int ii;
  for(ii=0; ii<nArg; ii++){
    Tcl_EvalEx((Tcl_Interp *)aArg[ii], "unlock_notify", -1, TCL_EVAL_GLOBAL);
  }
}
#endif /* SQLITE4_ENABLE_UNLOCK_NOTIFY */

/*
** tclcmd:  sqlite4_unlock_notify db
*/
#ifdef SQLITE4_ENABLE_UNLOCK_NOTIFY
static int test_unlock_notify(
  ClientData clientData, /* Unused */
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int objc,              /* Number of arguments */
  Tcl_Obj *CONST objv[]  /* Command arguments */
){
  sqlite4 *db;
  int rc;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB");
    return TCL_ERROR;
  }

  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ){
    return TCL_ERROR;
  }
  rc = sqlite4_unlock_notify(db, test_unlock_notify_cb, (void *)interp);
  Tcl_SetResult(interp, (char *)t1ErrorName(rc), TCL_STATIC);
  return TCL_OK;
}
#endif


/*
** tclcmd:  test_sqlite4_log ?SCRIPT?
*/
static struct LogCallback {
  Tcl_Interp *pInterp;
  Tcl_Obj *pObj;
} logcallback = {0, 0};
static void xLogcallback(void *unused, int err, char *zMsg){
  Tcl_Obj *pNew = Tcl_DuplicateObj(logcallback.pObj);
  Tcl_IncrRefCount(pNew);
  Tcl_ListObjAppendElement(
      0, pNew, Tcl_NewStringObj(sqlite4TestErrorName(err), -1)
  );
  Tcl_ListObjAppendElement(0, pNew, Tcl_NewStringObj(zMsg, -1));
  Tcl_EvalObjEx(logcallback.pInterp, pNew, TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
  Tcl_DecrRefCount(pNew);
}
static int test_sqlite4_log(
  ClientData clientData,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int objc,              /* Number of arguments */
  Tcl_Obj *CONST objv[]  /* Command arguments */
){
  if( objc>2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "SCRIPT");
    return TCL_ERROR;
  }
  if( logcallback.pObj ){
    Tcl_DecrRefCount(logcallback.pObj);
    logcallback.pObj = 0;
    logcallback.pInterp = 0;
    sqlite4_env_config(0, SQLITE4_ENVCONFIG_LOG, 0, 0);
  }
  if( objc>1 ){
    logcallback.pObj = objv[1];
    Tcl_IncrRefCount(logcallback.pObj);
    logcallback.pInterp = interp;
    sqlite4_env_config(0, SQLITE4_ENVCONFIG_LOG, xLogcallback, 0);
  }
  return TCL_OK;
}

/*
**     tcl_objproc COMMANDNAME ARGS...
**
** Run a TCL command using its objProc interface.  Throw an error if
** the command has no objProc interface.
*/
static int runAsObjProc(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  Tcl_CmdInfo cmdInfo;
  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "COMMAND ...");
    return TCL_ERROR;
  }
  if( !Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &cmdInfo) ){
    Tcl_AppendResult(interp, "command not found: ",
           Tcl_GetString(objv[1]), (char*)0);
    return TCL_ERROR;
  }
  if( cmdInfo.objProc==0 ){
    Tcl_AppendResult(interp, "command has no objProc: ",
           Tcl_GetString(objv[1]), (char*)0);
    return TCL_ERROR;
  }
  return cmdInfo.objProc(cmdInfo.objClientData, interp, objc-1, objv+1);
}

#ifndef SQLITE4_OMIT_EXPLAIN
/*
** WARNING: The following function, printExplainQueryPlan() is an exact
** copy of example code from eqp.in (eqp.html). If this code is modified,
** then the documentation copy needs to be modified as well.
*/
/*
** Argument pStmt is a prepared SQL statement. This function compiles
** an EXPLAIN QUERY PLAN command to report on the prepared statement,
** and prints the report to stdout using printf().
*/
int printExplainQueryPlan(sqlite4_stmt *pStmt){
  const char *zSql;               /* Input SQL */
  char *zExplain;                 /* SQL with EXPLAIN QUERY PLAN prepended */
  sqlite4_stmt *pExplain;         /* Compiled EXPLAIN QUERY PLAN command */
  int rc;                         /* Return code from sqlite4_prepare() */

  zSql = sqlite4_stmt_sql(pStmt);
  if( zSql==0 ) return SQLITE4_ERROR;

  zExplain = sqlite4_mprintf(0, "EXPLAIN QUERY PLAN %s", zSql);
  if( zExplain==0 ) return SQLITE4_NOMEM;

  rc = sqlite4_prepare(sqlite4_db_handle(pStmt), zExplain, -1, &pExplain, 0);
  sqlite4_free(0, zExplain);
  if( rc!=SQLITE4_OK ) return rc;

  while( SQLITE4_ROW==sqlite4_step(pExplain) ){
    int iSelectid = sqlite4_column_int(pExplain, 0);
    int iOrder = sqlite4_column_int(pExplain, 1);
    int iFrom = sqlite4_column_int(pExplain, 2);
    const char *zDetail = (const char *)sqlite4_column_text(pExplain, 3, 0);

    printf("%d %d %d %s\n", iSelectid, iOrder, iFrom, zDetail);
  }

  return sqlite4_finalize(pExplain);
}

static int test_print_eqp(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  int rc;
  sqlite4_stmt *pStmt;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "STMT");
    return TCL_ERROR;
  }
  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  rc = printExplainQueryPlan(pStmt);
  /* This is needed on Windows so that a test case using this 
  ** function can open a read pipe and get the output of
  ** printExplainQueryPlan() immediately.
  */
  fflush(stdout);
  Tcl_SetResult(interp, (char *)t1ErrorName(rc), 0);
  return TCL_OK;
}
#endif /* SQLITE4_OMIT_EXPLAIN */

static int test_prng_state_get(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_int64 iVal;
  if( objc!=0 ){
    Tcl_WrongNumArgs(interp, 1, objv, "");
    return TCL_ERROR;
  }
  sqlite4_test_control(SQLITE4_TESTCTRL_PRNG_SET, (sqlite4_env *)0, &iVal);
  Tcl_SetObjResult(interp, Tcl_NewWideIntObj(iVal));
  return TCL_OK;
}

static int test_prng_state_set(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  sqlite4_int64 iVal;
  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "VALUE");
    return TCL_ERROR;
  }
  if( Tcl_GetWideIntFromObj(interp, objv[1], &iVal) ){
    return TCL_ERROR;
  }
  sqlite4_test_control(SQLITE4_TESTCTRL_PRNG_SET, (sqlite4_env *)0, iVal);
  return TCL_OK;
}

/*
** sqlite4_test_control VERB ARGS...
*/
static int test_test_control(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  struct Verb {
    const char *zName;
    int i;
  } aVerb[] = {
    { "SQLITE4_TESTCTRL_LOCALTIME_FAULT", SQLITE4_TESTCTRL_LOCALTIME_FAULT }, 
  };
  int iVerb;
  int iFlag;
  int rc;

  if( objc<2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "VERB ARGS...");
    return TCL_ERROR;
  }

  rc = Tcl_GetIndexFromObjStruct(
      interp, objv[1], aVerb, sizeof(aVerb[0]), "VERB", 0, &iVerb
  );
  if( rc!=TCL_OK ) return rc;

  iFlag = aVerb[iVerb].i;
  switch( iFlag ){
    case SQLITE4_TESTCTRL_LOCALTIME_FAULT: {
      int val;
      if( objc!=3 ){
        Tcl_WrongNumArgs(interp, 2, objv, "ONOFF");
        return TCL_ERROR;
      }
      if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR;
      sqlite4_test_control(SQLITE4_TESTCTRL_LOCALTIME_FAULT, val);
      break;
    }
  }

  Tcl_ResetResult(interp);
  return TCL_OK;
}

#if SQLITE4_OS_WIN
/*
** Information passed from the main thread into the windows file locker
** background thread.
*/
struct win32FileLocker {
  char *evName;       /* Name of event to signal thread startup */
  HANDLE h;           /* Handle of the file to be locked */
  int delay1;         /* Delay before locking */
  int delay2;         /* Delay before unlocking */
  int ok;             /* Finished ok */
  int err;            /* True if an error occurs */
};
#endif


#if SQLITE4_OS_WIN
/*
** The background thread that does file locking.
*/
static void win32_file_locker(void *pAppData){
  struct win32FileLocker *p = (struct win32FileLocker*)pAppData;
  if( p->evName ){
    HANDLE ev = OpenEvent(EVENT_MODIFY_STATE, FALSE, p->evName);
    if ( ev ){
      SetEvent(ev);
      CloseHandle(ev);
    }
  }
  if( p->delay1 ) Sleep(p->delay1);
  if( LockFile(p->h, 0, 0, 100000000, 0) ){
    Sleep(p->delay2);
    UnlockFile(p->h, 0, 0, 100000000, 0);
    p->ok = 1;
  }else{
    p->err = 1;
  }
  CloseHandle(p->h);
  p->h = 0;
  p->delay1 = 0;
  p->delay2 = 0;
}
#endif

#if SQLITE4_OS_WIN
/*
**      lock_win32_file FILENAME DELAY1 DELAY2
**
** Get an exclusive manditory lock on file for DELAY2 milliseconds.
** Wait DELAY1 milliseconds before acquiring the lock.
*/
static int win32_file_lock(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  static struct win32FileLocker x = { "win32_file_lock", 0, 0, 0, 0, 0 };
  const char *zFilename;
  char zBuf[200];
  int retry = 0;
  HANDLE ev;
  DWORD wResult;
  
  if( objc!=4 && objc!=1 ){
    Tcl_WrongNumArgs(interp, 1, objv, "FILENAME DELAY1 DELAY2");
    return TCL_ERROR;
  }
  if( objc==1 ){
    sqlite4_snprintf(zBuf, sizeof(zBuf), "%d %d %d %d %d",
                     x.ok, x.err, x.delay1, x.delay2, x.h);
    Tcl_AppendResult(interp, zBuf, (char*)0);
    return TCL_OK;
  }
  while( x.h && retry<30 ){
    retry++;
    Sleep(100);
  }
  if( x.h ){
    Tcl_AppendResult(interp, "busy", (char*)0);
    return TCL_ERROR;
  }
  if( Tcl_GetIntFromObj(interp, objv[2], &x.delay1) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[3], &x.delay2) ) return TCL_ERROR;
  zFilename = Tcl_GetString(objv[1]);
  x.h = CreateFile(zFilename, GENERIC_READ|GENERIC_WRITE,
              FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_ALWAYS,
              FILE_ATTRIBUTE_NORMAL, 0);
  if( !x.h ){
    Tcl_AppendResult(interp, "cannot open file: ", zFilename, (char*)0);
    return TCL_ERROR;
  }
  ev = CreateEvent(NULL, TRUE, FALSE, x.evName);
  if ( !ev ){
    Tcl_AppendResult(interp, "cannot create event: ", x.evName, (char*)0);
    return TCL_ERROR;
  }
  _beginthread(win32_file_locker, 0, (void*)&x);
  Sleep(0);
  if ( (wResult = WaitForSingleObject(ev, 10000))!=WAIT_OBJECT_0 ){
    sqlite4_snprintf(zBuf, sizeof(zBuf), "0x%x", wResult);
    Tcl_AppendResult(interp, "wait failed: ", zBuf, (char*)0);
    CloseHandle(ev);
    return TCL_ERROR;
  }
  CloseHandle(ev);
  return TCL_OK;
}
#endif


/*
**      optimization_control DB OPT BOOLEAN
**
** Enable or disable query optimizations using the sqlite4_test_control()
** interface.  Disable if BOOLEAN is false and enable if BOOLEAN is true.
** OPT is the name of the optimization to be disabled.
*/
static int optimization_control(
  void * clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
  int i;
  sqlite4 *db;
  const char *zOpt;
  int onoff;
  int mask;
  static const struct {
    const char *zOptName;
    int mask;
  } aOpt[] = {
    { "all",              SQLITE4_OptMask        },
    { "query-flattener",  SQLITE4_QueryFlattener },
    { "column-cache",     SQLITE4_ColumnCache    },
    { "index-sort",       SQLITE4_IndexSort      },
    { "index-search",     SQLITE4_IndexSearch    },
    { "index-cover",      SQLITE4_IndexCover     },
    { "groupby-order",    SQLITE4_GroupByOrder   },
    { "factor-constants", SQLITE4_FactorOutConst },
    { "real-as-int",      SQLITE4_IdxRealAsInt   },
  };

  if( objc!=4 ){
    Tcl_WrongNumArgs(interp, 1, objv, "DB OPT BOOLEAN");
    return TCL_ERROR;
  }
  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
  if( Tcl_GetBooleanFromObj(interp, objv[3], &onoff) ) return TCL_ERROR;
  zOpt = Tcl_GetString(objv[2]);
  for(i=0; i<sizeof(aOpt)/sizeof(aOpt[0]); i++){
    if( strcmp(zOpt, aOpt[i].zOptName)==0 ){
      mask = aOpt[i].mask;
      break;
    }
  }
  if( onoff ) mask = ~mask;
  if( i>=sizeof(aOpt)/sizeof(aOpt[0]) ){
    Tcl_AppendResult(interp, "unknown optimization - should be one of:",
                     (char*)0);
    for(i=0; i<sizeof(aOpt)/sizeof(aOpt[0]); i++){
      Tcl_AppendResult(interp, " ", aOpt[i].zOptName);
    }
    return TCL_ERROR;
  }
  sqlite4_test_control(SQLITE4_TESTCTRL_OPTIMIZATIONS, db, mask);
  return TCL_OK;
}

/*
** Usage: sqlite4_column_text STMT column
**
** Usage: sqlite4_column_decltype STMT column
**
** Usage: sqlite4_column_name STMT column
*/
static int test_stmt_utf16(
  void * clientData,     /* Pointer to SQLite API function to be invoked */
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *CONST objv[]
){
#ifndef SQLITE4_OMIT_UTF16
  sqlite4_stmt *pStmt;
  int col;
  Tcl_Obj *pRet;
  const void *zName16;
  const void *(*xFunc)(sqlite4_stmt*, int, int*);
  int dummy;

  xFunc = (const void *(*)(sqlite4_stmt*, int, int*))clientData;
  if( objc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"",
       Tcl_GetString(objv[0]), " STMT column", 0);
    return TCL_ERROR;
  }

  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
  if( Tcl_GetIntFromObj(interp, objv[2], &col) ) return TCL_ERROR;

  zName16 = xFunc(pStmt, col, &dummy);
  if( zName16 ){
    int n;
    const char *z = zName16;
    for(n=0; z[n] || z[n+1]; n+=2){}
    pRet = Tcl_NewByteArrayObj(zName16, n+2);
    Tcl_SetObjResult(interp, pRet);
  }
#endif /* SQLITE4_OMIT_UTF16 */

  return TCL_OK;
}

void sqlite4TestInit(Tcl_Interp *interp){
  Sqlitetest_auth_init(interp);
  Sqlitetest_num_init(interp);
  Sqlitetest_func_Init(interp);
  SqlitetestBt_Init(interp);
}

/*
** Register commands with the TCL interpreter.
*/
int Sqlitetest1_Init(Tcl_Interp *interp){
  extern int sqlite4_search_count;
  extern int sqlite4_found_count;
  extern int sqlite4_interrupt_count;
  extern int sqlite4_sort_count;
  extern int sqlite4_current_time;
#if SQLITE4_OS_UNIX && defined(__APPLE__) && SQLITE4_ENABLE_LOCKING_STYLE
  extern int sqlite4_hostid_num;
#endif
  extern int sqlite4_max_blobsize;
  static struct {
     char *zName;
     Tcl_CmdProc *xProc;
  } aCmd[] = {
     { "db_enter",                      (Tcl_CmdProc*)db_enter               },
     { "db_leave",                      (Tcl_CmdProc*)db_leave               },
     { "sqlite4_mprintf_int",           (Tcl_CmdProc*)sqlite4_mprintf_int    },
     { "sqlite4_mprintf_int64",         (Tcl_CmdProc*)sqlite4_mprintf_int64  },
     { "sqlite4_mprintf_long",          (Tcl_CmdProc*)sqlite4_mprintf_long   },
     { "sqlite4_mprintf_str",           (Tcl_CmdProc*)sqlite4_mprintf_str    },
     { "sqlite4_snprintf_str",          (Tcl_CmdProc*)sqlite4_snprintf_str   },
     { "sqlite4_mprintf_stronly",       (Tcl_CmdProc*)sqlite4_mprintf_stronly},
     { "sqlite4_mprintf_double",        (Tcl_CmdProc*)sqlite4_mprintf_double },
     { "sqlite4_mprintf_scaled",        (Tcl_CmdProc*)sqlite4_mprintf_scaled },
     { "sqlite4_mprintf_hexdouble",   (Tcl_CmdProc*)sqlite4_mprintf_hexdouble},
     { "sqlite4_mprintf_z_test",        (Tcl_CmdProc*)test_mprintf_z        },
     { "sqlite4_mprintf_n_test",        (Tcl_CmdProc*)test_mprintf_n        },
     { "sqlite4_snprintf_int",          (Tcl_CmdProc*)test_snprintf_int     },
     { "sqlite4_exec_printf",           (Tcl_CmdProc*)test_exec_printf      },
     { "sqlite4_exec_hex",              (Tcl_CmdProc*)test_exec_hex         },
     { "sqlite4_exec",                  (Tcl_CmdProc*)test_exec             },
     { "sqlite4_exec_nr",               (Tcl_CmdProc*)test_exec_nr          },
     { "sqlite4_close",                 (Tcl_CmdProc*)sqlite_test_close     },
     { "sqlite4_create_function",       (Tcl_CmdProc*)test_create_function  },
     { "sqlite4_create_aggregate",      (Tcl_CmdProc*)test_create_aggregate },
     { "sqlite_register_test_function", (Tcl_CmdProc*)test_register_func    },
     { "sqlite_abort",                  (Tcl_CmdProc*)sqlite_abort          },
     { "sqlite_bind",                   (Tcl_CmdProc*)test_bind             },
     { "breakpoint",                    (Tcl_CmdProc*)test_breakpoint       },
     { "sqlite4_key",                   (Tcl_CmdProc*)test_key              },
     { "sqlite4_rekey",                 (Tcl_CmdProc*)test_rekey            },
     { "sqlite_set_magic",              (Tcl_CmdProc*)sqlite_set_magic      },
     { "sqlite4_interrupt",             (Tcl_CmdProc*)test_interrupt        },
     { "sqlite_delete_function",        (Tcl_CmdProc*)delete_function       },
     { "sqlite_delete_collation",       (Tcl_CmdProc*)delete_collation      },
     { "sqlite4_db_transaction_status", (Tcl_CmdProc*)db_transaction_status },
     { "sqlite4_stack_used",            (Tcl_CmdProc*)test_stack_used       },
     { "printf",                        (Tcl_CmdProc*)test_printf           },
     { "sqlite4IoTrace",                (Tcl_CmdProc*)test_io_trace         },
  };
  static struct {
     char *zName;
     Tcl_ObjCmdProc *xProc;
     void *clientData;
  } aObjCmd[] = {
     { "sqlite4_connection_pointer",    get_sqlite_pointer, 0 },
     { "sqlite4_bind_int",              test_bind_int,      0 },
     { "sqlite4_bind_int64",            test_bind_int64,    0 },
     { "sqlite4_bind_double",           test_bind_double,   0 },
     { "sqlite4_bind_null",             test_bind_null     ,0 },
     { "sqlite4_bind_text",             test_bind_text     ,0 },
     { "sqlite4_bind_text16",           test_bind_text16   ,0 },
     { "sqlite4_bind_blob",             test_bind_blob     ,0 },
     { "sqlite4_bind_parameter_count",  test_bind_parameter_count, 0},
     { "sqlite4_bind_parameter_name",   test_bind_parameter_name,  0},
     { "sqlite4_bind_parameter_index",  test_bind_parameter_index, 0},
     { "sqlite4_clear_bindings",        test_clear_bindings, 0},
     { "sqlite4_sleep",                 test_sleep,          0},
     { "sqlite4_errcode",               test_errcode       ,0 },
     { "sqlite4_errmsg",                test_errmsg        ,0 },
     { "sqlite4_open",                  test_open          ,0 },
     { "sqlite4_open_v2",               test_open_v2       ,0 },

     { "sqlite4_prepare",               test_prepare       ,0 },
     { "sqlite4_prepare_tkt3134",       test_prepare_tkt3134, 0},
     { "sqlite4_finalize",              test_finalize      ,0 },
     { "sqlite4_stmt_status",           test_stmt_status   ,0 },
     { "sqlite4_reset",                 test_reset         ,0 },
     { "sqlite4_changes",               test_changes       ,0 },
     { "sqlite4_step",                  test_step          ,0 },
     { "sqlite4_stmt_sql",              test_stmt_sql      ,0 },
     { "sqlite4_next_stmt",             test_next_stmt     ,0 },
     { "sqlite4_stmt_readonly",         test_stmt_readonly ,0 },
     { "sqlite4_stmt_busy",             test_stmt_busy     ,0 },
     { "uses_stmt_journal",             uses_stmt_journal ,0 },

     { "sqlite4_db_release_memory",     test_db_release_memory,  0},

     { "sqlite4_limit",                 test_limit,                 0},

     { "optimization_control",          optimization_control,0},
#if SQLITE4_OS_WIN
     { "lock_win32_file",               win32_file_lock,    0 },
#endif
     { "tcl_objproc",                   runAsObjProc,       0 },

     /* sqlite4_column_*() API */
     { "sqlite4_column_count",          test_column_count  ,0 },
     { "sqlite4_data_count",            test_data_count    ,0 },
     { "sqlite4_column_type",           test_column_type   ,0 },
     { "sqlite4_column_blob",           test_column_blob   ,0 },
     { "sqlite4_column_double",         test_column_double ,0 },
     { "sqlite4_column_int64",          test_column_int64  ,0 },
     { "sqlite4_column_text",   test_stmt_utf8,  (void*)sqlite4_column_text },
     { "sqlite4_column_name",   test_stmt_utf8,  (void*)sqlite4_column_name },
     { "sqlite4_column_int",    test_stmt_int,   (void*)sqlite4_column_int  },
#ifndef SQLITE4_OMIT_DECLTYPE
     { "sqlite4_column_decltype",test_stmt_utf8,(void*)sqlite4_column_decltype},
#endif
#ifdef SQLITE4_ENABLE_COLUMN_METADATA
{ "sqlite4_column_database_name",test_stmt_utf8,(void*)sqlite4_column_database_name},
{ "sqlite4_column_table_name",test_stmt_utf8,(void*)sqlite4_column_table_name},
{ "sqlite4_column_origin_name",test_stmt_utf8,(void*)sqlite4_column_origin_name},
#endif

#ifndef SQLITE4_OMIT_UTF16
  { "sqlite4_column_text16",  test_stmt_utf16, (void*)sqlite4_column_text16},
#endif

     { "sqlite4_create_collation",   test_create_collation, 0 },
     { "sqlite4_collation_needed",   test_collation_needed, 0 },
     { "sqlite4_profile",            test_profile, 0 },
     { "sqlite4_trace",              test_trace, 0 },
     { "working_64bit_int",          working_64bit_int,   0   },
     { "sqlite4_create_function_v2", test_create_function_v2, 0 },

     /* Functions from os.h */
#ifndef SQLITE4_OMIT_UTF16
     { "add_test_function",       test_function, 0           },
#endif
     { "sqlite4_test_errstr",     test_errstr, 0             },
     { "tcl_variable_type",       tcl_variable_type, 0       },
     { "sqlite4_libversion_number", test_libversion_number, 0  },
     { "test_sqlite4_log",         test_sqlite4_log, 0  },
#ifndef SQLITE4_OMIT_EXPLAIN
     { "print_explain_query_plan", test_print_eqp, 0  },
#endif
     { "sqlite4_test_control", test_test_control },

     { "prng_state_get", test_prng_state_get },
     { "prng_state_set", test_prng_state_set },
  };
  static int bitmask_size = sizeof(Bitmask)*8;
  int i;
  extern int sqlite4_opentemp_count;
  extern int sqlite4_like_count;
  extern int sqlite4_xferopt_count;
  extern int sqlite4_pager_readdb_count;
  extern int sqlite4_pager_writedb_count;
  extern int sqlite4_pager_writej_count;
#if SQLITE4_OS_WIN
  extern int sqlite4_os_type;
#endif
#ifdef SQLITE4_DEBUG
  extern int sqlite4WhereTrace;
  extern int sqlite4OSTrace;
#endif
#ifdef SQLITE4_TEST
#ifdef SQLITE4_ENABLE_FTS3
  extern int sqlite4_fts3_enable_parentheses;
#endif
#endif

  for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
    Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
  }
  for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, 
        aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
  }
  Tcl_LinkVar(interp, "sqlite_search_count", 
      (char*)&sqlite4_search_count, TCL_LINK_INT);
  Tcl_LinkVar(interp, "sqlite_found_count", 
      (char*)&sqlite4_found_count, TCL_LINK_INT);
  Tcl_LinkVar(interp, "sqlite_sort_count", 
      (char*)&sqlite4_sort_count, TCL_LINK_INT);
  Tcl_LinkVar(interp, "sqlite4_max_blobsize", 
      (char*)&sqlite4_max_blobsize, TCL_LINK_INT);
  Tcl_LinkVar(interp, "sqlite_like_count", 
      (char*)&sqlite4_like_count, TCL_LINK_INT);
  Tcl_LinkVar(interp, "sqlite_interrupt_count", 
      (char*)&sqlite4_interrupt_count, TCL_LINK_INT);

  Tcl_LinkVar(interp, "sqlite_current_time", 
      (char*)&sqlite4_current_time, TCL_LINK_INT);
#if SQLITE4_OS_UNIX && defined(__APPLE__) && SQLITE4_ENABLE_LOCKING_STYLE
  Tcl_LinkVar(interp, "sqlite_hostid_num", 
      (char*)&sqlite4_hostid_num, TCL_LINK_INT);
#endif
  Tcl_LinkVar(interp, "sqlite4_xferopt_count",
      (char*)&sqlite4_xferopt_count, TCL_LINK_INT);
#if SQLITE4_OS_WIN
  Tcl_LinkVar(interp, "sqlite_os_type",
      (char*)&sqlite4_os_type, TCL_LINK_INT);
#endif
#ifdef SQLITE4_DEBUG
  Tcl_LinkVar(interp, "sqlite_where_trace",
      (char*)&sqlite4WhereTrace, TCL_LINK_INT);
#endif
  Tcl_LinkVar(interp, "sqlite_static_bind_value",
      (char*)&sqlite_static_bind_value, TCL_LINK_STRING);
  Tcl_LinkVar(interp, "sqlite_static_bind_nbyte",
      (char*)&sqlite_static_bind_nbyte, TCL_LINK_INT);
  Tcl_LinkVar(interp, "bitmask_size",
      (char*)&bitmask_size, TCL_LINK_INT|TCL_LINK_READ_ONLY);
#if defined(SQLITE4_ENABLE_FTS3) && defined(SQLITE4_TEST)
  Tcl_LinkVar(interp, "sqlite_fts3_enable_parentheses",
      (char*)&sqlite4_fts3_enable_parentheses, TCL_LINK_INT);
#endif
  return TCL_OK;
}