Home | History | Annotate | Download | only in src
      1 /*
      2 ** 2003 December 18
      3 **
      4 ** The author disclaims copyright to this source code.  In place of
      5 ** a legal notice, here is a blessing:
      6 **
      7 **    May you do good and not evil.
      8 **    May you find forgiveness for yourself and forgive others.
      9 **    May you share freely, never taking more than you give.
     10 **
     11 *************************************************************************
     12 ** Code for testing the the SQLite library in a multithreaded environment.
     13 */
     14 #include "sqliteInt.h"
     15 #include "tcl.h"
     16 #if SQLITE_OS_UNIX && SQLITE_THREADSAFE
     17 #include <stdlib.h>
     18 #include <string.h>
     19 #include <pthread.h>
     20 #include <sched.h>
     21 #include <ctype.h>
     22 
     23 /*
     24 ** Each thread is controlled by an instance of the following
     25 ** structure.
     26 */
     27 typedef struct Thread Thread;
     28 struct Thread {
     29   /* The first group of fields are writable by the master and read-only
     30   ** to the thread. */
     31   char *zFilename;       /* Name of database file */
     32   void (*xOp)(Thread*);  /* next operation to do */
     33   char *zArg;            /* argument usable by xOp */
     34   int opnum;             /* Operation number */
     35   int busy;              /* True if this thread is in use */
     36 
     37   /* The next group of fields are writable by the thread but read-only to the
     38   ** master. */
     39   int completed;        /* Number of operations completed */
     40   sqlite3 *db;           /* Open database */
     41   sqlite3_stmt *pStmt;     /* Pending operation */
     42   char *zErr;           /* operation error */
     43   char *zStaticErr;     /* Static error message */
     44   int rc;               /* operation return code */
     45   int argc;             /* number of columns in result */
     46   const char *argv[100];    /* result columns */
     47   const char *colv[100];    /* result column names */
     48 };
     49 
     50 /*
     51 ** There can be as many as 26 threads running at once.  Each is named
     52 ** by a capital letter: A, B, C, ..., Y, Z.
     53 */
     54 #define N_THREAD 26
     55 static Thread threadset[N_THREAD];
     56 
     57 
     58 /*
     59 ** The main loop for a thread.  Threads use busy waiting.
     60 */
     61 static void *thread_main(void *pArg){
     62   Thread *p = (Thread*)pArg;
     63   if( p->db ){
     64     sqlite3_close(p->db);
     65   }
     66   sqlite3_open(p->zFilename, &p->db);
     67   if( SQLITE_OK!=sqlite3_errcode(p->db) ){
     68     p->zErr = strdup(sqlite3_errmsg(p->db));
     69     sqlite3_close(p->db);
     70     p->db = 0;
     71   }
     72   p->pStmt = 0;
     73   p->completed = 1;
     74   while( p->opnum<=p->completed ) sched_yield();
     75   while( p->xOp ){
     76     if( p->zErr && p->zErr!=p->zStaticErr ){
     77       sqlite3_free(p->zErr);
     78       p->zErr = 0;
     79     }
     80     (*p->xOp)(p);
     81     p->completed++;
     82     while( p->opnum<=p->completed ) sched_yield();
     83   }
     84   if( p->pStmt ){
     85     sqlite3_finalize(p->pStmt);
     86     p->pStmt = 0;
     87   }
     88   if( p->db ){
     89     sqlite3_close(p->db);
     90     p->db = 0;
     91   }
     92   if( p->zErr && p->zErr!=p->zStaticErr ){
     93     sqlite3_free(p->zErr);
     94     p->zErr = 0;
     95   }
     96   p->completed++;
     97 #ifndef SQLITE_OMIT_DEPRECATED
     98   sqlite3_thread_cleanup();
     99 #endif
    100   return 0;
    101 }
    102 
    103 /*
    104 ** Get a thread ID which is an upper case letter.  Return the index.
    105 ** If the argument is not a valid thread ID put an error message in
    106 ** the interpreter and return -1.
    107 */
    108 static int parse_thread_id(Tcl_Interp *interp, const char *zArg){
    109   if( zArg==0 || zArg[0]==0 || zArg[1]!=0 || !isupper((unsigned char)zArg[0]) ){
    110     Tcl_AppendResult(interp, "thread ID must be an upper case letter", 0);
    111     return -1;
    112   }
    113   return zArg[0] - 'A';
    114 }
    115 
    116 /*
    117 ** Usage:    thread_create NAME  FILENAME
    118 **
    119 ** NAME should be an upper case letter.  Start the thread running with
    120 ** an open connection to the given database.
    121 */
    122 static int tcl_thread_create(
    123   void *NotUsed,
    124   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    125   int argc,              /* Number of arguments */
    126   const char **argv      /* Text of each argument */
    127 ){
    128   int i;
    129   pthread_t x;
    130   int rc;
    131 
    132   if( argc!=3 ){
    133     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    134        " ID FILENAME", 0);
    135     return TCL_ERROR;
    136   }
    137   i = parse_thread_id(interp, argv[1]);
    138   if( i<0 ) return TCL_ERROR;
    139   if( threadset[i].busy ){
    140     Tcl_AppendResult(interp, "thread ", argv[1], " is already running", 0);
    141     return TCL_ERROR;
    142   }
    143   threadset[i].busy = 1;
    144   sqlite3_free(threadset[i].zFilename);
    145   threadset[i].zFilename = sqlite3_mprintf("%s", argv[2]);
    146   threadset[i].opnum = 1;
    147   threadset[i].completed = 0;
    148   rc = pthread_create(&x, 0, thread_main, &threadset[i]);
    149   if( rc ){
    150     Tcl_AppendResult(interp, "failed to create the thread", 0);
    151     sqlite3_free(threadset[i].zFilename);
    152     threadset[i].busy = 0;
    153     return TCL_ERROR;
    154   }
    155   pthread_detach(x);
    156   return TCL_OK;
    157 }
    158 
    159 /*
    160 ** Wait for a thread to reach its idle state.
    161 */
    162 static void thread_wait(Thread *p){
    163   while( p->opnum>p->completed ) sched_yield();
    164 }
    165 
    166 /*
    167 ** Usage:  thread_wait ID
    168 **
    169 ** Wait on thread ID to reach its idle state.
    170 */
    171 static int tcl_thread_wait(
    172   void *NotUsed,
    173   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    174   int argc,              /* Number of arguments */
    175   const char **argv      /* Text of each argument */
    176 ){
    177   int i;
    178 
    179   if( argc!=2 ){
    180     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    181        " ID", 0);
    182     return TCL_ERROR;
    183   }
    184   i = parse_thread_id(interp, argv[1]);
    185   if( i<0 ) return TCL_ERROR;
    186   if( !threadset[i].busy ){
    187     Tcl_AppendResult(interp, "no such thread", 0);
    188     return TCL_ERROR;
    189   }
    190   thread_wait(&threadset[i]);
    191   return TCL_OK;
    192 }
    193 
    194 /*
    195 ** Stop a thread.
    196 */
    197 static void stop_thread(Thread *p){
    198   thread_wait(p);
    199   p->xOp = 0;
    200   p->opnum++;
    201   thread_wait(p);
    202   sqlite3_free(p->zArg);
    203   p->zArg = 0;
    204   sqlite3_free(p->zFilename);
    205   p->zFilename = 0;
    206   p->busy = 0;
    207 }
    208 
    209 /*
    210 ** Usage:  thread_halt ID
    211 **
    212 ** Cause a thread to shut itself down.  Wait for the shutdown to be
    213 ** completed.  If ID is "*" then stop all threads.
    214 */
    215 static int tcl_thread_halt(
    216   void *NotUsed,
    217   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    218   int argc,              /* Number of arguments */
    219   const char **argv      /* Text of each argument */
    220 ){
    221   int i;
    222 
    223   if( argc!=2 ){
    224     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    225        " ID", 0);
    226     return TCL_ERROR;
    227   }
    228   if( argv[1][0]=='*' && argv[1][1]==0 ){
    229     for(i=0; i<N_THREAD; i++){
    230       if( threadset[i].busy ) stop_thread(&threadset[i]);
    231     }
    232   }else{
    233     i = parse_thread_id(interp, argv[1]);
    234     if( i<0 ) return TCL_ERROR;
    235     if( !threadset[i].busy ){
    236       Tcl_AppendResult(interp, "no such thread", 0);
    237       return TCL_ERROR;
    238     }
    239     stop_thread(&threadset[i]);
    240   }
    241   return TCL_OK;
    242 }
    243 
    244 /*
    245 ** Usage: thread_argc  ID
    246 **
    247 ** Wait on the most recent thread_step to complete, then return the
    248 ** number of columns in the result set.
    249 */
    250 static int tcl_thread_argc(
    251   void *NotUsed,
    252   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    253   int argc,              /* Number of arguments */
    254   const char **argv      /* Text of each argument */
    255 ){
    256   int i;
    257   char zBuf[100];
    258 
    259   if( argc!=2 ){
    260     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    261        " ID", 0);
    262     return TCL_ERROR;
    263   }
    264   i = parse_thread_id(interp, argv[1]);
    265   if( i<0 ) return TCL_ERROR;
    266   if( !threadset[i].busy ){
    267     Tcl_AppendResult(interp, "no such thread", 0);
    268     return TCL_ERROR;
    269   }
    270   thread_wait(&threadset[i]);
    271   sprintf(zBuf, "%d", threadset[i].argc);
    272   Tcl_AppendResult(interp, zBuf, 0);
    273   return TCL_OK;
    274 }
    275 
    276 /*
    277 ** Usage: thread_argv  ID   N
    278 **
    279 ** Wait on the most recent thread_step to complete, then return the
    280 ** value of the N-th columns in the result set.
    281 */
    282 static int tcl_thread_argv(
    283   void *NotUsed,
    284   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    285   int argc,              /* Number of arguments */
    286   const char **argv      /* Text of each argument */
    287 ){
    288   int i;
    289   int n;
    290 
    291   if( argc!=3 ){
    292     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    293        " ID N", 0);
    294     return TCL_ERROR;
    295   }
    296   i = parse_thread_id(interp, argv[1]);
    297   if( i<0 ) return TCL_ERROR;
    298   if( !threadset[i].busy ){
    299     Tcl_AppendResult(interp, "no such thread", 0);
    300     return TCL_ERROR;
    301   }
    302   if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
    303   thread_wait(&threadset[i]);
    304   if( n<0 || n>=threadset[i].argc ){
    305     Tcl_AppendResult(interp, "column number out of range", 0);
    306     return TCL_ERROR;
    307   }
    308   Tcl_AppendResult(interp, threadset[i].argv[n], 0);
    309   return TCL_OK;
    310 }
    311 
    312 /*
    313 ** Usage: thread_colname  ID   N
    314 **
    315 ** Wait on the most recent thread_step to complete, then return the
    316 ** name of the N-th columns in the result set.
    317 */
    318 static int tcl_thread_colname(
    319   void *NotUsed,
    320   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    321   int argc,              /* Number of arguments */
    322   const char **argv      /* Text of each argument */
    323 ){
    324   int i;
    325   int n;
    326 
    327   if( argc!=3 ){
    328     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    329        " ID N", 0);
    330     return TCL_ERROR;
    331   }
    332   i = parse_thread_id(interp, argv[1]);
    333   if( i<0 ) return TCL_ERROR;
    334   if( !threadset[i].busy ){
    335     Tcl_AppendResult(interp, "no such thread", 0);
    336     return TCL_ERROR;
    337   }
    338   if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
    339   thread_wait(&threadset[i]);
    340   if( n<0 || n>=threadset[i].argc ){
    341     Tcl_AppendResult(interp, "column number out of range", 0);
    342     return TCL_ERROR;
    343   }
    344   Tcl_AppendResult(interp, threadset[i].colv[n], 0);
    345   return TCL_OK;
    346 }
    347 
    348 /*
    349 ** Usage: thread_result  ID
    350 **
    351 ** Wait on the most recent operation to complete, then return the
    352 ** result code from that operation.
    353 */
    354 static int tcl_thread_result(
    355   void *NotUsed,
    356   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    357   int argc,              /* Number of arguments */
    358   const char **argv      /* Text of each argument */
    359 ){
    360   int i;
    361   const char *zName;
    362 
    363   if( argc!=2 ){
    364     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    365        " ID", 0);
    366     return TCL_ERROR;
    367   }
    368   i = parse_thread_id(interp, argv[1]);
    369   if( i<0 ) return TCL_ERROR;
    370   if( !threadset[i].busy ){
    371     Tcl_AppendResult(interp, "no such thread", 0);
    372     return TCL_ERROR;
    373   }
    374   thread_wait(&threadset[i]);
    375   switch( threadset[i].rc ){
    376     case SQLITE_OK:         zName = "SQLITE_OK";          break;
    377     case SQLITE_ERROR:      zName = "SQLITE_ERROR";       break;
    378     case SQLITE_PERM:       zName = "SQLITE_PERM";        break;
    379     case SQLITE_ABORT:      zName = "SQLITE_ABORT";       break;
    380     case SQLITE_BUSY:       zName = "SQLITE_BUSY";        break;
    381     case SQLITE_LOCKED:     zName = "SQLITE_LOCKED";      break;
    382     case SQLITE_NOMEM:      zName = "SQLITE_NOMEM";       break;
    383     case SQLITE_READONLY:   zName = "SQLITE_READONLY";    break;
    384     case SQLITE_INTERRUPT:  zName = "SQLITE_INTERRUPT";   break;
    385     case SQLITE_IOERR:      zName = "SQLITE_IOERR";       break;
    386     case SQLITE_CORRUPT:    zName = "SQLITE_CORRUPT";     break;
    387     case SQLITE_FULL:       zName = "SQLITE_FULL";        break;
    388     case SQLITE_CANTOPEN:   zName = "SQLITE_CANTOPEN";    break;
    389     case SQLITE_PROTOCOL:   zName = "SQLITE_PROTOCOL";    break;
    390     case SQLITE_EMPTY:      zName = "SQLITE_EMPTY";       break;
    391     case SQLITE_SCHEMA:     zName = "SQLITE_SCHEMA";      break;
    392     case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT";  break;
    393     case SQLITE_MISMATCH:   zName = "SQLITE_MISMATCH";    break;
    394     case SQLITE_MISUSE:     zName = "SQLITE_MISUSE";      break;
    395     case SQLITE_NOLFS:      zName = "SQLITE_NOLFS";       break;
    396     case SQLITE_AUTH:       zName = "SQLITE_AUTH";        break;
    397     case SQLITE_FORMAT:     zName = "SQLITE_FORMAT";      break;
    398     case SQLITE_RANGE:      zName = "SQLITE_RANGE";       break;
    399     case SQLITE_ROW:        zName = "SQLITE_ROW";         break;
    400     case SQLITE_DONE:       zName = "SQLITE_DONE";        break;
    401     default:                zName = "SQLITE_Unknown";     break;
    402   }
    403   Tcl_AppendResult(interp, zName, 0);
    404   return TCL_OK;
    405 }
    406 
    407 /*
    408 ** Usage: thread_error  ID
    409 **
    410 ** Wait on the most recent operation to complete, then return the
    411 ** error string.
    412 */
    413 static int tcl_thread_error(
    414   void *NotUsed,
    415   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    416   int argc,              /* Number of arguments */
    417   const char **argv      /* Text of each argument */
    418 ){
    419   int i;
    420 
    421   if( argc!=2 ){
    422     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    423        " ID", 0);
    424     return TCL_ERROR;
    425   }
    426   i = parse_thread_id(interp, argv[1]);
    427   if( i<0 ) return TCL_ERROR;
    428   if( !threadset[i].busy ){
    429     Tcl_AppendResult(interp, "no such thread", 0);
    430     return TCL_ERROR;
    431   }
    432   thread_wait(&threadset[i]);
    433   Tcl_AppendResult(interp, threadset[i].zErr, 0);
    434   return TCL_OK;
    435 }
    436 
    437 /*
    438 ** This procedure runs in the thread to compile an SQL statement.
    439 */
    440 static void do_compile(Thread *p){
    441   if( p->db==0 ){
    442     p->zErr = p->zStaticErr = "no database is open";
    443     p->rc = SQLITE_ERROR;
    444     return;
    445   }
    446   if( p->pStmt ){
    447     sqlite3_finalize(p->pStmt);
    448     p->pStmt = 0;
    449   }
    450   p->rc = sqlite3_prepare(p->db, p->zArg, -1, &p->pStmt, 0);
    451 }
    452 
    453 /*
    454 ** Usage: thread_compile ID SQL
    455 **
    456 ** Compile a new virtual machine.
    457 */
    458 static int tcl_thread_compile(
    459   void *NotUsed,
    460   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    461   int argc,              /* Number of arguments */
    462   const char **argv      /* Text of each argument */
    463 ){
    464   int i;
    465   if( argc!=3 ){
    466     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    467        " ID SQL", 0);
    468     return TCL_ERROR;
    469   }
    470   i = parse_thread_id(interp, argv[1]);
    471   if( i<0 ) return TCL_ERROR;
    472   if( !threadset[i].busy ){
    473     Tcl_AppendResult(interp, "no such thread", 0);
    474     return TCL_ERROR;
    475   }
    476   thread_wait(&threadset[i]);
    477   threadset[i].xOp = do_compile;
    478   sqlite3_free(threadset[i].zArg);
    479   threadset[i].zArg = sqlite3_mprintf("%s", argv[2]);
    480   threadset[i].opnum++;
    481   return TCL_OK;
    482 }
    483 
    484 /*
    485 ** This procedure runs in the thread to step the virtual machine.
    486 */
    487 static void do_step(Thread *p){
    488   int i;
    489   if( p->pStmt==0 ){
    490     p->zErr = p->zStaticErr = "no virtual machine available";
    491     p->rc = SQLITE_ERROR;
    492     return;
    493   }
    494   p->rc = sqlite3_step(p->pStmt);
    495   if( p->rc==SQLITE_ROW ){
    496     p->argc = sqlite3_column_count(p->pStmt);
    497     for(i=0; i<sqlite3_data_count(p->pStmt); i++){
    498       p->argv[i] = (char*)sqlite3_column_text(p->pStmt, i);
    499     }
    500     for(i=0; i<p->argc; i++){
    501       p->colv[i] = sqlite3_column_name(p->pStmt, i);
    502     }
    503   }
    504 }
    505 
    506 /*
    507 ** Usage: thread_step ID
    508 **
    509 ** Advance the virtual machine by one step
    510 */
    511 static int tcl_thread_step(
    512   void *NotUsed,
    513   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    514   int argc,              /* Number of arguments */
    515   const char **argv      /* Text of each argument */
    516 ){
    517   int i;
    518   if( argc!=2 ){
    519     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    520        " IDL", 0);
    521     return TCL_ERROR;
    522   }
    523   i = parse_thread_id(interp, argv[1]);
    524   if( i<0 ) return TCL_ERROR;
    525   if( !threadset[i].busy ){
    526     Tcl_AppendResult(interp, "no such thread", 0);
    527     return TCL_ERROR;
    528   }
    529   thread_wait(&threadset[i]);
    530   threadset[i].xOp = do_step;
    531   threadset[i].opnum++;
    532   return TCL_OK;
    533 }
    534 
    535 /*
    536 ** This procedure runs in the thread to finalize a virtual machine.
    537 */
    538 static void do_finalize(Thread *p){
    539   if( p->pStmt==0 ){
    540     p->zErr = p->zStaticErr = "no virtual machine available";
    541     p->rc = SQLITE_ERROR;
    542     return;
    543   }
    544   p->rc = sqlite3_finalize(p->pStmt);
    545   p->pStmt = 0;
    546 }
    547 
    548 /*
    549 ** Usage: thread_finalize ID
    550 **
    551 ** Finalize the virtual machine.
    552 */
    553 static int tcl_thread_finalize(
    554   void *NotUsed,
    555   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    556   int argc,              /* Number of arguments */
    557   const char **argv      /* Text of each argument */
    558 ){
    559   int i;
    560   if( argc!=2 ){
    561     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    562        " IDL", 0);
    563     return TCL_ERROR;
    564   }
    565   i = parse_thread_id(interp, argv[1]);
    566   if( i<0 ) return TCL_ERROR;
    567   if( !threadset[i].busy ){
    568     Tcl_AppendResult(interp, "no such thread", 0);
    569     return TCL_ERROR;
    570   }
    571   thread_wait(&threadset[i]);
    572   threadset[i].xOp = do_finalize;
    573   sqlite3_free(threadset[i].zArg);
    574   threadset[i].zArg = 0;
    575   threadset[i].opnum++;
    576   return TCL_OK;
    577 }
    578 
    579 /*
    580 ** Usage: thread_swap ID ID
    581 **
    582 ** Interchange the sqlite* pointer between two threads.
    583 */
    584 static int tcl_thread_swap(
    585   void *NotUsed,
    586   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    587   int argc,              /* Number of arguments */
    588   const char **argv      /* Text of each argument */
    589 ){
    590   int i, j;
    591   sqlite3 *temp;
    592   if( argc!=3 ){
    593     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    594        " ID1 ID2", 0);
    595     return TCL_ERROR;
    596   }
    597   i = parse_thread_id(interp, argv[1]);
    598   if( i<0 ) return TCL_ERROR;
    599   if( !threadset[i].busy ){
    600     Tcl_AppendResult(interp, "no such thread", 0);
    601     return TCL_ERROR;
    602   }
    603   thread_wait(&threadset[i]);
    604   j = parse_thread_id(interp, argv[2]);
    605   if( j<0 ) return TCL_ERROR;
    606   if( !threadset[j].busy ){
    607     Tcl_AppendResult(interp, "no such thread", 0);
    608     return TCL_ERROR;
    609   }
    610   thread_wait(&threadset[j]);
    611   temp = threadset[i].db;
    612   threadset[i].db = threadset[j].db;
    613   threadset[j].db = temp;
    614   return TCL_OK;
    615 }
    616 
    617 /*
    618 ** Usage: thread_db_get ID
    619 **
    620 ** Return the database connection pointer for the given thread.  Then
    621 ** remove the pointer from the thread itself.  Afterwards, the thread
    622 ** can be stopped and the connection can be used by the main thread.
    623 */
    624 static int tcl_thread_db_get(
    625   void *NotUsed,
    626   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    627   int argc,              /* Number of arguments */
    628   const char **argv      /* Text of each argument */
    629 ){
    630   int i;
    631   char zBuf[100];
    632   extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);
    633   if( argc!=2 ){
    634     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    635        " ID", 0);
    636     return TCL_ERROR;
    637   }
    638   i = parse_thread_id(interp, argv[1]);
    639   if( i<0 ) return TCL_ERROR;
    640   if( !threadset[i].busy ){
    641     Tcl_AppendResult(interp, "no such thread", 0);
    642     return TCL_ERROR;
    643   }
    644   thread_wait(&threadset[i]);
    645   sqlite3TestMakePointerStr(interp, zBuf, threadset[i].db);
    646   threadset[i].db = 0;
    647   Tcl_AppendResult(interp, zBuf, (char*)0);
    648   return TCL_OK;
    649 }
    650 
    651 /*
    652 ** Usage: thread_db_put ID DB
    653 **
    654 */
    655 static int tcl_thread_db_put(
    656   void *NotUsed,
    657   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    658   int argc,              /* Number of arguments */
    659   const char **argv      /* Text of each argument */
    660 ){
    661   int i;
    662   extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);
    663   extern void *sqlite3TestTextToPtr(const char *);
    664   if( argc!=3 ){
    665     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    666        " ID DB", 0);
    667     return TCL_ERROR;
    668   }
    669   i = parse_thread_id(interp, argv[1]);
    670   if( i<0 ) return TCL_ERROR;
    671   if( !threadset[i].busy ){
    672     Tcl_AppendResult(interp, "no such thread", 0);
    673     return TCL_ERROR;
    674   }
    675   thread_wait(&threadset[i]);
    676   assert( !threadset[i].db );
    677   threadset[i].db = (sqlite3*)sqlite3TestTextToPtr(argv[2]);
    678   return TCL_OK;
    679 }
    680 
    681 /*
    682 ** Usage: thread_stmt_get ID
    683 **
    684 ** Return the database stmt pointer for the given thread.  Then
    685 ** remove the pointer from the thread itself.
    686 */
    687 static int tcl_thread_stmt_get(
    688   void *NotUsed,
    689   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
    690   int argc,              /* Number of arguments */
    691   const char **argv      /* Text of each argument */
    692 ){
    693   int i;
    694   char zBuf[100];
    695   extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);
    696   if( argc!=2 ){
    697     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
    698        " ID", 0);
    699     return TCL_ERROR;
    700   }
    701   i = parse_thread_id(interp, argv[1]);
    702   if( i<0 ) return TCL_ERROR;
    703   if( !threadset[i].busy ){
    704     Tcl_AppendResult(interp, "no such thread", 0);
    705     return TCL_ERROR;
    706   }
    707   thread_wait(&threadset[i]);
    708   sqlite3TestMakePointerStr(interp, zBuf, threadset[i].pStmt);
    709   threadset[i].pStmt = 0;
    710   Tcl_AppendResult(interp, zBuf, (char*)0);
    711   return TCL_OK;
    712 }
    713 
    714 /*
    715 ** Register commands with the TCL interpreter.
    716 */
    717 int Sqlitetest4_Init(Tcl_Interp *interp){
    718   static struct {
    719      char *zName;
    720      Tcl_CmdProc *xProc;
    721   } aCmd[] = {
    722      { "thread_create",     (Tcl_CmdProc*)tcl_thread_create     },
    723      { "thread_wait",       (Tcl_CmdProc*)tcl_thread_wait       },
    724      { "thread_halt",       (Tcl_CmdProc*)tcl_thread_halt       },
    725      { "thread_argc",       (Tcl_CmdProc*)tcl_thread_argc       },
    726      { "thread_argv",       (Tcl_CmdProc*)tcl_thread_argv       },
    727      { "thread_colname",    (Tcl_CmdProc*)tcl_thread_colname    },
    728      { "thread_result",     (Tcl_CmdProc*)tcl_thread_result     },
    729      { "thread_error",      (Tcl_CmdProc*)tcl_thread_error      },
    730      { "thread_compile",    (Tcl_CmdProc*)tcl_thread_compile    },
    731      { "thread_step",       (Tcl_CmdProc*)tcl_thread_step       },
    732      { "thread_finalize",   (Tcl_CmdProc*)tcl_thread_finalize   },
    733      { "thread_swap",       (Tcl_CmdProc*)tcl_thread_swap       },
    734      { "thread_db_get",     (Tcl_CmdProc*)tcl_thread_db_get     },
    735      { "thread_db_put",     (Tcl_CmdProc*)tcl_thread_db_put     },
    736      { "thread_stmt_get",   (Tcl_CmdProc*)tcl_thread_stmt_get   },
    737   };
    738   int i;
    739 
    740   for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
    741     Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
    742   }
    743   return TCL_OK;
    744 }
    745 #else
    746 int Sqlitetest4_Init(Tcl_Interp *interp){ return TCL_OK; }
    747 #endif /* SQLITE_OS_UNIX */
    748