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 SQLite library in a multithreaded environment. 13 */ 14 #include "sqliteInt.h" 15 #include "tcl.h" 16 #if SQLITE_OS_UNIX && SQLITE_THREADSAFE 17 #include <stdlib.h> 18 #include <string.h> 19 #include <pthread.h> 20 #include <sched.h> 21 #include <ctype.h> 22 23 extern const char *sqlite3ErrName(int); 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 = sqlite3_mprintf("%s", 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 sqlite3_snprintf(sizeof(zBuf), 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 zName = sqlite3ErrName(threadset[i].rc); 378 Tcl_AppendResult(interp, zName, 0); 379 return TCL_OK; 380 } 381 382 /* 383 ** Usage: thread_error ID 384 ** 385 ** Wait on the most recent operation to complete, then return the 386 ** error string. 387 */ 388 static int tcl_thread_error( 389 void *NotUsed, 390 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 391 int argc, /* Number of arguments */ 392 const char **argv /* Text of each argument */ 393 ){ 394 int i; 395 396 if( argc!=2 ){ 397 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 398 " ID", 0); 399 return TCL_ERROR; 400 } 401 i = parse_thread_id(interp, argv[1]); 402 if( i<0 ) return TCL_ERROR; 403 if( !threadset[i].busy ){ 404 Tcl_AppendResult(interp, "no such thread", 0); 405 return TCL_ERROR; 406 } 407 thread_wait(&threadset[i]); 408 Tcl_AppendResult(interp, threadset[i].zErr, 0); 409 return TCL_OK; 410 } 411 412 /* 413 ** This procedure runs in the thread to compile an SQL statement. 414 */ 415 static void do_compile(Thread *p){ 416 if( p->db==0 ){ 417 p->zErr = p->zStaticErr = "no database is open"; 418 p->rc = SQLITE_ERROR; 419 return; 420 } 421 if( p->pStmt ){ 422 sqlite3_finalize(p->pStmt); 423 p->pStmt = 0; 424 } 425 p->rc = sqlite3_prepare(p->db, p->zArg, -1, &p->pStmt, 0); 426 } 427 428 /* 429 ** Usage: thread_compile ID SQL 430 ** 431 ** Compile a new virtual machine. 432 */ 433 static int tcl_thread_compile( 434 void *NotUsed, 435 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 436 int argc, /* Number of arguments */ 437 const char **argv /* Text of each argument */ 438 ){ 439 int i; 440 if( argc!=3 ){ 441 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 442 " ID SQL", 0); 443 return TCL_ERROR; 444 } 445 i = parse_thread_id(interp, argv[1]); 446 if( i<0 ) return TCL_ERROR; 447 if( !threadset[i].busy ){ 448 Tcl_AppendResult(interp, "no such thread", 0); 449 return TCL_ERROR; 450 } 451 thread_wait(&threadset[i]); 452 threadset[i].xOp = do_compile; 453 sqlite3_free(threadset[i].zArg); 454 threadset[i].zArg = sqlite3_mprintf("%s", argv[2]); 455 threadset[i].opnum++; 456 return TCL_OK; 457 } 458 459 /* 460 ** This procedure runs in the thread to step the virtual machine. 461 */ 462 static void do_step(Thread *p){ 463 int i; 464 if( p->pStmt==0 ){ 465 p->zErr = p->zStaticErr = "no virtual machine available"; 466 p->rc = SQLITE_ERROR; 467 return; 468 } 469 p->rc = sqlite3_step(p->pStmt); 470 if( p->rc==SQLITE_ROW ){ 471 p->argc = sqlite3_column_count(p->pStmt); 472 for(i=0; i<sqlite3_data_count(p->pStmt); i++){ 473 p->argv[i] = (char*)sqlite3_column_text(p->pStmt, i); 474 } 475 for(i=0; i<p->argc; i++){ 476 p->colv[i] = sqlite3_column_name(p->pStmt, i); 477 } 478 } 479 } 480 481 /* 482 ** Usage: thread_step ID 483 ** 484 ** Advance the virtual machine by one step 485 */ 486 static int tcl_thread_step( 487 void *NotUsed, 488 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 489 int argc, /* Number of arguments */ 490 const char **argv /* Text of each argument */ 491 ){ 492 int i; 493 if( argc!=2 ){ 494 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 495 " IDL", 0); 496 return TCL_ERROR; 497 } 498 i = parse_thread_id(interp, argv[1]); 499 if( i<0 ) return TCL_ERROR; 500 if( !threadset[i].busy ){ 501 Tcl_AppendResult(interp, "no such thread", 0); 502 return TCL_ERROR; 503 } 504 thread_wait(&threadset[i]); 505 threadset[i].xOp = do_step; 506 threadset[i].opnum++; 507 return TCL_OK; 508 } 509 510 /* 511 ** This procedure runs in the thread to finalize a virtual machine. 512 */ 513 static void do_finalize(Thread *p){ 514 if( p->pStmt==0 ){ 515 p->zErr = p->zStaticErr = "no virtual machine available"; 516 p->rc = SQLITE_ERROR; 517 return; 518 } 519 p->rc = sqlite3_finalize(p->pStmt); 520 p->pStmt = 0; 521 } 522 523 /* 524 ** Usage: thread_finalize ID 525 ** 526 ** Finalize the virtual machine. 527 */ 528 static int tcl_thread_finalize( 529 void *NotUsed, 530 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 531 int argc, /* Number of arguments */ 532 const char **argv /* Text of each argument */ 533 ){ 534 int i; 535 if( argc!=2 ){ 536 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 537 " IDL", 0); 538 return TCL_ERROR; 539 } 540 i = parse_thread_id(interp, argv[1]); 541 if( i<0 ) return TCL_ERROR; 542 if( !threadset[i].busy ){ 543 Tcl_AppendResult(interp, "no such thread", 0); 544 return TCL_ERROR; 545 } 546 thread_wait(&threadset[i]); 547 threadset[i].xOp = do_finalize; 548 sqlite3_free(threadset[i].zArg); 549 threadset[i].zArg = 0; 550 threadset[i].opnum++; 551 return TCL_OK; 552 } 553 554 /* 555 ** Usage: thread_swap ID ID 556 ** 557 ** Interchange the sqlite* pointer between two threads. 558 */ 559 static int tcl_thread_swap( 560 void *NotUsed, 561 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 562 int argc, /* Number of arguments */ 563 const char **argv /* Text of each argument */ 564 ){ 565 int i, j; 566 sqlite3 *temp; 567 if( argc!=3 ){ 568 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 569 " ID1 ID2", 0); 570 return TCL_ERROR; 571 } 572 i = parse_thread_id(interp, argv[1]); 573 if( i<0 ) return TCL_ERROR; 574 if( !threadset[i].busy ){ 575 Tcl_AppendResult(interp, "no such thread", 0); 576 return TCL_ERROR; 577 } 578 thread_wait(&threadset[i]); 579 j = parse_thread_id(interp, argv[2]); 580 if( j<0 ) return TCL_ERROR; 581 if( !threadset[j].busy ){ 582 Tcl_AppendResult(interp, "no such thread", 0); 583 return TCL_ERROR; 584 } 585 thread_wait(&threadset[j]); 586 temp = threadset[i].db; 587 threadset[i].db = threadset[j].db; 588 threadset[j].db = temp; 589 return TCL_OK; 590 } 591 592 /* 593 ** Usage: thread_db_get ID 594 ** 595 ** Return the database connection pointer for the given thread. Then 596 ** remove the pointer from the thread itself. Afterwards, the thread 597 ** can be stopped and the connection can be used by the main thread. 598 */ 599 static int tcl_thread_db_get( 600 void *NotUsed, 601 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 602 int argc, /* Number of arguments */ 603 const char **argv /* Text of each argument */ 604 ){ 605 int i; 606 char zBuf[100]; 607 extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*); 608 if( argc!=2 ){ 609 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 610 " ID", 0); 611 return TCL_ERROR; 612 } 613 i = parse_thread_id(interp, argv[1]); 614 if( i<0 ) return TCL_ERROR; 615 if( !threadset[i].busy ){ 616 Tcl_AppendResult(interp, "no such thread", 0); 617 return TCL_ERROR; 618 } 619 thread_wait(&threadset[i]); 620 sqlite3TestMakePointerStr(interp, zBuf, threadset[i].db); 621 threadset[i].db = 0; 622 Tcl_AppendResult(interp, zBuf, (char*)0); 623 return TCL_OK; 624 } 625 626 /* 627 ** Usage: thread_db_put ID DB 628 ** 629 */ 630 static int tcl_thread_db_put( 631 void *NotUsed, 632 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 633 int argc, /* Number of arguments */ 634 const char **argv /* Text of each argument */ 635 ){ 636 int i; 637 extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*); 638 extern void *sqlite3TestTextToPtr(const char *); 639 if( argc!=3 ){ 640 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 641 " ID DB", 0); 642 return TCL_ERROR; 643 } 644 i = parse_thread_id(interp, argv[1]); 645 if( i<0 ) return TCL_ERROR; 646 if( !threadset[i].busy ){ 647 Tcl_AppendResult(interp, "no such thread", 0); 648 return TCL_ERROR; 649 } 650 thread_wait(&threadset[i]); 651 assert( !threadset[i].db ); 652 threadset[i].db = (sqlite3*)sqlite3TestTextToPtr(argv[2]); 653 return TCL_OK; 654 } 655 656 /* 657 ** Usage: thread_stmt_get ID 658 ** 659 ** Return the database stmt pointer for the given thread. Then 660 ** remove the pointer from the thread itself. 661 */ 662 static int tcl_thread_stmt_get( 663 void *NotUsed, 664 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 665 int argc, /* Number of arguments */ 666 const char **argv /* Text of each argument */ 667 ){ 668 int i; 669 char zBuf[100]; 670 extern int sqlite3TestMakePointerStr(Tcl_Interp*, char*, void*); 671 if( argc!=2 ){ 672 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 673 " ID", 0); 674 return TCL_ERROR; 675 } 676 i = parse_thread_id(interp, argv[1]); 677 if( i<0 ) return TCL_ERROR; 678 if( !threadset[i].busy ){ 679 Tcl_AppendResult(interp, "no such thread", 0); 680 return TCL_ERROR; 681 } 682 thread_wait(&threadset[i]); 683 sqlite3TestMakePointerStr(interp, zBuf, threadset[i].pStmt); 684 threadset[i].pStmt = 0; 685 Tcl_AppendResult(interp, zBuf, (char*)0); 686 return TCL_OK; 687 } 688 689 /* 690 ** Register commands with the TCL interpreter. 691 */ 692 int Sqlitetest4_Init(Tcl_Interp *interp){ 693 static struct { 694 char *zName; 695 Tcl_CmdProc *xProc; 696 } aCmd[] = { 697 { "thread_create", (Tcl_CmdProc*)tcl_thread_create }, 698 { "thread_wait", (Tcl_CmdProc*)tcl_thread_wait }, 699 { "thread_halt", (Tcl_CmdProc*)tcl_thread_halt }, 700 { "thread_argc", (Tcl_CmdProc*)tcl_thread_argc }, 701 { "thread_argv", (Tcl_CmdProc*)tcl_thread_argv }, 702 { "thread_colname", (Tcl_CmdProc*)tcl_thread_colname }, 703 { "thread_result", (Tcl_CmdProc*)tcl_thread_result }, 704 { "thread_error", (Tcl_CmdProc*)tcl_thread_error }, 705 { "thread_compile", (Tcl_CmdProc*)tcl_thread_compile }, 706 { "thread_step", (Tcl_CmdProc*)tcl_thread_step }, 707 { "thread_finalize", (Tcl_CmdProc*)tcl_thread_finalize }, 708 { "thread_swap", (Tcl_CmdProc*)tcl_thread_swap }, 709 { "thread_db_get", (Tcl_CmdProc*)tcl_thread_db_get }, 710 { "thread_db_put", (Tcl_CmdProc*)tcl_thread_db_put }, 711 { "thread_stmt_get", (Tcl_CmdProc*)tcl_thread_stmt_get }, 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 Sqlitetest4_Init(Tcl_Interp *interp){ return TCL_OK; } 722 #endif /* SQLITE_OS_UNIX */ 723