1 /* 2 ** 2007 September 9 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 ** 13 ** This file contains the implementation of some Tcl commands used to 14 ** test that sqlite3 database handles may be concurrently accessed by 15 ** multiple threads. Right now this only works on unix. 16 ** 17 ** $Id: test_thread.c,v 1.15 2009/03/27 12:32:56 drh Exp $ 18 */ 19 20 #include "sqliteInt.h" 21 #include <tcl.h> 22 23 #if SQLITE_THREADSAFE 24 25 #include <errno.h> 26 27 #if !defined(_MSC_VER) 28 #include <unistd.h> 29 #endif 30 31 /* 32 ** One of these is allocated for each thread created by [sqlthread spawn]. 33 */ 34 typedef struct SqlThread SqlThread; 35 struct SqlThread { 36 Tcl_ThreadId parent; /* Thread id of parent thread */ 37 Tcl_Interp *interp; /* Parent interpreter */ 38 char *zScript; /* The script to execute. */ 39 char *zVarname; /* Varname in parent script */ 40 }; 41 42 /* 43 ** A custom Tcl_Event type used by this module. When the event is 44 ** handled, script zScript is evaluated in interpreter interp. If 45 ** the evaluation throws an exception (returns TCL_ERROR), then the 46 ** error is handled by Tcl_BackgroundError(). If no error occurs, 47 ** the result is simply discarded. 48 */ 49 typedef struct EvalEvent EvalEvent; 50 struct EvalEvent { 51 Tcl_Event base; /* Base class of type Tcl_Event */ 52 char *zScript; /* The script to execute. */ 53 Tcl_Interp *interp; /* The interpreter to execute it in. */ 54 }; 55 56 static Tcl_ObjCmdProc sqlthread_proc; 57 static Tcl_ObjCmdProc clock_seconds_proc; 58 #if defined(SQLITE_OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) 59 static Tcl_ObjCmdProc blocking_step_proc; 60 static Tcl_ObjCmdProc blocking_prepare_v2_proc; 61 #endif 62 int Sqlitetest1_Init(Tcl_Interp *); 63 64 /* Functions from test1.c */ 65 void *sqlite3TestTextToPtr(const char *); 66 const char *sqlite3TestErrorName(int); 67 int getDbPointer(Tcl_Interp *, const char *, sqlite3 **); 68 int sqlite3TestMakePointerStr(Tcl_Interp *, char *, void *); 69 int sqlite3TestErrCode(Tcl_Interp *, sqlite3 *, int); 70 71 /* 72 ** Handler for events of type EvalEvent. 73 */ 74 static int tclScriptEvent(Tcl_Event *evPtr, int flags){ 75 int rc; 76 EvalEvent *p = (EvalEvent *)evPtr; 77 rc = Tcl_Eval(p->interp, p->zScript); 78 if( rc!=TCL_OK ){ 79 Tcl_BackgroundError(p->interp); 80 } 81 UNUSED_PARAMETER(flags); 82 return 1; 83 } 84 85 /* 86 ** Register an EvalEvent to evaluate the script pScript in the 87 ** parent interpreter/thread of SqlThread p. 88 */ 89 static void postToParent(SqlThread *p, Tcl_Obj *pScript){ 90 EvalEvent *pEvent; 91 char *zMsg; 92 int nMsg; 93 94 zMsg = Tcl_GetStringFromObj(pScript, &nMsg); 95 pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1); 96 pEvent->base.nextPtr = 0; 97 pEvent->base.proc = tclScriptEvent; 98 pEvent->zScript = (char *)&pEvent[1]; 99 memcpy(pEvent->zScript, zMsg, nMsg+1); 100 pEvent->interp = p->interp; 101 102 Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL); 103 Tcl_ThreadAlert(p->parent); 104 } 105 106 /* 107 ** The main function for threads created with [sqlthread spawn]. 108 */ 109 static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){ 110 Tcl_Interp *interp; 111 Tcl_Obj *pRes; 112 Tcl_Obj *pList; 113 int rc; 114 SqlThread *p = (SqlThread *)pSqlThread; 115 extern int Sqlitetest_mutex_Init(Tcl_Interp*); 116 117 interp = Tcl_CreateInterp(); 118 Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0); 119 Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0); 120 #if defined(SQLITE_OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) 121 Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0); 122 Tcl_CreateObjCommand(interp, 123 "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0); 124 Tcl_CreateObjCommand(interp, 125 "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0); 126 #endif 127 Sqlitetest1_Init(interp); 128 Sqlitetest_mutex_Init(interp); 129 130 rc = Tcl_Eval(interp, p->zScript); 131 pRes = Tcl_GetObjResult(interp); 132 pList = Tcl_NewObj(); 133 Tcl_IncrRefCount(pList); 134 Tcl_IncrRefCount(pRes); 135 136 if( rc!=TCL_OK ){ 137 Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("error", -1)); 138 Tcl_ListObjAppendElement(interp, pList, pRes); 139 postToParent(p, pList); 140 Tcl_DecrRefCount(pList); 141 pList = Tcl_NewObj(); 142 } 143 144 Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("set", -1)); 145 Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(p->zVarname, -1)); 146 Tcl_ListObjAppendElement(interp, pList, pRes); 147 postToParent(p, pList); 148 149 ckfree((void *)p); 150 Tcl_DecrRefCount(pList); 151 Tcl_DecrRefCount(pRes); 152 Tcl_DeleteInterp(interp); 153 TCL_THREAD_CREATE_RETURN; 154 } 155 156 /* 157 ** sqlthread spawn VARNAME SCRIPT 158 ** 159 ** Spawn a new thread with its own Tcl interpreter and run the 160 ** specified SCRIPT(s) in it. The thread terminates after running 161 ** the script. The result of the script is stored in the variable 162 ** VARNAME. 163 ** 164 ** The caller can wait for the script to terminate using [vwait VARNAME]. 165 */ 166 static int sqlthread_spawn( 167 ClientData clientData, 168 Tcl_Interp *interp, 169 int objc, 170 Tcl_Obj *CONST objv[] 171 ){ 172 Tcl_ThreadId x; 173 SqlThread *pNew; 174 int rc; 175 176 int nVarname; char *zVarname; 177 int nScript; char *zScript; 178 179 /* Parameters for thread creation */ 180 const int nStack = TCL_THREAD_STACK_DEFAULT; 181 const int flags = TCL_THREAD_NOFLAGS; 182 183 assert(objc==4); 184 UNUSED_PARAMETER(clientData); 185 UNUSED_PARAMETER(objc); 186 187 zVarname = Tcl_GetStringFromObj(objv[2], &nVarname); 188 zScript = Tcl_GetStringFromObj(objv[3], &nScript); 189 190 pNew = (SqlThread *)ckalloc(sizeof(SqlThread)+nVarname+nScript+2); 191 pNew->zVarname = (char *)&pNew[1]; 192 pNew->zScript = (char *)&pNew->zVarname[nVarname+1]; 193 memcpy(pNew->zVarname, zVarname, nVarname+1); 194 memcpy(pNew->zScript, zScript, nScript+1); 195 pNew->parent = Tcl_GetCurrentThread(); 196 pNew->interp = interp; 197 198 rc = Tcl_CreateThread(&x, tclScriptThread, (void *)pNew, nStack, flags); 199 if( rc!=TCL_OK ){ 200 Tcl_AppendResult(interp, "Error in Tcl_CreateThread()", 0); 201 ckfree((char *)pNew); 202 return TCL_ERROR; 203 } 204 205 return TCL_OK; 206 } 207 208 /* 209 ** sqlthread parent SCRIPT 210 ** 211 ** This can be called by spawned threads only. It sends the specified 212 ** script back to the parent thread for execution. The result of 213 ** evaluating the SCRIPT is returned. The parent thread must enter 214 ** the event loop for this to work - otherwise the caller will 215 ** block indefinitely. 216 ** 217 ** NOTE: At the moment, this doesn't work. FIXME. 218 */ 219 static int sqlthread_parent( 220 ClientData clientData, 221 Tcl_Interp *interp, 222 int objc, 223 Tcl_Obj *CONST objv[] 224 ){ 225 EvalEvent *pEvent; 226 char *zMsg; 227 int nMsg; 228 SqlThread *p = (SqlThread *)clientData; 229 230 assert(objc==3); 231 UNUSED_PARAMETER(objc); 232 233 if( p==0 ){ 234 Tcl_AppendResult(interp, "no parent thread", 0); 235 return TCL_ERROR; 236 } 237 238 zMsg = Tcl_GetStringFromObj(objv[2], &nMsg); 239 pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1); 240 pEvent->base.nextPtr = 0; 241 pEvent->base.proc = tclScriptEvent; 242 pEvent->zScript = (char *)&pEvent[1]; 243 memcpy(pEvent->zScript, zMsg, nMsg+1); 244 pEvent->interp = p->interp; 245 Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL); 246 Tcl_ThreadAlert(p->parent); 247 248 return TCL_OK; 249 } 250 251 static int xBusy(void *pArg, int nBusy){ 252 UNUSED_PARAMETER(pArg); 253 UNUSED_PARAMETER(nBusy); 254 sqlite3_sleep(50); 255 return 1; /* Try again... */ 256 } 257 258 /* 259 ** sqlthread open 260 ** 261 ** Open a database handle and return the string representation of 262 ** the pointer value. 263 */ 264 static int sqlthread_open( 265 ClientData clientData, 266 Tcl_Interp *interp, 267 int objc, 268 Tcl_Obj *CONST objv[] 269 ){ 270 int sqlite3TestMakePointerStr(Tcl_Interp *interp, char *zPtr, void *p); 271 272 const char *zFilename; 273 sqlite3 *db; 274 int rc; 275 char zBuf[100]; 276 extern void Md5_Register(sqlite3*); 277 278 UNUSED_PARAMETER(clientData); 279 UNUSED_PARAMETER(objc); 280 281 zFilename = Tcl_GetString(objv[2]); 282 rc = sqlite3_open(zFilename, &db); 283 Md5_Register(db); 284 sqlite3_busy_handler(db, xBusy, 0); 285 286 if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR; 287 Tcl_AppendResult(interp, zBuf, 0); 288 289 return TCL_OK; 290 } 291 292 293 /* 294 ** sqlthread open 295 ** 296 ** Return the current thread-id (Tcl_GetCurrentThread()) cast to 297 ** an integer. 298 */ 299 static int sqlthread_id( 300 ClientData clientData, 301 Tcl_Interp *interp, 302 int objc, 303 Tcl_Obj *CONST objv[] 304 ){ 305 Tcl_ThreadId id = Tcl_GetCurrentThread(); 306 Tcl_SetObjResult(interp, Tcl_NewIntObj((int)id)); 307 UNUSED_PARAMETER(clientData); 308 UNUSED_PARAMETER(objc); 309 UNUSED_PARAMETER(objv); 310 return TCL_OK; 311 } 312 313 314 /* 315 ** Dispatch routine for the sub-commands of [sqlthread]. 316 */ 317 static int sqlthread_proc( 318 ClientData clientData, 319 Tcl_Interp *interp, 320 int objc, 321 Tcl_Obj *CONST objv[] 322 ){ 323 struct SubCommand { 324 char *zName; 325 Tcl_ObjCmdProc *xProc; 326 int nArg; 327 char *zUsage; 328 } aSub[] = { 329 {"parent", sqlthread_parent, 1, "SCRIPT"}, 330 {"spawn", sqlthread_spawn, 2, "VARNAME SCRIPT"}, 331 {"open", sqlthread_open, 1, "DBNAME"}, 332 {"id", sqlthread_id, 0, ""}, 333 {0, 0, 0} 334 }; 335 struct SubCommand *pSub; 336 int rc; 337 int iIndex; 338 339 if( objc<2 ){ 340 Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND"); 341 return TCL_ERROR; 342 } 343 344 rc = Tcl_GetIndexFromObjStruct( 345 interp, objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iIndex 346 ); 347 if( rc!=TCL_OK ) return rc; 348 pSub = &aSub[iIndex]; 349 350 if( objc!=(pSub->nArg+2) ){ 351 Tcl_WrongNumArgs(interp, 2, objv, pSub->zUsage); 352 return TCL_ERROR; 353 } 354 355 return pSub->xProc(clientData, interp, objc, objv); 356 } 357 358 /* 359 ** The [clock_seconds] command. This is more or less the same as the 360 ** regular tcl [clock seconds], except that it is available in testfixture 361 ** when linked against both Tcl 8.4 and 8.5. Because [clock seconds] is 362 ** implemented as a script in Tcl 8.5, it is not usually available to 363 ** testfixture. 364 */ 365 static int clock_seconds_proc( 366 ClientData clientData, 367 Tcl_Interp *interp, 368 int objc, 369 Tcl_Obj *CONST objv[] 370 ){ 371 Tcl_Time now; 372 Tcl_GetTime(&now); 373 Tcl_SetObjResult(interp, Tcl_NewIntObj(now.sec)); 374 UNUSED_PARAMETER(clientData); 375 UNUSED_PARAMETER(objc); 376 UNUSED_PARAMETER(objv); 377 return TCL_OK; 378 } 379 380 /************************************************************************* 381 ** This block contains the implementation of the [sqlite3_blocking_step] 382 ** command available to threads created by [sqlthread spawn] commands. It 383 ** is only available on UNIX for now. This is because pthread condition 384 ** variables are used. 385 ** 386 ** The source code for the C functions sqlite3_blocking_step(), 387 ** blocking_step_notify() and the structure UnlockNotification is 388 ** automatically extracted from this file and used as part of the 389 ** documentation for the sqlite3_unlock_notify() API function. This 390 ** should be considered if these functions are to be extended (i.e. to 391 ** support windows) in the future. 392 */ 393 #if defined(SQLITE_OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) 394 395 /* BEGIN_SQLITE_BLOCKING_STEP */ 396 /* This example uses the pthreads API */ 397 #include <pthread.h> 398 399 /* 400 ** A pointer to an instance of this structure is passed as the user-context 401 ** pointer when registering for an unlock-notify callback. 402 */ 403 typedef struct UnlockNotification UnlockNotification; 404 struct UnlockNotification { 405 int fired; /* True after unlock event has occured */ 406 pthread_cond_t cond; /* Condition variable to wait on */ 407 pthread_mutex_t mutex; /* Mutex to protect structure */ 408 }; 409 410 /* 411 ** This function is an unlock-notify callback registered with SQLite. 412 */ 413 static void unlock_notify_cb(void **apArg, int nArg){ 414 int i; 415 for(i=0; i<nArg; i++){ 416 UnlockNotification *p = (UnlockNotification *)apArg[i]; 417 pthread_mutex_lock(&p->mutex); 418 p->fired = 1; 419 pthread_cond_signal(&p->cond); 420 pthread_mutex_unlock(&p->mutex); 421 } 422 } 423 424 /* 425 ** This function assumes that an SQLite API call (either sqlite3_prepare_v2() 426 ** or sqlite3_step()) has just returned SQLITE_LOCKED. The argument is the 427 ** associated database connection. 428 ** 429 ** This function calls sqlite3_unlock_notify() to register for an 430 ** unlock-notify callback, then blocks until that callback is delivered 431 ** and returns SQLITE_OK. The caller should then retry the failed operation. 432 ** 433 ** Or, if sqlite3_unlock_notify() indicates that to block would deadlock 434 ** the system, then this function returns SQLITE_LOCKED immediately. In 435 ** this case the caller should not retry the operation and should roll 436 ** back the current transaction (if any). 437 */ 438 static int wait_for_unlock_notify(sqlite3 *db){ 439 int rc; 440 UnlockNotification un; 441 442 /* Initialize the UnlockNotification structure. */ 443 un.fired = 0; 444 pthread_mutex_init(&un.mutex, 0); 445 pthread_cond_init(&un.cond, 0); 446 447 /* Register for an unlock-notify callback. */ 448 rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un); 449 assert( rc==SQLITE_LOCKED || rc==SQLITE_OK ); 450 451 /* The call to sqlite3_unlock_notify() always returns either SQLITE_LOCKED 452 ** or SQLITE_OK. 453 ** 454 ** If SQLITE_LOCKED was returned, then the system is deadlocked. In this 455 ** case this function needs to return SQLITE_LOCKED to the caller so 456 ** that the current transaction can be rolled back. Otherwise, block 457 ** until the unlock-notify callback is invoked, then return SQLITE_OK. 458 */ 459 if( rc==SQLITE_OK ){ 460 pthread_mutex_lock(&un.mutex); 461 if( !un.fired ){ 462 pthread_cond_wait(&un.cond, &un.mutex); 463 } 464 pthread_mutex_unlock(&un.mutex); 465 } 466 467 /* Destroy the mutex and condition variables. */ 468 pthread_cond_destroy(&un.cond); 469 pthread_mutex_destroy(&un.mutex); 470 471 return rc; 472 } 473 474 /* 475 ** This function is a wrapper around the SQLite function sqlite3_step(). 476 ** It functions in the same way as step(), except that if a required 477 ** shared-cache lock cannot be obtained, this function may block waiting for 478 ** the lock to become available. In this scenario the normal API step() 479 ** function always returns SQLITE_LOCKED. 480 ** 481 ** If this function returns SQLITE_LOCKED, the caller should rollback 482 ** the current transaction (if any) and try again later. Otherwise, the 483 ** system may become deadlocked. 484 */ 485 int sqlite3_blocking_step(sqlite3_stmt *pStmt){ 486 int rc; 487 while( SQLITE_LOCKED==(rc = sqlite3_step(pStmt)) ){ 488 rc = wait_for_unlock_notify(sqlite3_db_handle(pStmt)); 489 if( rc!=SQLITE_OK ) break; 490 sqlite3_reset(pStmt); 491 } 492 return rc; 493 } 494 495 /* 496 ** This function is a wrapper around the SQLite function sqlite3_prepare_v2(). 497 ** It functions in the same way as prepare_v2(), except that if a required 498 ** shared-cache lock cannot be obtained, this function may block waiting for 499 ** the lock to become available. In this scenario the normal API prepare_v2() 500 ** function always returns SQLITE_LOCKED. 501 ** 502 ** If this function returns SQLITE_LOCKED, the caller should rollback 503 ** the current transaction (if any) and try again later. Otherwise, the 504 ** system may become deadlocked. 505 */ 506 int sqlite3_blocking_prepare_v2( 507 sqlite3 *db, /* Database handle. */ 508 const char *zSql, /* UTF-8 encoded SQL statement. */ 509 int nSql, /* Length of zSql in bytes. */ 510 sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ 511 const char **pz /* OUT: End of parsed string */ 512 ){ 513 int rc; 514 while( SQLITE_LOCKED==(rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, pz)) ){ 515 rc = wait_for_unlock_notify(db); 516 if( rc!=SQLITE_OK ) break; 517 } 518 return rc; 519 } 520 /* END_SQLITE_BLOCKING_STEP */ 521 522 /* 523 ** Usage: sqlite3_blocking_step STMT 524 ** 525 ** Advance the statement to the next row. 526 */ 527 static int blocking_step_proc( 528 void * clientData, 529 Tcl_Interp *interp, 530 int objc, 531 Tcl_Obj *CONST objv[] 532 ){ 533 534 sqlite3_stmt *pStmt; 535 int rc; 536 537 if( objc!=2 ){ 538 Tcl_WrongNumArgs(interp, 1, objv, "STMT"); 539 return TCL_ERROR; 540 } 541 542 pStmt = (sqlite3_stmt*)sqlite3TestTextToPtr(Tcl_GetString(objv[1])); 543 rc = sqlite3_blocking_step(pStmt); 544 545 Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), 0); 546 return TCL_OK; 547 } 548 549 /* 550 ** Usage: sqlite3_blocking_prepare_v2 DB sql bytes ?tailvar? 551 ** Usage: sqlite3_nonblocking_prepare_v2 DB sql bytes ?tailvar? 552 */ 553 static int blocking_prepare_v2_proc( 554 void * clientData, 555 Tcl_Interp *interp, 556 int objc, 557 Tcl_Obj *CONST objv[] 558 ){ 559 sqlite3 *db; 560 const char *zSql; 561 int bytes; 562 const char *zTail = 0; 563 sqlite3_stmt *pStmt = 0; 564 char zBuf[50]; 565 int rc; 566 int isBlocking = !(clientData==0); 567 568 if( objc!=5 && objc!=4 ){ 569 Tcl_AppendResult(interp, "wrong # args: should be \"", 570 Tcl_GetString(objv[0]), " DB sql bytes tailvar", 0); 571 return TCL_ERROR; 572 } 573 if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; 574 zSql = Tcl_GetString(objv[2]); 575 if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR; 576 577 if( isBlocking ){ 578 rc = sqlite3_blocking_prepare_v2(db, zSql, bytes, &pStmt, &zTail); 579 }else{ 580 rc = sqlite3_prepare_v2(db, zSql, bytes, &pStmt, &zTail); 581 } 582 583 assert(rc==SQLITE_OK || pStmt==0); 584 if( zTail && objc>=5 ){ 585 if( bytes>=0 ){ 586 bytes = bytes - (zTail-zSql); 587 } 588 Tcl_ObjSetVar2(interp, objv[4], 0, Tcl_NewStringObj(zTail, bytes), 0); 589 } 590 if( rc!=SQLITE_OK ){ 591 assert( pStmt==0 ); 592 sprintf(zBuf, "%s ", (char *)sqlite3TestErrorName(rc)); 593 Tcl_AppendResult(interp, zBuf, sqlite3_errmsg(db), 0); 594 return TCL_ERROR; 595 } 596 597 if( pStmt ){ 598 if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR; 599 Tcl_AppendResult(interp, zBuf, 0); 600 } 601 return TCL_OK; 602 } 603 604 #endif /* SQLITE_OS_UNIX && SQLITE_ENABLE_UNLOCK_NOTIFY */ 605 /* 606 ** End of implementation of [sqlite3_blocking_step]. 607 ************************************************************************/ 608 609 /* 610 ** Register commands with the TCL interpreter. 611 */ 612 int SqlitetestThread_Init(Tcl_Interp *interp){ 613 Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0); 614 Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0); 615 #if defined(SQLITE_OS_UNIX) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) 616 Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0); 617 Tcl_CreateObjCommand(interp, 618 "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0); 619 Tcl_CreateObjCommand(interp, 620 "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0); 621 #endif 622 return TCL_OK; 623 } 624 #else 625 int SqlitetestThread_Init(Tcl_Interp *interp){ 626 return TCL_OK; 627 } 628 #endif 629