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

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