#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
typedef struct SqlThread SqlThread;
struct SqlThread {
Tcl_ThreadId parent;
Tcl_Interp *interp;
char *zScript;
char *zVarname;
};
typedef struct EvalEvent EvalEvent;
struct EvalEvent {
Tcl_Event base;
char *zScript;
Tcl_Interp *interp;
};
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 *);
extern const char *sqlite3ErrName(int);
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);
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;
}
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);
}
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;
}
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;
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;
}
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;
}
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);
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;
}
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;
}
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);
}
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;
}
static int SQLITE_TCLAPI clock_milliseconds_proc(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *CONST objv[]
){
Tcl_Time now;
Tcl_GetTime(&now);
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(
((Tcl_WideInt)now.sec * 1000) + (now.usec / 1000)
));
UNUSED_PARAMETER(clientData);
UNUSED_PARAMETER(objc);
UNUSED_PARAMETER(objv);
return TCL_OK;
}
#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
#include <pthread.h>
typedef struct UnlockNotification UnlockNotification;
struct UnlockNotification {
int fired;
pthread_cond_t cond;
pthread_mutex_t mutex;
};
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);
}
}
static int wait_for_unlock_notify(sqlite3 *db){
int rc;
UnlockNotification un;
un.fired = 0;
pthread_mutex_init(&un.mutex, 0);
pthread_cond_init(&un.cond, 0);
rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un);
assert( rc==SQLITE_LOCKED || rc==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);
}
pthread_cond_destroy(&un.cond);
pthread_mutex_destroy(&un.mutex);
return rc;
}
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;
}
int sqlite3_blocking_prepare_v2(
sqlite3 *db,
const char *zSql,
int nSql,
sqlite3_stmt **ppStmt,
const char **pz
){
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;
}
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;
}
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
int SqlitetestThread_Init(Tcl_Interp *interp){
struct TclCmd {
int (*xProc)(void*, Tcl_Interp*, int, Tcl_Obj*const*);
const char *zName;
int iCtx;
} aCmd[] = {
{ sqlthread_proc, "sqlthread", 0 },
{ clock_seconds_proc, "clock_second", 0 },
{ clock_milliseconds_proc, "clock_milliseconds", 0 },
#if SQLITE_OS_UNIX && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
{ blocking_step_proc, "sqlite3_blocking_step", 0 },
{ blocking_prepare_v2_proc, "sqlite3_blocking_prepare_v2", 1 },
{ blocking_prepare_v2_proc, "sqlite3_nonblocking_prepare_v2", 0 },
#endif
};
int ii;
for(ii=0; ii<sizeof(aCmd)/sizeof(aCmd[0]); ii++){
void *p = SQLITE_INT_TO_PTR(aCmd[ii].iCtx);
Tcl_CreateObjCommand(interp, aCmd[ii].zName, aCmd[ii].xProc, p, 0);
}
return TCL_OK;
}
#else
int SqlitetestThread_Init(Tcl_Interp *interp){
return TCL_OK;
}
#endif