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