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*/ 34int sqlite3_client_open(const char*, sqlite3**); 35int sqlite3_client_prepare(sqlite3*,const char*,int, 36 sqlite3_stmt**,const char**); 37int sqlite3_client_step(sqlite3_stmt*); 38int sqlite3_client_reset(sqlite3_stmt*); 39int sqlite3_client_finalize(sqlite3_stmt*); 40int sqlite3_client_close(sqlite3*); 41int sqlite3_server_start(void); 42int sqlite3_server_stop(void); 43 44/* 45** Each thread is controlled by an instance of the following 46** structure. 47*/ 48typedef struct Thread Thread; 49struct 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 78static Thread threadset[N_THREAD]; 79 80/* 81** The main loop for a thread. Threads use busy waiting. 82*/ 83static 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*/ 130static 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*/ 144static 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*/ 185static 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*/ 194static 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*/ 220static 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*/ 238static 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*/ 281static 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*/ 313static 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*/ 349static 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*/ 385static 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*/ 444static 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*/ 471static 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*/ 489static 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*/ 518static 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*/ 542static 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*/ 569static 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*/ 584static 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*/ 613static 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*/ 628static 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*/ 659static 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*/ 695int 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 722int Sqlitetest7_Init(Tcl_Interp *interp){ return TCL_OK; } 723#endif /* SQLITE_OS_UNIX */ 724