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