|  | /* | 
|  | ** 2007 September 9 | 
|  | ** | 
|  | ** The author disclaims copyright to this source code.  In place of | 
|  | ** a legal notice, here is a blessing: | 
|  | ** | 
|  | **    May you do good and not evil. | 
|  | **    May you find forgiveness for yourself and forgive others. | 
|  | **    May you share freely, never taking more than you give. | 
|  | ** | 
|  | ************************************************************************* | 
|  | ** | 
|  | ** This file contains the implementation of some Tcl commands used to | 
|  | ** test that sqlite3 database handles may be concurrently accessed by | 
|  | ** multiple threads. Right now this only works on unix. | 
|  | */ | 
|  |  | 
|  | #include "sqliteInt.h" | 
|  | #if defined(INCLUDE_SQLITE_TCL_H) | 
|  | #  include "sqlite_tcl.h" | 
|  | #else | 
|  | #  include "tcl.h" | 
|  | #endif | 
|  |  | 
|  | #if SQLITE_THREADSAFE | 
|  |  | 
|  | #include <errno.h> | 
|  |  | 
|  | #if !defined(_MSC_VER) | 
|  | #include <unistd.h> | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | ** One of these is allocated for each thread created by [sqlthread spawn]. | 
|  | */ | 
|  | typedef struct SqlThread SqlThread; | 
|  | struct SqlThread { | 
|  | Tcl_ThreadId parent;     /* Thread id of parent thread */ | 
|  | Tcl_Interp *interp;      /* Parent interpreter */ | 
|  | char *zScript;           /* The script to execute. */ | 
|  | char *zVarname;          /* Varname in parent script */ | 
|  | }; | 
|  |  | 
|  | /* | 
|  | ** A custom Tcl_Event type used by this module. When the event is | 
|  | ** handled, script zScript is evaluated in interpreter interp. If | 
|  | ** the evaluation throws an exception (returns TCL_ERROR), then the | 
|  | ** error is handled by Tcl_BackgroundError(). If no error occurs, | 
|  | ** the result is simply discarded. | 
|  | */ | 
|  | typedef struct EvalEvent EvalEvent; | 
|  | struct EvalEvent { | 
|  | Tcl_Event base;          /* Base class of type Tcl_Event */ | 
|  | char *zScript;           /* The script to execute. */ | 
|  | Tcl_Interp *interp;      /* The interpreter to execute it in. */ | 
|  | }; | 
|  |  | 
|  | static Tcl_ObjCmdProc sqlthread_proc; | 
|  | static Tcl_ObjCmdProc clock_seconds_proc; | 
|  | #if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) | 
|  | static Tcl_ObjCmdProc blocking_step_proc; | 
|  | static Tcl_ObjCmdProc blocking_prepare_v2_proc; | 
|  | #endif | 
|  | int Sqlitetest1_Init(Tcl_Interp *); | 
|  | int Sqlite3_Init(Tcl_Interp *); | 
|  |  | 
|  | /* Functions from main.c */ | 
|  | extern const char *sqlite3ErrName(int); | 
|  |  | 
|  | /* Functions from test1.c */ | 
|  | extern void *sqlite3TestTextToPtr(const char *); | 
|  | extern int getDbPointer(Tcl_Interp *, const char *, sqlite3 **); | 
|  | extern int sqlite3TestMakePointerStr(Tcl_Interp *, char *, void *); | 
|  | extern int sqlite3TestErrCode(Tcl_Interp *, sqlite3 *, int); | 
|  |  | 
|  | /* | 
|  | ** Handler for events of type EvalEvent. | 
|  | */ | 
|  | static int SQLITE_TCLAPI tclScriptEvent(Tcl_Event *evPtr, int flags){ | 
|  | int rc; | 
|  | EvalEvent *p = (EvalEvent *)evPtr; | 
|  | rc = Tcl_Eval(p->interp, p->zScript); | 
|  | if( rc!=TCL_OK ){ | 
|  | Tcl_BackgroundError(p->interp); | 
|  | } | 
|  | UNUSED_PARAMETER(flags); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | /* | 
|  | ** Register an EvalEvent to evaluate the script pScript in the | 
|  | ** parent interpreter/thread of SqlThread p. | 
|  | */ | 
|  | static void postToParent(SqlThread *p, Tcl_Obj *pScript){ | 
|  | EvalEvent *pEvent; | 
|  | char *zMsg; | 
|  | int nMsg; | 
|  |  | 
|  | zMsg = Tcl_GetStringFromObj(pScript, &nMsg); | 
|  | pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1); | 
|  | pEvent->base.nextPtr = 0; | 
|  | pEvent->base.proc = tclScriptEvent; | 
|  | pEvent->zScript = (char *)&pEvent[1]; | 
|  | memcpy(pEvent->zScript, zMsg, nMsg+1); | 
|  | pEvent->interp = p->interp; | 
|  |  | 
|  | Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL); | 
|  | Tcl_ThreadAlert(p->parent); | 
|  | } | 
|  |  | 
|  | /* | 
|  | ** The main function for threads created with [sqlthread spawn]. | 
|  | */ | 
|  | static Tcl_ThreadCreateType tclScriptThread(ClientData pSqlThread){ | 
|  | Tcl_Interp *interp; | 
|  | Tcl_Obj *pRes; | 
|  | Tcl_Obj *pList; | 
|  | int rc; | 
|  | SqlThread *p = (SqlThread *)pSqlThread; | 
|  | extern int Sqlitetest_mutex_Init(Tcl_Interp*); | 
|  |  | 
|  | interp = Tcl_CreateInterp(); | 
|  | Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0); | 
|  | Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, pSqlThread, 0); | 
|  | #if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) | 
|  | Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0); | 
|  | Tcl_CreateObjCommand(interp, | 
|  | "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0); | 
|  | Tcl_CreateObjCommand(interp, | 
|  | "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0); | 
|  | #endif | 
|  | Sqlitetest1_Init(interp); | 
|  | Sqlitetest_mutex_Init(interp); | 
|  | Sqlite3_Init(interp); | 
|  |  | 
|  | rc = Tcl_Eval(interp, p->zScript); | 
|  | pRes = Tcl_GetObjResult(interp); | 
|  | pList = Tcl_NewObj(); | 
|  | Tcl_IncrRefCount(pList); | 
|  | Tcl_IncrRefCount(pRes); | 
|  |  | 
|  | if( rc!=TCL_OK ){ | 
|  | Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("error", -1)); | 
|  | Tcl_ListObjAppendElement(interp, pList, pRes); | 
|  | postToParent(p, pList); | 
|  | Tcl_DecrRefCount(pList); | 
|  | pList = Tcl_NewObj(); | 
|  | } | 
|  |  | 
|  | Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj("set", -1)); | 
|  | Tcl_ListObjAppendElement(interp, pList, Tcl_NewStringObj(p->zVarname, -1)); | 
|  | Tcl_ListObjAppendElement(interp, pList, pRes); | 
|  | postToParent(p, pList); | 
|  |  | 
|  | ckfree((void *)p); | 
|  | Tcl_DecrRefCount(pList); | 
|  | Tcl_DecrRefCount(pRes); | 
|  | Tcl_DeleteInterp(interp); | 
|  | while( Tcl_DoOneEvent(TCL_ALL_EVENTS|TCL_DONT_WAIT) ); | 
|  | Tcl_ExitThread(0); | 
|  | TCL_THREAD_CREATE_RETURN; | 
|  | } | 
|  |  | 
|  | /* | 
|  | ** sqlthread spawn VARNAME SCRIPT | 
|  | ** | 
|  | **     Spawn a new thread with its own Tcl interpreter and run the | 
|  | **     specified SCRIPT(s) in it. The thread terminates after running | 
|  | **     the script. The result of the script is stored in the variable | 
|  | **     VARNAME. | 
|  | ** | 
|  | **     The caller can wait for the script to terminate using [vwait VARNAME]. | 
|  | */ | 
|  | static int SQLITE_TCLAPI sqlthread_spawn( | 
|  | ClientData clientData, | 
|  | Tcl_Interp *interp, | 
|  | int objc, | 
|  | Tcl_Obj *CONST objv[] | 
|  | ){ | 
|  | Tcl_ThreadId x; | 
|  | SqlThread *pNew; | 
|  | int rc; | 
|  |  | 
|  | int nVarname; char *zVarname; | 
|  | int nScript; char *zScript; | 
|  |  | 
|  | /* Parameters for thread creation */ | 
|  | const int nStack = TCL_THREAD_STACK_DEFAULT; | 
|  | const int flags = TCL_THREAD_NOFLAGS; | 
|  |  | 
|  | assert(objc==4); | 
|  | UNUSED_PARAMETER(clientData); | 
|  | UNUSED_PARAMETER(objc); | 
|  |  | 
|  | zVarname = Tcl_GetStringFromObj(objv[2], &nVarname); | 
|  | zScript = Tcl_GetStringFromObj(objv[3], &nScript); | 
|  |  | 
|  | pNew = (SqlThread *)ckalloc(sizeof(SqlThread)+nVarname+nScript+2); | 
|  | pNew->zVarname = (char *)&pNew[1]; | 
|  | pNew->zScript = (char *)&pNew->zVarname[nVarname+1]; | 
|  | memcpy(pNew->zVarname, zVarname, nVarname+1); | 
|  | memcpy(pNew->zScript, zScript, nScript+1); | 
|  | pNew->parent = Tcl_GetCurrentThread(); | 
|  | pNew->interp = interp; | 
|  |  | 
|  | rc = Tcl_CreateThread(&x, tclScriptThread, (void *)pNew, nStack, flags); | 
|  | if( rc!=TCL_OK ){ | 
|  | Tcl_AppendResult(interp, "Error in Tcl_CreateThread()", 0); | 
|  | ckfree((char *)pNew); | 
|  | return TCL_ERROR; | 
|  | } | 
|  |  | 
|  | return TCL_OK; | 
|  | } | 
|  |  | 
|  | /* | 
|  | ** sqlthread parent SCRIPT | 
|  | ** | 
|  | **     This can be called by spawned threads only. It sends the specified | 
|  | **     script back to the parent thread for execution. The result of | 
|  | **     evaluating the SCRIPT is returned. The parent thread must enter | 
|  | **     the event loop for this to work - otherwise the caller will | 
|  | **     block indefinitely. | 
|  | ** | 
|  | **     NOTE: At the moment, this doesn't work. FIXME. | 
|  | */ | 
|  | static int SQLITE_TCLAPI sqlthread_parent( | 
|  | ClientData clientData, | 
|  | Tcl_Interp *interp, | 
|  | int objc, | 
|  | Tcl_Obj *CONST objv[] | 
|  | ){ | 
|  | EvalEvent *pEvent; | 
|  | char *zMsg; | 
|  | int nMsg; | 
|  | SqlThread *p = (SqlThread *)clientData; | 
|  |  | 
|  | assert(objc==3); | 
|  | UNUSED_PARAMETER(objc); | 
|  |  | 
|  | if( p==0 ){ | 
|  | Tcl_AppendResult(interp, "no parent thread", 0); | 
|  | return TCL_ERROR; | 
|  | } | 
|  |  | 
|  | zMsg = Tcl_GetStringFromObj(objv[2], &nMsg); | 
|  | pEvent = (EvalEvent *)ckalloc(sizeof(EvalEvent)+nMsg+1); | 
|  | pEvent->base.nextPtr = 0; | 
|  | pEvent->base.proc = tclScriptEvent; | 
|  | pEvent->zScript = (char *)&pEvent[1]; | 
|  | memcpy(pEvent->zScript, zMsg, nMsg+1); | 
|  | pEvent->interp = p->interp; | 
|  | Tcl_ThreadQueueEvent(p->parent, (Tcl_Event *)pEvent, TCL_QUEUE_TAIL); | 
|  | Tcl_ThreadAlert(p->parent); | 
|  |  | 
|  | return TCL_OK; | 
|  | } | 
|  |  | 
|  | static int xBusy(void *pArg, int nBusy){ | 
|  | UNUSED_PARAMETER(pArg); | 
|  | UNUSED_PARAMETER(nBusy); | 
|  | sqlite3_sleep(50); | 
|  | return 1;             /* Try again... */ | 
|  | } | 
|  |  | 
|  | /* | 
|  | ** sqlthread open | 
|  | ** | 
|  | **     Open a database handle and return the string representation of | 
|  | **     the pointer value. | 
|  | */ | 
|  | static int SQLITE_TCLAPI sqlthread_open( | 
|  | ClientData clientData, | 
|  | Tcl_Interp *interp, | 
|  | int objc, | 
|  | Tcl_Obj *CONST objv[] | 
|  | ){ | 
|  | int sqlite3TestMakePointerStr(Tcl_Interp *interp, char *zPtr, void *p); | 
|  |  | 
|  | const char *zFilename; | 
|  | sqlite3 *db; | 
|  | char zBuf[100]; | 
|  | extern int Md5_Register(sqlite3*,char**,const sqlite3_api_routines*); | 
|  |  | 
|  | UNUSED_PARAMETER(clientData); | 
|  | UNUSED_PARAMETER(objc); | 
|  |  | 
|  | zFilename = Tcl_GetString(objv[2]); | 
|  | sqlite3_open(zFilename, &db); | 
|  | #ifdef SQLITE_HAS_CODEC | 
|  | if( db && objc>=4 ){ | 
|  | const char *zKey; | 
|  | int nKey; | 
|  | int rc; | 
|  | zKey = Tcl_GetStringFromObj(objv[3], &nKey); | 
|  | rc = sqlite3_key(db, zKey, nKey); | 
|  | if( rc!=SQLITE_OK ){ | 
|  | char *zErrMsg = sqlite3_mprintf("error %d: %s", rc, sqlite3_errmsg(db)); | 
|  | sqlite3_close(db); | 
|  | Tcl_AppendResult(interp, zErrMsg, (char*)0); | 
|  | sqlite3_free(zErrMsg); | 
|  | return TCL_ERROR; | 
|  | } | 
|  | } | 
|  | #endif | 
|  | Md5_Register(db, 0, 0); | 
|  | sqlite3_busy_handler(db, xBusy, 0); | 
|  |  | 
|  | if( sqlite3TestMakePointerStr(interp, zBuf, db) ) return TCL_ERROR; | 
|  | Tcl_AppendResult(interp, zBuf, 0); | 
|  |  | 
|  | return TCL_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | ** sqlthread open | 
|  | ** | 
|  | **     Return the current thread-id (Tcl_GetCurrentThread()) cast to | 
|  | **     an integer. | 
|  | */ | 
|  | static int SQLITE_TCLAPI sqlthread_id( | 
|  | ClientData clientData, | 
|  | Tcl_Interp *interp, | 
|  | int objc, | 
|  | Tcl_Obj *CONST objv[] | 
|  | ){ | 
|  | Tcl_ThreadId id = Tcl_GetCurrentThread(); | 
|  | Tcl_SetObjResult(interp, Tcl_NewIntObj(SQLITE_PTR_TO_INT(id))); | 
|  | UNUSED_PARAMETER(clientData); | 
|  | UNUSED_PARAMETER(objc); | 
|  | UNUSED_PARAMETER(objv); | 
|  | return TCL_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* | 
|  | ** Dispatch routine for the sub-commands of [sqlthread]. | 
|  | */ | 
|  | static int SQLITE_TCLAPI sqlthread_proc( | 
|  | ClientData clientData, | 
|  | Tcl_Interp *interp, | 
|  | int objc, | 
|  | Tcl_Obj *CONST objv[] | 
|  | ){ | 
|  | struct SubCommand { | 
|  | char *zName; | 
|  | Tcl_ObjCmdProc *xProc; | 
|  | int nArg; | 
|  | char *zUsage; | 
|  | } aSub[] = { | 
|  | {"parent", sqlthread_parent, 1, "SCRIPT"}, | 
|  | {"spawn",  sqlthread_spawn,  2, "VARNAME SCRIPT"}, | 
|  | {"open",   sqlthread_open,   1, "DBNAME"}, | 
|  | {"id",     sqlthread_id,     0, ""}, | 
|  | {0, 0, 0} | 
|  | }; | 
|  | struct SubCommand *pSub; | 
|  | int rc; | 
|  | int iIndex; | 
|  |  | 
|  | if( objc<2 ){ | 
|  | Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND"); | 
|  | return TCL_ERROR; | 
|  | } | 
|  |  | 
|  | rc = Tcl_GetIndexFromObjStruct( | 
|  | interp, objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iIndex | 
|  | ); | 
|  | if( rc!=TCL_OK ) return rc; | 
|  | pSub = &aSub[iIndex]; | 
|  |  | 
|  | if( objc<(pSub->nArg+2) ){ | 
|  | Tcl_WrongNumArgs(interp, 2, objv, pSub->zUsage); | 
|  | return TCL_ERROR; | 
|  | } | 
|  |  | 
|  | return pSub->xProc(clientData, interp, objc, objv); | 
|  | } | 
|  |  | 
|  | /* | 
|  | ** The [clock_seconds] command. This is more or less the same as the | 
|  | ** regular tcl [clock seconds], except that it is available in testfixture | 
|  | ** when linked against both Tcl 8.4 and 8.5. Because [clock seconds] is | 
|  | ** implemented as a script in Tcl 8.5, it is not usually available to | 
|  | ** testfixture. | 
|  | */ | 
|  | static int SQLITE_TCLAPI clock_seconds_proc( | 
|  | ClientData clientData, | 
|  | Tcl_Interp *interp, | 
|  | int objc, | 
|  | Tcl_Obj *CONST objv[] | 
|  | ){ | 
|  | Tcl_Time now; | 
|  | Tcl_GetTime(&now); | 
|  | Tcl_SetObjResult(interp, Tcl_NewIntObj(now.sec)); | 
|  | UNUSED_PARAMETER(clientData); | 
|  | UNUSED_PARAMETER(objc); | 
|  | UNUSED_PARAMETER(objv); | 
|  | return TCL_OK; | 
|  | } | 
|  |  | 
|  | /************************************************************************* | 
|  | ** This block contains the implementation of the [sqlite3_blocking_step] | 
|  | ** command available to threads created by [sqlthread spawn] commands. It | 
|  | ** is only available on UNIX for now. This is because pthread condition | 
|  | ** variables are used. | 
|  | ** | 
|  | ** The source code for the C functions sqlite3_blocking_step(), | 
|  | ** blocking_step_notify() and the structure UnlockNotification is | 
|  | ** automatically extracted from this file and used as part of the | 
|  | ** documentation for the sqlite3_unlock_notify() API function. This | 
|  | ** should be considered if these functions are to be extended (i.e. to | 
|  | ** support windows) in the future. | 
|  | */ | 
|  | #if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) | 
|  |  | 
|  | /* BEGIN_SQLITE_BLOCKING_STEP */ | 
|  | /* This example uses the pthreads API */ | 
|  | #include <pthread.h> | 
|  |  | 
|  | /* | 
|  | ** A pointer to an instance of this structure is passed as the user-context | 
|  | ** pointer when registering for an unlock-notify callback. | 
|  | */ | 
|  | typedef struct UnlockNotification UnlockNotification; | 
|  | struct UnlockNotification { | 
|  | int fired;                         /* True after unlock event has occurred */ | 
|  | pthread_cond_t cond;               /* Condition variable to wait on */ | 
|  | pthread_mutex_t mutex;             /* Mutex to protect structure */ | 
|  | }; | 
|  |  | 
|  | /* | 
|  | ** This function is an unlock-notify callback registered with SQLite. | 
|  | */ | 
|  | static void unlock_notify_cb(void **apArg, int nArg){ | 
|  | int i; | 
|  | for(i=0; i<nArg; i++){ | 
|  | UnlockNotification *p = (UnlockNotification *)apArg[i]; | 
|  | pthread_mutex_lock(&p->mutex); | 
|  | p->fired = 1; | 
|  | pthread_cond_signal(&p->cond); | 
|  | pthread_mutex_unlock(&p->mutex); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | ** This function assumes that an SQLite API call (either sqlite3_prepare_v2() | 
|  | ** or sqlite3_step()) has just returned SQLITE_LOCKED. The argument is the | 
|  | ** associated database connection. | 
|  | ** | 
|  | ** This function calls sqlite3_unlock_notify() to register for an | 
|  | ** unlock-notify callback, then blocks until that callback is delivered | 
|  | ** and returns SQLITE_OK. The caller should then retry the failed operation. | 
|  | ** | 
|  | ** Or, if sqlite3_unlock_notify() indicates that to block would deadlock | 
|  | ** the system, then this function returns SQLITE_LOCKED immediately. In | 
|  | ** this case the caller should not retry the operation and should roll | 
|  | ** back the current transaction (if any). | 
|  | */ | 
|  | static int wait_for_unlock_notify(sqlite3 *db){ | 
|  | int rc; | 
|  | UnlockNotification un; | 
|  |  | 
|  | /* Initialize the UnlockNotification structure. */ | 
|  | un.fired = 0; | 
|  | pthread_mutex_init(&un.mutex, 0); | 
|  | pthread_cond_init(&un.cond, 0); | 
|  |  | 
|  | /* Register for an unlock-notify callback. */ | 
|  | rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un); | 
|  | assert( rc==SQLITE_LOCKED || rc==SQLITE_OK ); | 
|  |  | 
|  | /* The call to sqlite3_unlock_notify() always returns either SQLITE_LOCKED | 
|  | ** or SQLITE_OK. | 
|  | ** | 
|  | ** If SQLITE_LOCKED was returned, then the system is deadlocked. In this | 
|  | ** case this function needs to return SQLITE_LOCKED to the caller so | 
|  | ** that the current transaction can be rolled back. Otherwise, block | 
|  | ** until the unlock-notify callback is invoked, then return SQLITE_OK. | 
|  | */ | 
|  | if( rc==SQLITE_OK ){ | 
|  | pthread_mutex_lock(&un.mutex); | 
|  | if( !un.fired ){ | 
|  | pthread_cond_wait(&un.cond, &un.mutex); | 
|  | } | 
|  | pthread_mutex_unlock(&un.mutex); | 
|  | } | 
|  |  | 
|  | /* Destroy the mutex and condition variables. */ | 
|  | pthread_cond_destroy(&un.cond); | 
|  | pthread_mutex_destroy(&un.mutex); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* | 
|  | ** This function is a wrapper around the SQLite function sqlite3_step(). | 
|  | ** It functions in the same way as step(), except that if a required | 
|  | ** shared-cache lock cannot be obtained, this function may block waiting for | 
|  | ** the lock to become available. In this scenario the normal API step() | 
|  | ** function always returns SQLITE_LOCKED. | 
|  | ** | 
|  | ** If this function returns SQLITE_LOCKED, the caller should rollback | 
|  | ** the current transaction (if any) and try again later. Otherwise, the | 
|  | ** system may become deadlocked. | 
|  | */ | 
|  | int sqlite3_blocking_step(sqlite3_stmt *pStmt){ | 
|  | int rc; | 
|  | while( SQLITE_LOCKED==(rc = sqlite3_step(pStmt)) ){ | 
|  | rc = wait_for_unlock_notify(sqlite3_db_handle(pStmt)); | 
|  | if( rc!=SQLITE_OK ) break; | 
|  | sqlite3_reset(pStmt); | 
|  | } | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* | 
|  | ** This function is a wrapper around the SQLite function sqlite3_prepare_v2(). | 
|  | ** It functions in the same way as prepare_v2(), except that if a required | 
|  | ** shared-cache lock cannot be obtained, this function may block waiting for | 
|  | ** the lock to become available. In this scenario the normal API prepare_v2() | 
|  | ** function always returns SQLITE_LOCKED. | 
|  | ** | 
|  | ** If this function returns SQLITE_LOCKED, the caller should rollback | 
|  | ** the current transaction (if any) and try again later. Otherwise, the | 
|  | ** system may become deadlocked. | 
|  | */ | 
|  | int sqlite3_blocking_prepare_v2( | 
|  | sqlite3 *db,              /* Database handle. */ | 
|  | const char *zSql,         /* UTF-8 encoded SQL statement. */ | 
|  | int nSql,                 /* Length of zSql in bytes. */ | 
|  | sqlite3_stmt **ppStmt,    /* OUT: A pointer to the prepared statement */ | 
|  | const char **pz           /* OUT: End of parsed string */ | 
|  | ){ | 
|  | int rc; | 
|  | while( SQLITE_LOCKED==(rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, pz)) ){ | 
|  | rc = wait_for_unlock_notify(db); | 
|  | if( rc!=SQLITE_OK ) break; | 
|  | } | 
|  | return rc; | 
|  | } | 
|  | /* END_SQLITE_BLOCKING_STEP */ | 
|  |  | 
|  | /* | 
|  | ** Usage: sqlite3_blocking_step STMT | 
|  | ** | 
|  | ** Advance the statement to the next row. | 
|  | */ | 
|  | static int SQLITE_TCLAPI blocking_step_proc( | 
|  | void * clientData, | 
|  | Tcl_Interp *interp, | 
|  | int objc, | 
|  | Tcl_Obj *CONST objv[] | 
|  | ){ | 
|  |  | 
|  | sqlite3_stmt *pStmt; | 
|  | int rc; | 
|  |  | 
|  | if( objc!=2 ){ | 
|  | Tcl_WrongNumArgs(interp, 1, objv, "STMT"); | 
|  | return TCL_ERROR; | 
|  | } | 
|  |  | 
|  | pStmt = (sqlite3_stmt*)sqlite3TestTextToPtr(Tcl_GetString(objv[1])); | 
|  | rc = sqlite3_blocking_step(pStmt); | 
|  |  | 
|  | Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), 0); | 
|  | return TCL_OK; | 
|  | } | 
|  |  | 
|  | /* | 
|  | ** Usage: sqlite3_blocking_prepare_v2 DB sql bytes ?tailvar? | 
|  | ** Usage: sqlite3_nonblocking_prepare_v2 DB sql bytes ?tailvar? | 
|  | */ | 
|  | static int SQLITE_TCLAPI blocking_prepare_v2_proc( | 
|  | void * clientData, | 
|  | Tcl_Interp *interp, | 
|  | int objc, | 
|  | Tcl_Obj *CONST objv[] | 
|  | ){ | 
|  | sqlite3 *db; | 
|  | const char *zSql; | 
|  | int bytes; | 
|  | const char *zTail = 0; | 
|  | sqlite3_stmt *pStmt = 0; | 
|  | char zBuf[50]; | 
|  | int rc; | 
|  | int isBlocking = !(clientData==0); | 
|  |  | 
|  | if( objc!=5 && objc!=4 ){ | 
|  | Tcl_AppendResult(interp, "wrong # args: should be \"", | 
|  | Tcl_GetString(objv[0]), " DB sql bytes tailvar", 0); | 
|  | return TCL_ERROR; | 
|  | } | 
|  | if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; | 
|  | zSql = Tcl_GetString(objv[2]); | 
|  | if( Tcl_GetIntFromObj(interp, objv[3], &bytes) ) return TCL_ERROR; | 
|  |  | 
|  | if( isBlocking ){ | 
|  | rc = sqlite3_blocking_prepare_v2(db, zSql, bytes, &pStmt, &zTail); | 
|  | }else{ | 
|  | rc = sqlite3_prepare_v2(db, zSql, bytes, &pStmt, &zTail); | 
|  | } | 
|  |  | 
|  | assert(rc==SQLITE_OK || pStmt==0); | 
|  | if( zTail && objc>=5 ){ | 
|  | if( bytes>=0 ){ | 
|  | bytes = bytes - (zTail-zSql); | 
|  | } | 
|  | Tcl_ObjSetVar2(interp, objv[4], 0, Tcl_NewStringObj(zTail, bytes), 0); | 
|  | } | 
|  | if( rc!=SQLITE_OK ){ | 
|  | assert( pStmt==0 ); | 
|  | sqlite3_snprintf(sizeof(zBuf), zBuf, "%s ", (char *)sqlite3ErrName(rc)); | 
|  | Tcl_AppendResult(interp, zBuf, sqlite3_errmsg(db), 0); | 
|  | return TCL_ERROR; | 
|  | } | 
|  |  | 
|  | if( pStmt ){ | 
|  | if( sqlite3TestMakePointerStr(interp, zBuf, pStmt) ) return TCL_ERROR; | 
|  | Tcl_AppendResult(interp, zBuf, 0); | 
|  | } | 
|  | return TCL_OK; | 
|  | } | 
|  |  | 
|  | #endif /* SQLITE_OS_UNIX && SQLITE_ENABLE_UNLOCK_NOTIFY */ | 
|  | /* | 
|  | ** End of implementation of [sqlite3_blocking_step]. | 
|  | ************************************************************************/ | 
|  |  | 
|  | /* | 
|  | ** Register commands with the TCL interpreter. | 
|  | */ | 
|  | int SqlitetestThread_Init(Tcl_Interp *interp){ | 
|  | Tcl_CreateObjCommand(interp, "sqlthread", sqlthread_proc, 0, 0); | 
|  | Tcl_CreateObjCommand(interp, "clock_seconds", clock_seconds_proc, 0, 0); | 
|  | #if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY) | 
|  | Tcl_CreateObjCommand(interp, "sqlite3_blocking_step", blocking_step_proc,0,0); | 
|  | Tcl_CreateObjCommand(interp, | 
|  | "sqlite3_blocking_prepare_v2", blocking_prepare_v2_proc, (void *)1, 0); | 
|  | Tcl_CreateObjCommand(interp, | 
|  | "sqlite3_nonblocking_prepare_v2", blocking_prepare_v2_proc, 0, 0); | 
|  | #endif | 
|  | return TCL_OK; | 
|  | } | 
|  | #else | 
|  | int SqlitetestThread_Init(Tcl_Interp *interp){ | 
|  | return TCL_OK; | 
|  | } | 
|  | #endif |