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