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 */
client_main(void * pArg)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 */
parse_client_id(Tcl_Interp * interp,const char * zArg)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 */
tcl_client_create(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
client_wait(Thread * p)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 */
tcl_client_wait(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
stop_thread(Thread * p)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 */
tcl_client_halt(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
tcl_client_argc(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
tcl_client_argv(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
tcl_client_colname(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
tcl_client_result(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
tcl_client_error(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
do_compile(Thread * p)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 */
tcl_client_compile(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
do_step(Thread * p)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 */
tcl_client_step(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
do_finalize(Thread * p)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 */
tcl_client_finalize(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
do_reset(Thread * p)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 */
tcl_client_reset(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
tcl_client_swap(void * NotUsed,Tcl_Interp * interp,int argc,const char ** argv)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 */
Sqlitetest7_Init(Tcl_Interp * interp)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
Sqlitetest7_Init(Tcl_Interp * interp)717 int Sqlitetest7_Init(Tcl_Interp *interp){ return TCL_OK; }
718 #endif /* SQLITE_OS_UNIX */
719