modernc.org/cc@v1.0.1/v2/testdata/_sqlite/src/test4.c (about)

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