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 extern const char *sqlite3TestErrorName(int rc); 380 381 /* 382 ** Usage: client_result ID 383 ** 384 ** Wait on the most recent operation to complete, then return the 385 ** result code from that operation. 386 */ 387 static int tcl_client_result( 388 void *NotUsed, 389 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 390 int argc, /* Number of arguments */ 391 const char **argv /* Text of each argument */ 392 ){ 393 int i; 394 const char *zName; 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_client_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 client_wait(&threadset[i]); 408 zName = sqlite3TestErrorName(threadset[i].rc); 409 Tcl_AppendResult(interp, zName, 0); 410 return TCL_OK; 411 } 412 413 /* 414 ** Usage: client_error ID 415 ** 416 ** Wait on the most recent operation to complete, then return the 417 ** error string. 418 */ 419 static int tcl_client_error( 420 void *NotUsed, 421 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 422 int argc, /* Number of arguments */ 423 const char **argv /* Text of each argument */ 424 ){ 425 int i; 426 427 if( argc!=2 ){ 428 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 429 " ID", 0); 430 return TCL_ERROR; 431 } 432 i = parse_client_id(interp, argv[1]); 433 if( i<0 ) return TCL_ERROR; 434 if( !threadset[i].busy ){ 435 Tcl_AppendResult(interp, "no such thread", 0); 436 return TCL_ERROR; 437 } 438 client_wait(&threadset[i]); 439 Tcl_AppendResult(interp, threadset[i].zErr, 0); 440 return TCL_OK; 441 } 442 443 /* 444 ** This procedure runs in the thread to compile an SQL statement. 445 */ 446 static void do_compile(Thread *p){ 447 if( p->db==0 ){ 448 p->zErr = p->zStaticErr = "no database is open"; 449 p->rc = SQLITE_ERROR; 450 return; 451 } 452 if( p->pStmt ){ 453 sqlite3_client_finalize(p->pStmt); 454 p->pStmt = 0; 455 } 456 p->rc = sqlite3_client_prepare(p->db, p->zArg, -1, &p->pStmt, 0); 457 } 458 459 /* 460 ** Usage: client_compile ID SQL 461 ** 462 ** Compile a new virtual machine. 463 */ 464 static int tcl_client_compile( 465 void *NotUsed, 466 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 467 int argc, /* Number of arguments */ 468 const char **argv /* Text of each argument */ 469 ){ 470 int i; 471 if( argc!=3 ){ 472 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 473 " ID SQL", 0); 474 return TCL_ERROR; 475 } 476 i = parse_client_id(interp, argv[1]); 477 if( i<0 ) return TCL_ERROR; 478 if( !threadset[i].busy ){ 479 Tcl_AppendResult(interp, "no such thread", 0); 480 return TCL_ERROR; 481 } 482 client_wait(&threadset[i]); 483 threadset[i].xOp = do_compile; 484 sqlite3_free(threadset[i].zArg); 485 threadset[i].zArg = sqlite3_mprintf("%s", argv[2]); 486 threadset[i].opnum++; 487 return TCL_OK; 488 } 489 490 /* 491 ** This procedure runs in the thread to step the virtual machine. 492 */ 493 static void do_step(Thread *p){ 494 int i; 495 if( p->pStmt==0 ){ 496 p->zErr = p->zStaticErr = "no virtual machine available"; 497 p->rc = SQLITE_ERROR; 498 return; 499 } 500 p->rc = sqlite3_client_step(p->pStmt); 501 if( p->rc==SQLITE_ROW ){ 502 p->argc = sqlite3_column_count(p->pStmt); 503 for(i=0; i<sqlite3_data_count(p->pStmt); i++){ 504 p->argv[i] = (char*)sqlite3_column_text(p->pStmt, i); 505 } 506 for(i=0; i<p->argc; i++){ 507 p->colv[i] = sqlite3_column_name(p->pStmt, i); 508 } 509 } 510 } 511 512 /* 513 ** Usage: client_step ID 514 ** 515 ** Advance the virtual machine by one step 516 */ 517 static int tcl_client_step( 518 void *NotUsed, 519 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 520 int argc, /* Number of arguments */ 521 const char **argv /* Text of each argument */ 522 ){ 523 int i; 524 if( argc!=2 ){ 525 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 526 " IDL", 0); 527 return TCL_ERROR; 528 } 529 i = parse_client_id(interp, argv[1]); 530 if( i<0 ) return TCL_ERROR; 531 if( !threadset[i].busy ){ 532 Tcl_AppendResult(interp, "no such thread", 0); 533 return TCL_ERROR; 534 } 535 client_wait(&threadset[i]); 536 threadset[i].xOp = do_step; 537 threadset[i].opnum++; 538 return TCL_OK; 539 } 540 541 /* 542 ** This procedure runs in the thread to finalize a virtual machine. 543 */ 544 static void do_finalize(Thread *p){ 545 if( p->pStmt==0 ){ 546 p->zErr = p->zStaticErr = "no virtual machine available"; 547 p->rc = SQLITE_ERROR; 548 return; 549 } 550 p->rc = sqlite3_client_finalize(p->pStmt); 551 p->pStmt = 0; 552 } 553 554 /* 555 ** Usage: client_finalize ID 556 ** 557 ** Finalize the virtual machine. 558 */ 559 static int tcl_client_finalize( 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; 566 if( argc!=2 ){ 567 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 568 " IDL", 0); 569 return TCL_ERROR; 570 } 571 i = parse_client_id(interp, argv[1]); 572 if( i<0 ) return TCL_ERROR; 573 if( !threadset[i].busy ){ 574 Tcl_AppendResult(interp, "no such thread", 0); 575 return TCL_ERROR; 576 } 577 client_wait(&threadset[i]); 578 threadset[i].xOp = do_finalize; 579 sqlite3_free(threadset[i].zArg); 580 threadset[i].zArg = 0; 581 threadset[i].opnum++; 582 return TCL_OK; 583 } 584 585 /* 586 ** This procedure runs in the thread to reset a virtual machine. 587 */ 588 static void do_reset(Thread *p){ 589 if( p->pStmt==0 ){ 590 p->zErr = p->zStaticErr = "no virtual machine available"; 591 p->rc = SQLITE_ERROR; 592 return; 593 } 594 p->rc = sqlite3_client_reset(p->pStmt); 595 p->pStmt = 0; 596 } 597 598 /* 599 ** Usage: client_reset ID 600 ** 601 ** Finalize the virtual machine. 602 */ 603 static int tcl_client_reset( 604 void *NotUsed, 605 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 606 int argc, /* Number of arguments */ 607 const char **argv /* Text of each argument */ 608 ){ 609 int i; 610 if( argc!=2 ){ 611 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 612 " IDL", 0); 613 return TCL_ERROR; 614 } 615 i = parse_client_id(interp, argv[1]); 616 if( i<0 ) return TCL_ERROR; 617 if( !threadset[i].busy ){ 618 Tcl_AppendResult(interp, "no such thread", 0); 619 return TCL_ERROR; 620 } 621 client_wait(&threadset[i]); 622 threadset[i].xOp = do_reset; 623 sqlite3_free(threadset[i].zArg); 624 threadset[i].zArg = 0; 625 threadset[i].opnum++; 626 return TCL_OK; 627 } 628 629 /* 630 ** Usage: client_swap ID ID 631 ** 632 ** Interchange the sqlite* pointer between two threads. 633 */ 634 static int tcl_client_swap( 635 void *NotUsed, 636 Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ 637 int argc, /* Number of arguments */ 638 const char **argv /* Text of each argument */ 639 ){ 640 int i, j; 641 sqlite3 *temp; 642 if( argc!=3 ){ 643 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], 644 " ID1 ID2", 0); 645 return TCL_ERROR; 646 } 647 i = parse_client_id(interp, argv[1]); 648 if( i<0 ) return TCL_ERROR; 649 if( !threadset[i].busy ){ 650 Tcl_AppendResult(interp, "no such thread", 0); 651 return TCL_ERROR; 652 } 653 client_wait(&threadset[i]); 654 j = parse_client_id(interp, argv[2]); 655 if( j<0 ) return TCL_ERROR; 656 if( !threadset[j].busy ){ 657 Tcl_AppendResult(interp, "no such thread", 0); 658 return TCL_ERROR; 659 } 660 client_wait(&threadset[j]); 661 temp = threadset[i].db; 662 threadset[i].db = threadset[j].db; 663 threadset[j].db = temp; 664 return TCL_OK; 665 } 666 667 /* 668 ** Register commands with the TCL interpreter. 669 */ 670 int Sqlitetest7_Init(Tcl_Interp *interp){ 671 static struct { 672 char *zName; 673 Tcl_CmdProc *xProc; 674 } aCmd[] = { 675 { "client_create", (Tcl_CmdProc*)tcl_client_create }, 676 { "client_wait", (Tcl_CmdProc*)tcl_client_wait }, 677 { "client_halt", (Tcl_CmdProc*)tcl_client_halt }, 678 { "client_argc", (Tcl_CmdProc*)tcl_client_argc }, 679 { "client_argv", (Tcl_CmdProc*)tcl_client_argv }, 680 { "client_colname", (Tcl_CmdProc*)tcl_client_colname }, 681 { "client_result", (Tcl_CmdProc*)tcl_client_result }, 682 { "client_error", (Tcl_CmdProc*)tcl_client_error }, 683 { "client_compile", (Tcl_CmdProc*)tcl_client_compile }, 684 { "client_step", (Tcl_CmdProc*)tcl_client_step }, 685 { "client_reset", (Tcl_CmdProc*)tcl_client_reset }, 686 { "client_finalize", (Tcl_CmdProc*)tcl_client_finalize }, 687 { "client_swap", (Tcl_CmdProc*)tcl_client_swap }, 688 }; 689 int i; 690 691 for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){ 692 Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0); 693 } 694 return TCL_OK; 695 } 696 #else 697 int Sqlitetest7_Init(Tcl_Interp *interp){ return TCL_OK; } 698 #endif /* SQLITE_OS_UNIX */ 699