persistentstorage/sqlite3api/TEST/SRC/test4.c
changeset 0 08ec8eefde2f
equal deleted inserted replaced
-1:000000000000 0:08ec8eefde2f
       
     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 */