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