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