Main Page | Directories | File List

test4.c

00001 /*
00002 ** 2003 December 18
00003 **
00004 ** The author disclaims copyright to this source code.  In place of
00005 ** a legal notice, here is a blessing:
00006 **
00007 **    May you do good and not evil.
00008 **    May you find forgiveness for yourself and forgive others.
00009 **    May you share freely, never taking more than you give.
00010 **
00011 *************************************************************************
00012 ** Code for testing the the SQLite library in a multithreaded environment.
00013 **
00014 ** $Id: test4.c,v 1.3 2004/04/23 17:04:45 drh Exp $
00015 */
00016 #include "sqliteInt.h"
00017 #include "tcl.h"
00018 #if defined(OS_UNIX) && OS_UNIX==1 && defined(THREADSAFE) && THREADSAFE==1
00019 #include <stdlib.h>
00020 #include <string.h>
00021 #include <pthread.h>
00022 #include <sched.h>
00023 #include <ctype.h>
00024 
00025 /*
00026 ** Each thread is controlled by an instance of the following
00027 ** structure.
00028 */
00029 typedef struct Thread Thread;
00030 struct Thread {
00031   /* The first group of fields are writable by the master and read-only
00032   ** to the thread. */
00033   char *zFilename;       /* Name of database file */
00034   void (*xOp)(Thread*);  /* next operation to do */
00035   char *zArg;            /* argument usable by xOp */
00036   int opnum;             /* Operation number */
00037   int busy;              /* True if this thread is in use */
00038 
00039   /* The next group of fields are writable by the thread but read-only to the
00040   ** master. */
00041   int completed;        /* Number of operations completed */
00042   sqlite *db;           /* Open database */
00043   sqlite_vm *vm;        /* Pending operation */
00044   char *zErr;           /* operation error */
00045   char *zStaticErr;     /* Static error message */
00046   int rc;               /* operation return code */
00047   int argc;             /* number of columns in result */
00048   const char **argv;    /* result columns */
00049   const char **colv;    /* result column names */
00050 };
00051 
00052 /*
00053 ** There can be as many as 26 threads running at once.  Each is named
00054 ** by a capital letter: A, B, C, ..., Y, Z.
00055 */
00056 #define N_THREAD 26
00057 static Thread threadset[N_THREAD];
00058 
00059 
00060 /*
00061 ** The main loop for a thread.  Threads use busy waiting. 
00062 */
00063 static void *thread_main(void *pArg){
00064   Thread *p = (Thread*)pArg;
00065   if( p->db ){
00066     sqlite_close(p->db);
00067   }
00068   p->db = sqlite_open(p->zFilename, 0, &p->zErr);
00069   p->vm = 0;
00070   p->completed = 1;
00071   while( p->opnum<=p->completed ) sched_yield();
00072   while( p->xOp ){
00073     if( p->zErr && p->zErr!=p->zStaticErr ){
00074       sqlite_freemem(p->zErr);
00075       p->zErr = 0;
00076     }
00077     (*p->xOp)(p);
00078     p->completed++;
00079     while( p->opnum<=p->completed ) sched_yield();
00080   }
00081   if( p->vm ){
00082     sqlite_finalize(p->vm, 0);
00083     p->vm = 0;
00084   }
00085   if( p->db ){
00086     sqlite_close(p->db);
00087     p->db = 0;
00088   }
00089   if( p->zErr && p->zErr!=p->zStaticErr ){
00090     sqlite_freemem(p->zErr);
00091     p->zErr = 0;
00092   }
00093   p->completed++;
00094   return 0;
00095 }
00096 
00097 /*
00098 ** Get a thread ID which is an upper case letter.  Return the index.
00099 ** If the argument is not a valid thread ID put an error message in
00100 ** the interpreter and return -1.
00101 */
00102 static int parse_thread_id(Tcl_Interp *interp, const char *zArg){
00103   if( zArg==0 || zArg[0]==0 || zArg[1]!=0 || !isupper(zArg[0]) ){
00104     Tcl_AppendResult(interp, "thread ID must be an upper case letter", 0);
00105     return -1;
00106   }
00107   return zArg[0] - 'A';
00108 }
00109 
00110 /*
00111 ** Usage:    thread_create NAME  FILENAME
00112 **
00113 ** NAME should be an upper case letter.  Start the thread running with
00114 ** an open connection to the given database.
00115 */
00116 static int tcl_thread_create(
00117   void *NotUsed,
00118   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00119   int argc,              /* Number of arguments */
00120   const char **argv      /* Text of each argument */
00121 ){
00122   int i;
00123   pthread_t x;
00124   int rc;
00125 
00126   if( argc!=3 ){
00127     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00128        " ID FILENAME", 0);
00129     return TCL_ERROR;
00130   }
00131   i = parse_thread_id(interp, argv[1]);
00132   if( i<0 ) return TCL_ERROR;
00133   if( threadset[i].busy ){
00134     Tcl_AppendResult(interp, "thread ", argv[1], " is already running", 0);
00135     return TCL_ERROR;
00136   }
00137   threadset[i].busy = 1;
00138   sqliteFree(threadset[i].zFilename);
00139   threadset[i].zFilename = sqliteStrDup(argv[2]);
00140   threadset[i].opnum = 1;
00141   threadset[i].completed = 0;
00142   rc = pthread_create(&x, 0, thread_main, &threadset[i]);
00143   if( rc ){
00144     Tcl_AppendResult(interp, "failed to create the thread", 0);
00145     sqliteFree(threadset[i].zFilename);
00146     threadset[i].busy = 0;
00147     return TCL_ERROR;
00148   }
00149   pthread_detach(x);
00150   return TCL_OK;
00151 }
00152 
00153 /*
00154 ** Wait for a thread to reach its idle state.
00155 */
00156 static void thread_wait(Thread *p){
00157   while( p->opnum>p->completed ) sched_yield();
00158 }
00159 
00160 /*
00161 ** Usage:  thread_wait ID
00162 **
00163 ** Wait on thread ID to reach its idle state.
00164 */
00165 static int tcl_thread_wait(
00166   void *NotUsed,
00167   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00168   int argc,              /* Number of arguments */
00169   const char **argv      /* Text of each argument */
00170 ){
00171   int i;
00172 
00173   if( argc!=2 ){
00174     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00175        " ID", 0);
00176     return TCL_ERROR;
00177   }
00178   i = parse_thread_id(interp, argv[1]);
00179   if( i<0 ) return TCL_ERROR;
00180   if( !threadset[i].busy ){
00181     Tcl_AppendResult(interp, "no such thread", 0);
00182     return TCL_ERROR;
00183   }
00184   thread_wait(&threadset[i]);
00185   return TCL_OK;
00186 }
00187 
00188 /*
00189 ** Stop a thread.
00190 */
00191 static void stop_thread(Thread *p){
00192   thread_wait(p);
00193   p->xOp = 0;
00194   p->opnum++;
00195   thread_wait(p);
00196   sqliteFree(p->zArg);
00197   p->zArg = 0;
00198   sqliteFree(p->zFilename);
00199   p->zFilename = 0;
00200   p->busy = 0;
00201 }
00202 
00203 /*
00204 ** Usage:  thread_halt ID
00205 **
00206 ** Cause a thread to shut itself down.  Wait for the shutdown to be
00207 ** completed.  If ID is "*" then stop all threads.
00208 */
00209 static int tcl_thread_halt(
00210   void *NotUsed,
00211   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00212   int argc,              /* Number of arguments */
00213   const char **argv      /* Text of each argument */
00214 ){
00215   int i;
00216 
00217   if( argc!=2 ){
00218     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00219        " ID", 0);
00220     return TCL_ERROR;
00221   }
00222   if( argv[1][0]=='*' && argv[1][1]==0 ){
00223     for(i=0; i<N_THREAD; i++){
00224       if( threadset[i].busy ) stop_thread(&threadset[i]);
00225     }
00226   }else{
00227     i = parse_thread_id(interp, argv[1]);
00228     if( i<0 ) return TCL_ERROR;
00229     if( !threadset[i].busy ){
00230       Tcl_AppendResult(interp, "no such thread", 0);
00231       return TCL_ERROR;
00232     }
00233     stop_thread(&threadset[i]);
00234   }
00235   return TCL_OK;
00236 }
00237 
00238 /*
00239 ** Usage: thread_argc  ID
00240 **
00241 ** Wait on the most recent thread_step to complete, then return the
00242 ** number of columns in the result set.
00243 */
00244 static int tcl_thread_argc(
00245   void *NotUsed,
00246   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00247   int argc,              /* Number of arguments */
00248   const char **argv      /* Text of each argument */
00249 ){
00250   int i;
00251   char zBuf[100];
00252 
00253   if( argc!=2 ){
00254     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00255        " ID", 0);
00256     return TCL_ERROR;
00257   }
00258   i = parse_thread_id(interp, argv[1]);
00259   if( i<0 ) return TCL_ERROR;
00260   if( !threadset[i].busy ){
00261     Tcl_AppendResult(interp, "no such thread", 0);
00262     return TCL_ERROR;
00263   }
00264   thread_wait(&threadset[i]);
00265   sprintf(zBuf, "%d", threadset[i].argc);
00266   Tcl_AppendResult(interp, zBuf, 0);
00267   return TCL_OK;
00268 }
00269 
00270 /*
00271 ** Usage: thread_argv  ID   N
00272 **
00273 ** Wait on the most recent thread_step to complete, then return the
00274 ** value of the N-th columns in the result set.
00275 */
00276 static int tcl_thread_argv(
00277   void *NotUsed,
00278   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00279   int argc,              /* Number of arguments */
00280   const char **argv      /* Text of each argument */
00281 ){
00282   int i;
00283   int n;
00284 
00285   if( argc!=3 ){
00286     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00287        " ID N", 0);
00288     return TCL_ERROR;
00289   }
00290   i = parse_thread_id(interp, argv[1]);
00291   if( i<0 ) return TCL_ERROR;
00292   if( !threadset[i].busy ){
00293     Tcl_AppendResult(interp, "no such thread", 0);
00294     return TCL_ERROR;
00295   }
00296   if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
00297   thread_wait(&threadset[i]);
00298   if( n<0 || n>=threadset[i].argc ){
00299     Tcl_AppendResult(interp, "column number out of range", 0);
00300     return TCL_ERROR;
00301   }
00302   Tcl_AppendResult(interp, threadset[i].argv[n], 0);
00303   return TCL_OK;
00304 }
00305 
00306 /*
00307 ** Usage: thread_colname  ID   N
00308 **
00309 ** Wait on the most recent thread_step to complete, then return the
00310 ** name of the N-th columns in the result set.
00311 */
00312 static int tcl_thread_colname(
00313   void *NotUsed,
00314   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00315   int argc,              /* Number of arguments */
00316   const char **argv      /* Text of each argument */
00317 ){
00318   int i;
00319   int n;
00320 
00321   if( argc!=3 ){
00322     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00323        " ID N", 0);
00324     return TCL_ERROR;
00325   }
00326   i = parse_thread_id(interp, argv[1]);
00327   if( i<0 ) return TCL_ERROR;
00328   if( !threadset[i].busy ){
00329     Tcl_AppendResult(interp, "no such thread", 0);
00330     return TCL_ERROR;
00331   }
00332   if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR;
00333   thread_wait(&threadset[i]);
00334   if( n<0 || n>=threadset[i].argc ){
00335     Tcl_AppendResult(interp, "column number out of range", 0);
00336     return TCL_ERROR;
00337   }
00338   Tcl_AppendResult(interp, threadset[i].colv[n], 0);
00339   return TCL_OK;
00340 }
00341 
00342 /*
00343 ** Usage: thread_result  ID
00344 **
00345 ** Wait on the most recent operation to complete, then return the
00346 ** result code from that operation.
00347 */
00348 static int tcl_thread_result(
00349   void *NotUsed,
00350   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00351   int argc,              /* Number of arguments */
00352   const char **argv      /* Text of each argument */
00353 ){
00354   int i;
00355   const char *zName;
00356 
00357   if( argc!=2 ){
00358     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00359        " ID", 0);
00360     return TCL_ERROR;
00361   }
00362   i = parse_thread_id(interp, argv[1]);
00363   if( i<0 ) return TCL_ERROR;
00364   if( !threadset[i].busy ){
00365     Tcl_AppendResult(interp, "no such thread", 0);
00366     return TCL_ERROR;
00367   }
00368   thread_wait(&threadset[i]);
00369   switch( threadset[i].rc ){
00370     case SQLITE_OK:         zName = "SQLITE_OK";          break;
00371     case SQLITE_ERROR:      zName = "SQLITE_ERROR";       break;
00372     case SQLITE_INTERNAL:   zName = "SQLITE_INTERNAL";    break;
00373     case SQLITE_PERM:       zName = "SQLITE_PERM";        break;
00374     case SQLITE_ABORT:      zName = "SQLITE_ABORT";       break;
00375     case SQLITE_BUSY:       zName = "SQLITE_BUSY";        break;
00376     case SQLITE_LOCKED:     zName = "SQLITE_LOCKED";      break;
00377     case SQLITE_NOMEM:      zName = "SQLITE_NOMEM";       break;
00378     case SQLITE_READONLY:   zName = "SQLITE_READONLY";    break;
00379     case SQLITE_INTERRUPT:  zName = "SQLITE_INTERRUPT";   break;
00380     case SQLITE_IOERR:      zName = "SQLITE_IOERR";       break;
00381     case SQLITE_CORRUPT:    zName = "SQLITE_CORRUPT";     break;
00382     case SQLITE_NOTFOUND:   zName = "SQLITE_NOTFOUND";    break;
00383     case SQLITE_FULL:       zName = "SQLITE_FULL";        break;
00384     case SQLITE_CANTOPEN:   zName = "SQLITE_CANTOPEN";    break;
00385     case SQLITE_PROTOCOL:   zName = "SQLITE_PROTOCOL";    break;
00386     case SQLITE_EMPTY:      zName = "SQLITE_EMPTY";       break;
00387     case SQLITE_SCHEMA:     zName = "SQLITE_SCHEMA";      break;
00388     case SQLITE_TOOBIG:     zName = "SQLITE_TOOBIG";      break;
00389     case SQLITE_CONSTRAINT: zName = "SQLITE_CONSTRAINT";  break;
00390     case SQLITE_MISMATCH:   zName = "SQLITE_MISMATCH";    break;
00391     case SQLITE_MISUSE:     zName = "SQLITE_MISUSE";      break;
00392     case SQLITE_NOLFS:      zName = "SQLITE_NOLFS";       break;
00393     case SQLITE_AUTH:       zName = "SQLITE_AUTH";        break;
00394     case SQLITE_FORMAT:     zName = "SQLITE_FORMAT";      break;
00395     case SQLITE_RANGE:      zName = "SQLITE_RANGE";       break;
00396     case SQLITE_ROW:        zName = "SQLITE_ROW";         break;
00397     case SQLITE_DONE:       zName = "SQLITE_DONE";        break;
00398     default:                zName = "SQLITE_Unknown";     break;
00399   }
00400   Tcl_AppendResult(interp, zName, 0);
00401   return TCL_OK;
00402 }
00403 
00404 /*
00405 ** Usage: thread_error  ID
00406 **
00407 ** Wait on the most recent operation to complete, then return the
00408 ** error string.
00409 */
00410 static int tcl_thread_error(
00411   void *NotUsed,
00412   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00413   int argc,              /* Number of arguments */
00414   const char **argv      /* Text of each argument */
00415 ){
00416   int i;
00417 
00418   if( argc!=2 ){
00419     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00420        " ID", 0);
00421     return TCL_ERROR;
00422   }
00423   i = parse_thread_id(interp, argv[1]);
00424   if( i<0 ) return TCL_ERROR;
00425   if( !threadset[i].busy ){
00426     Tcl_AppendResult(interp, "no such thread", 0);
00427     return TCL_ERROR;
00428   }
00429   thread_wait(&threadset[i]);
00430   Tcl_AppendResult(interp, threadset[i].zErr, 0);
00431   return TCL_OK;
00432 }
00433 
00434 /*
00435 ** This procedure runs in the thread to compile an SQL statement.
00436 */
00437 static void do_compile(Thread *p){
00438   if( p->db==0 ){
00439     p->zErr = p->zStaticErr = "no database is open";
00440     p->rc = SQLITE_ERROR;
00441     return;
00442   }
00443   if( p->vm ){
00444     sqlite_finalize(p->vm, 0);
00445     p->vm = 0;
00446   }
00447   p->rc = sqlite_compile(p->db, p->zArg, 0, &p->vm, &p->zErr);
00448 }
00449 
00450 /*
00451 ** Usage: thread_compile ID SQL
00452 **
00453 ** Compile a new virtual machine.
00454 */
00455 static int tcl_thread_compile(
00456   void *NotUsed,
00457   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00458   int argc,              /* Number of arguments */
00459   const char **argv      /* Text of each argument */
00460 ){
00461   int i;
00462   if( argc!=3 ){
00463     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00464        " ID SQL", 0);
00465     return TCL_ERROR;
00466   }
00467   i = parse_thread_id(interp, argv[1]);
00468   if( i<0 ) return TCL_ERROR;
00469   if( !threadset[i].busy ){
00470     Tcl_AppendResult(interp, "no such thread", 0);
00471     return TCL_ERROR;
00472   }
00473   thread_wait(&threadset[i]);
00474   threadset[i].xOp = do_compile;
00475   sqliteFree(threadset[i].zArg);
00476   threadset[i].zArg = sqliteStrDup(argv[2]);
00477   threadset[i].opnum++;
00478   return TCL_OK;
00479 }
00480 
00481 /*
00482 ** This procedure runs in the thread to step the virtual machine.
00483 */
00484 static void do_step(Thread *p){
00485   if( p->vm==0 ){
00486     p->zErr = p->zStaticErr = "no virtual machine available";
00487     p->rc = SQLITE_ERROR;
00488     return;
00489   }
00490   p->rc = sqlite_step(p->vm, &p->argc, &p->argv, &p->colv);
00491 }
00492 
00493 /*
00494 ** Usage: thread_step ID
00495 **
00496 ** Advance the virtual machine by one step
00497 */
00498 static int tcl_thread_step(
00499   void *NotUsed,
00500   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00501   int argc,              /* Number of arguments */
00502   const char **argv      /* Text of each argument */
00503 ){
00504   int i;
00505   if( argc!=2 ){
00506     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00507        " IDL", 0);
00508     return TCL_ERROR;
00509   }
00510   i = parse_thread_id(interp, argv[1]);
00511   if( i<0 ) return TCL_ERROR;
00512   if( !threadset[i].busy ){
00513     Tcl_AppendResult(interp, "no such thread", 0);
00514     return TCL_ERROR;
00515   }
00516   thread_wait(&threadset[i]);
00517   threadset[i].xOp = do_step;
00518   threadset[i].opnum++;
00519   return TCL_OK;
00520 }
00521 
00522 /*
00523 ** This procedure runs in the thread to finalize a virtual machine.
00524 */
00525 static void do_finalize(Thread *p){
00526   if( p->vm==0 ){
00527     p->zErr = p->zStaticErr = "no virtual machine available";
00528     p->rc = SQLITE_ERROR;
00529     return;
00530   }
00531   p->rc = sqlite_finalize(p->vm, &p->zErr);
00532   p->vm = 0;
00533 }
00534 
00535 /*
00536 ** Usage: thread_finalize ID
00537 **
00538 ** Finalize the virtual machine.
00539 */
00540 static int tcl_thread_finalize(
00541   void *NotUsed,
00542   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00543   int argc,              /* Number of arguments */
00544   const char **argv      /* Text of each argument */
00545 ){
00546   int i;
00547   if( argc!=2 ){
00548     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00549        " IDL", 0);
00550     return TCL_ERROR;
00551   }
00552   i = parse_thread_id(interp, argv[1]);
00553   if( i<0 ) return TCL_ERROR;
00554   if( !threadset[i].busy ){
00555     Tcl_AppendResult(interp, "no such thread", 0);
00556     return TCL_ERROR;
00557   }
00558   thread_wait(&threadset[i]);
00559   threadset[i].xOp = do_finalize;
00560   sqliteFree(threadset[i].zArg);
00561   threadset[i].zArg = 0;
00562   threadset[i].opnum++;
00563   return TCL_OK;
00564 }
00565 
00566 /*
00567 ** Usage: thread_swap ID ID
00568 **
00569 ** Interchange the sqlite* pointer between two threads.
00570 */
00571 static int tcl_thread_swap(
00572   void *NotUsed,
00573   Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
00574   int argc,              /* Number of arguments */
00575   const char **argv      /* Text of each argument */
00576 ){
00577   int i, j;
00578   sqlite *temp;
00579   if( argc!=3 ){
00580     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
00581        " ID1 ID2", 0);
00582     return TCL_ERROR;
00583   }
00584   i = parse_thread_id(interp, argv[1]);
00585   if( i<0 ) return TCL_ERROR;
00586   if( !threadset[i].busy ){
00587     Tcl_AppendResult(interp, "no such thread", 0);
00588     return TCL_ERROR;
00589   }
00590   thread_wait(&threadset[i]);
00591   j = parse_thread_id(interp, argv[2]);
00592   if( j<0 ) return TCL_ERROR;
00593   if( !threadset[j].busy ){
00594     Tcl_AppendResult(interp, "no such thread", 0);
00595     return TCL_ERROR;
00596   }
00597   thread_wait(&threadset[j]);
00598   temp = threadset[i].db;
00599   threadset[i].db = threadset[j].db;
00600   threadset[j].db = temp;
00601   return TCL_OK;
00602 }
00603 
00604 /*
00605 ** Register commands with the TCL interpreter.
00606 */
00607 int Sqlitetest4_Init(Tcl_Interp *interp){
00608   static struct {
00609      char *zName;
00610      Tcl_CmdProc *xProc;
00611   } aCmd[] = {
00612      { "thread_create",     (Tcl_CmdProc*)tcl_thread_create     },
00613      { "thread_wait",       (Tcl_CmdProc*)tcl_thread_wait       },
00614      { "thread_halt",       (Tcl_CmdProc*)tcl_thread_halt       },
00615      { "thread_argc",       (Tcl_CmdProc*)tcl_thread_argc       },
00616      { "thread_argv",       (Tcl_CmdProc*)tcl_thread_argv       },
00617      { "thread_colname",    (Tcl_CmdProc*)tcl_thread_colname    },
00618      { "thread_result",     (Tcl_CmdProc*)tcl_thread_result     },
00619      { "thread_error",      (Tcl_CmdProc*)tcl_thread_error      },
00620      { "thread_compile",    (Tcl_CmdProc*)tcl_thread_compile    },
00621      { "thread_step",       (Tcl_CmdProc*)tcl_thread_step       },
00622      { "thread_finalize",   (Tcl_CmdProc*)tcl_thread_finalize   },
00623      { "thread_swap",       (Tcl_CmdProc*)tcl_thread_swap       },
00624   };
00625   int i;
00626 
00627   for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
00628     Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
00629   }
00630   return TCL_OK;
00631 }
00632 #else
00633 int Sqlitetest4_Init(Tcl_Interp *interp){ return TCL_OK; }
00634 #endif /* OS_UNIX */

Generated on Sun Dec 25 12:29:52 2005 for sqlite 2.8.17 by  doxygen 1.4.2