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