os/persistentdata/persistentstorage/sqlite3api/TEST/SRC/test4.c
author sl@SLION-WIN7.fritz.box
Fri, 15 Jun 2012 03:10:57 +0200
changeset 0 bde4ae8d615e
permissions -rw-r--r--
First public contribution.
     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 ** $Id: test4.c,v 1.23 2008/07/28 19:34:54 drh Exp $
    15 */
    16 #include "sqliteInt.h"
    17 #include "tcl.h"
    18 #if defined(SQLITE_OS_UNIX) && OS_UNIX==1 && SQLITE_THREADSAFE
    19 #include <stdlib.h>
    20 #include <string.h>
    21 #include <pthread.h>
    22 #include <sched.h>
    23 #include <ctype.h>
    24 
    25 /*
    26 ** Each thread is controlled by an instance of the following
    27 ** structure.
    28 */
    29 typedef struct Thread Thread;
    30 struct Thread {
    31   /* The first group of fields are writable by the master and read-only
    32   ** to the thread. */
    33   char *zFilename;       /* Name of database file */
    34   void (*xOp)(Thread*);  /* next operation to do */
    35   char *zArg;            /* argument usable by xOp */
    36   int opnum;             /* Operation number */
    37   int busy;              /* True if this thread is in use */
    38 
    39   /* The next group of fields are writable by the thread but read-only to the
    40   ** master. */
    41   int completed;        /* Number of operations completed */
    42   sqlite3 *db;           /* Open database */
    43   sqlite3_stmt *pStmt;     /* Pending operation */
    44   char *zErr;           /* operation error */
    45   char *zStaticErr;     /* Static error message */
    46   int rc;               /* operation return code */
    47   int argc;             /* number of columns in result */
    48   const char *argv[100];    /* result columns */
    49   const char *colv[100];    /* result column names */
    50 };
    51 
    52 /*
    53 ** There can be as many as 26 threads running at once.  Each is named
    54 ** by a capital letter: A, B, C, ..., Y, Z.
    55 */
    56 #define N_THREAD 26
    57 static Thread threadset[N_THREAD];
    58 
    59 
    60 /*
    61 ** The main loop for a thread.  Threads use busy waiting. 
    62 */
    63 static void *thread_main(void *pArg){
    64   Thread *p = (Thread*)pArg;
    65   if( p->db ){
    66     sqlite3_close(p->db);
    67   }
    68   sqlite3_open(p->zFilename, &p->db);
    69   if( SQLITE_OK!=sqlite3_errcode(p->db) ){
    70     p->zErr = strdup(sqlite3_errmsg(p->db));
    71     sqlite3_close(p->db);
    72     p->db = 0;
    73   }
    74   p->pStmt = 0;
    75   p->completed = 1;
    76   while( p->opnum<=p->completed ) sched_yield();
    77   while( p->xOp ){
    78     if( p->zErr && p->zErr!=p->zStaticErr ){
    79       sqlite3_free(p->zErr);
    80       p->zErr = 0;
    81     }
    82     (*p->xOp)(p);
    83     p->completed++;
    84     while( p->opnum<=p->completed ) sched_yield();
    85   }
    86   if( p->pStmt ){
    87     sqlite3_finalize(p->pStmt);
    88     p->pStmt = 0;
    89   }
    90   if( p->db ){
    91     sqlite3_close(p->db);
    92     p->db = 0;
    93   }
    94   if( p->zErr && p->zErr!=p->zStaticErr ){
    95     sqlite3_free(p->zErr);
    96     p->zErr = 0;
    97   }
    98   p->completed++;
    99   sqlite3_thread_cleanup();
   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 = sqlite3DbStrDup(0, 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 = sqlite3DbStrDup(0, 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_stmt_get ID
   653 **
   654 ** Return the database stmt pointer for the given thread.  Then
   655 ** remove the pointer from the thread itself. 
   656 */
   657 static int tcl_thread_stmt_get(
   658   void *NotUsed,
   659   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
   660   int argc,              /* Number of arguments */
   661   const char **argv      /* Text of each argument */
   662 ){
   663   int i;
   664   char zBuf[100];
   665   extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*);
   666   if( argc!=2 ){
   667     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
   668        " ID", 0);
   669     return TCL_ERROR;
   670   }
   671   i = parse_thread_id(interp, argv[1]);
   672   if( i<0 ) return TCL_ERROR;
   673   if( !threadset[i].busy ){
   674     Tcl_AppendResult(interp, "no such thread", 0);
   675     return TCL_ERROR;
   676   }
   677   thread_wait(&threadset[i]);
   678   sqlite3TestMakePointerStr(interp, zBuf, threadset[i].pStmt);
   679   threadset[i].pStmt = 0;
   680   Tcl_AppendResult(interp, zBuf, (char*)0);
   681   return TCL_OK;
   682 }
   683 
   684 /*
   685 ** Register commands with the TCL interpreter.
   686 */
   687 int Sqlitetest4_Init(Tcl_Interp *interp){
   688   static struct {
   689      char *zName;
   690      Tcl_CmdProc *xProc;
   691   } aCmd[] = {
   692      { "thread_create",     (Tcl_CmdProc*)tcl_thread_create     },
   693      { "thread_wait",       (Tcl_CmdProc*)tcl_thread_wait       },
   694      { "thread_halt",       (Tcl_CmdProc*)tcl_thread_halt       },
   695      { "thread_argc",       (Tcl_CmdProc*)tcl_thread_argc       },
   696      { "thread_argv",       (Tcl_CmdProc*)tcl_thread_argv       },
   697      { "thread_colname",    (Tcl_CmdProc*)tcl_thread_colname    },
   698      { "thread_result",     (Tcl_CmdProc*)tcl_thread_result     },
   699      { "thread_error",      (Tcl_CmdProc*)tcl_thread_error      },
   700      { "thread_compile",    (Tcl_CmdProc*)tcl_thread_compile    },
   701      { "thread_step",       (Tcl_CmdProc*)tcl_thread_step       },
   702      { "thread_finalize",   (Tcl_CmdProc*)tcl_thread_finalize   },
   703      { "thread_swap",       (Tcl_CmdProc*)tcl_thread_swap       },
   704      { "thread_db_get",     (Tcl_CmdProc*)tcl_thread_db_get     },
   705      { "thread_stmt_get",   (Tcl_CmdProc*)tcl_thread_stmt_get   },
   706   };
   707   int i;
   708 
   709   for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
   710     Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
   711   }
   712   return TCL_OK;
   713 }
   714 #else
   715 int Sqlitetest4_Init(Tcl_Interp *interp){ return TCL_OK; }
   716 #endif /* SQLITE_OS_UNIX */