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