summaryrefslogtreecommitdiffstats
path: root/third_party/sqlite/src/tclsqlite.c
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/sqlite/src/tclsqlite.c')
-rw-r--r--third_party/sqlite/src/tclsqlite.c433
1 files changed, 374 insertions, 59 deletions
diff --git a/third_party/sqlite/src/tclsqlite.c b/third_party/sqlite/src/tclsqlite.c
index 35a69b8..223a7df 100644
--- a/third_party/sqlite/src/tclsqlite.c
+++ b/third_party/sqlite/src/tclsqlite.c
@@ -11,8 +11,6 @@
*************************************************************************
** A TCL Interface to SQLite. Append this file to sqlite3.c and
** compile the whole thing to build a TCL-enabled version of SQLite.
-**
-** $Id: tclsqlite.c,v 1.219 2008/07/10 17:52:49 danielk1977 Exp $
*/
#include "tcl.h"
#include <errno.h>
@@ -104,10 +102,12 @@ struct SqliteDb {
char *zProfile; /* The profile callback routine */
char *zProgress; /* The progress callback routine */
char *zAuth; /* The authorization callback routine */
+ int disableAuth; /* Disable the authorizer if it exists */
char *zNull; /* Text to substitute for an SQL NULL value */
SqlFunc *pFunc; /* List of SQL functions */
Tcl_Obj *pUpdateHook; /* Update hook script (if any) */
Tcl_Obj *pRollbackHook; /* Rollback hook script (if any) */
+ Tcl_Obj *pUnlockNotify; /* Unlock notify script (if any) */
SqlCollate *pCollate; /* List of SQL collation functions */
int rc; /* Return code of most recent sqlite3_exec() */
Tcl_Obj *pCollateNeeded; /* Collation needed script */
@@ -116,6 +116,8 @@ struct SqliteDb {
int maxStmt; /* The next maximum number of stmtList */
int nStmt; /* Number of statements in stmtList */
IncrblobChannel *pIncrblob;/* Linked list of open incrblob channels */
+ int nStep, nSort; /* Statistics for most recent operation */
+ int nTransaction; /* Number of nested [transaction] methods */
};
struct IncrblobChannel {
@@ -127,6 +129,17 @@ struct IncrblobChannel {
IncrblobChannel *pPrev; /* Linked list of all open incrblob channels */
};
+/*
+** Compute a string length that is limited to what can be stored in
+** lower 30 bits of a 32-bit signed integer.
+*/
+static int strlen30(const char *z){
+ const char *z2 = z;
+ while( *z2 ){ z2++; }
+ return 0x3fffffff & (int)(z2 - z);
+}
+
+
#ifndef SQLITE_OMIT_INCRBLOB
/*
** Close all incrblob channels opened using database connection pDb.
@@ -382,7 +395,7 @@ static int safeToUseEvalObjv(Tcl_Interp *interp, Tcl_Obj *pCmd){
static SqlFunc *findSqlFunc(SqliteDb *pDb, const char *zName){
SqlFunc *p, *pNew;
int i;
- pNew = (SqlFunc*)Tcl_Alloc( sizeof(*pNew) + strlen(zName) + 1 );
+ pNew = (SqlFunc*)Tcl_Alloc( sizeof(*pNew) + strlen30(zName) + 1 );
pNew->zName = (char*)&pNew[1];
for(i=0; zName[i]; i++){ pNew->zName[i] = tolower(zName[i]); }
pNew->zName[i] = 0;
@@ -560,6 +573,33 @@ static void DbRollbackHandler(void *clientData){
}
}
+#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_UNLOCK_NOTIFY)
+static void setTestUnlockNotifyVars(Tcl_Interp *interp, int iArg, int nArg){
+ char zBuf[64];
+ sprintf(zBuf, "%d", iArg);
+ Tcl_SetVar(interp, "sqlite_unlock_notify_arg", zBuf, TCL_GLOBAL_ONLY);
+ sprintf(zBuf, "%d", nArg);
+ Tcl_SetVar(interp, "sqlite_unlock_notify_argcount", zBuf, TCL_GLOBAL_ONLY);
+}
+#else
+# define setTestUnlockNotifyVars(x,y,z)
+#endif
+
+#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY
+static void DbUnlockNotify(void **apArg, int nArg){
+ int i;
+ for(i=0; i<nArg; i++){
+ const int flags = (TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
+ SqliteDb *pDb = (SqliteDb *)apArg[i];
+ setTestUnlockNotifyVars(pDb->interp, i, nArg);
+ assert( pDb->pUnlockNotify);
+ Tcl_EvalObjEx(pDb->interp, pDb->pUnlockNotify, flags);
+ Tcl_DecrRefCount(pDb->pUnlockNotify);
+ pDb->pUnlockNotify = 0;
+ }
+}
+#endif
+
static void DbUpdateHandler(
void *p,
int op,
@@ -761,6 +801,7 @@ static int auth_callback(
int rc;
const char *zReply;
SqliteDb *pDb = (SqliteDb*)pArg;
+ if( pDb->disableAuth ) return SQLITE_OK;
switch( code ){
case SQLITE_COPY : zCode="SQLITE_COPY"; break;
@@ -795,6 +836,7 @@ static int auth_callback(
case SQLITE_CREATE_VTABLE : zCode="SQLITE_CREATE_VTABLE"; break;
case SQLITE_DROP_VTABLE : zCode="SQLITE_DROP_VTABLE"; break;
case SQLITE_FUNCTION : zCode="SQLITE_FUNCTION"; break;
+ case SQLITE_SAVEPOINT : zCode="SQLITE_SAVEPOINT"; break;
default : zCode="????"; break;
}
Tcl_DStringInit(&str);
@@ -967,29 +1009,32 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
int choice;
int rc = TCL_OK;
static const char *DB_strs[] = {
- "authorizer", "busy", "cache",
- "changes", "close", "collate",
- "collation_needed", "commit_hook", "complete",
- "copy", "enable_load_extension","errorcode",
- "eval", "exists", "function",
- "incrblob", "interrupt", "last_insert_rowid",
- "nullvalue", "onecolumn", "profile",
- "progress", "rekey", "rollback_hook",
+ "authorizer", "backup", "busy",
+ "cache", "changes", "close",
+ "collate", "collation_needed", "commit_hook",
+ "complete", "copy", "enable_load_extension",
+ "errorcode", "eval", "exists",
+ "function", "incrblob", "interrupt",
+ "last_insert_rowid", "nullvalue", "onecolumn",
+ "profile", "progress", "rekey",
+ "restore", "rollback_hook", "status",
"timeout", "total_changes", "trace",
- "transaction", "update_hook", "version",
- 0
+ "transaction", "unlock_notify", "update_hook",
+ "version", 0
};
enum DB_enum {
- DB_AUTHORIZER, DB_BUSY, DB_CACHE,
- DB_CHANGES, DB_CLOSE, DB_COLLATE,
- DB_COLLATION_NEEDED, DB_COMMIT_HOOK, DB_COMPLETE,
- DB_COPY, DB_ENABLE_LOAD_EXTENSION,DB_ERRORCODE,
- DB_EVAL, DB_EXISTS, DB_FUNCTION,
- DB_INCRBLOB, DB_INTERRUPT, DB_LAST_INSERT_ROWID,
- DB_NULLVALUE, DB_ONECOLUMN, DB_PROFILE,
- DB_PROGRESS, DB_REKEY, DB_ROLLBACK_HOOK,
+ DB_AUTHORIZER, DB_BACKUP, DB_BUSY,
+ DB_CACHE, DB_CHANGES, DB_CLOSE,
+ DB_COLLATE, DB_COLLATION_NEEDED, DB_COMMIT_HOOK,
+ DB_COMPLETE, DB_COPY, DB_ENABLE_LOAD_EXTENSION,
+ DB_ERRORCODE, DB_EVAL, DB_EXISTS,
+ DB_FUNCTION, DB_INCRBLOB, DB_INTERRUPT,
+ DB_LAST_INSERT_ROWID, DB_NULLVALUE, DB_ONECOLUMN,
+ DB_PROFILE, DB_PROGRESS, DB_REKEY,
+ DB_RESTORE, DB_ROLLBACK_HOOK, DB_STATUS,
DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
- DB_TRANSACTION, DB_UPDATE_HOOK, DB_VERSION
+ DB_TRANSACTION, DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK,
+ DB_VERSION,
};
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
@@ -1057,6 +1102,55 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
break;
}
+ /* $db backup ?DATABASE? FILENAME
+ **
+ ** Open or create a database file named FILENAME. Transfer the
+ ** content of local database DATABASE (default: "main") into the
+ ** FILENAME database.
+ */
+ case DB_BACKUP: {
+ const char *zDestFile;
+ const char *zSrcDb;
+ sqlite3 *pDest;
+ sqlite3_backup *pBackup;
+
+ if( objc==3 ){
+ zSrcDb = "main";
+ zDestFile = Tcl_GetString(objv[2]);
+ }else if( objc==4 ){
+ zSrcDb = Tcl_GetString(objv[2]);
+ zDestFile = Tcl_GetString(objv[3]);
+ }else{
+ Tcl_WrongNumArgs(interp, 2, objv, "?DATABASE? FILENAME");
+ return TCL_ERROR;
+ }
+ rc = sqlite3_open(zDestFile, &pDest);
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "cannot open target database: ",
+ sqlite3_errmsg(pDest), (char*)0);
+ sqlite3_close(pDest);
+ return TCL_ERROR;
+ }
+ pBackup = sqlite3_backup_init(pDest, "main", pDb->db, zSrcDb);
+ if( pBackup==0 ){
+ Tcl_AppendResult(interp, "backup failed: ",
+ sqlite3_errmsg(pDest), (char*)0);
+ sqlite3_close(pDest);
+ return TCL_ERROR;
+ }
+ while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
+ sqlite3_backup_finish(pBackup);
+ if( rc==SQLITE_DONE ){
+ rc = TCL_OK;
+ }else{
+ Tcl_AppendResult(interp, "backup failed: ",
+ sqlite3_errmsg(pDest), (char*)0);
+ rc = TCL_ERROR;
+ }
+ sqlite3_close(pDest);
+ break;
+ }
+
/* $db busy ?CALLBACK?
**
** Invoke the given callback if an SQL statement attempts to open
@@ -1335,17 +1429,17 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
zConflict = Tcl_GetStringFromObj(objv[2], 0);
zTable = Tcl_GetStringFromObj(objv[3], 0);
zFile = Tcl_GetStringFromObj(objv[4], 0);
- nSep = strlen(zSep);
- nNull = strlen(zNull);
+ nSep = strlen30(zSep);
+ nNull = strlen30(zNull);
if( nSep==0 ){
Tcl_AppendResult(interp,"Error: non-null separator required for copy",0);
return TCL_ERROR;
}
- if(strcasecmp(zConflict, "rollback") != 0 &&
- strcasecmp(zConflict, "abort" ) != 0 &&
- strcasecmp(zConflict, "fail" ) != 0 &&
- strcasecmp(zConflict, "ignore" ) != 0 &&
- strcasecmp(zConflict, "replace" ) != 0 ) {
+ if(strcmp(zConflict, "rollback") != 0 &&
+ strcmp(zConflict, "abort" ) != 0 &&
+ strcmp(zConflict, "fail" ) != 0 &&
+ strcmp(zConflict, "ignore" ) != 0 &&
+ strcmp(zConflict, "replace" ) != 0 ) {
Tcl_AppendResult(interp, "Error: \"", zConflict,
"\", conflict-algorithm must be one of: rollback, "
"abort, fail, ignore, or replace", 0);
@@ -1356,7 +1450,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
Tcl_AppendResult(interp, "Error: no such table: ", zTable, 0);
return TCL_ERROR;
}
- nByte = strlen(zSql);
+ nByte = strlen30(zSql);
rc = sqlite3_prepare(pDb->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
if( rc ){
@@ -1376,7 +1470,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
}
sqlite3_snprintf(nByte+50, zSql, "INSERT OR %q INTO '%q' VALUES(?",
zConflict, zTable);
- j = strlen(zSql);
+ j = strlen30(zSql);
for(i=1; i<nCol; i++){
zSql[j++] = ',';
zSql[j++] = '?';
@@ -1421,7 +1515,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
}
if( i+1!=nCol ){
char *zErr;
- int nErr = strlen(zFile) + 200;
+ int nErr = strlen30(zFile) + 200;
zErr = malloc(nErr);
if( zErr ){
sqlite3_snprintf(nErr, zErr,
@@ -1435,7 +1529,9 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
}
for(i=0; i<nCol; i++){
/* check for null data, if so, bind as null */
- if ((nNull>0 && strcmp(azCol[i], zNull)==0) || strlen(azCol[i])==0) {
+ if( (nNull>0 && strcmp(azCol[i], zNull)==0)
+ || strlen30(azCol[i])==0
+ ){
sqlite3_bind_null(pStmt, i+1);
}else{
sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC);
@@ -1576,7 +1672,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
** which matches the next sequence of SQL.
*/
pStmt = 0;
- len = strlen(zSql);
+ while( isspace(zSql[0]) ){ zSql++; }
+ len = strlen30(zSql);
for(pPreStmt = pDb->stmtList; pPreStmt; pPreStmt=pPreStmt->pNext){
int n = pPreStmt->nSql;
if( len>=n
@@ -1758,6 +1855,10 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
}
if( pScript ){
+ pDb->nStep = sqlite3_stmt_status(pStmt,
+ SQLITE_STMTSTATUS_FULLSCAN_STEP, 0);
+ pDb->nSort = sqlite3_stmt_status(pStmt,
+ SQLITE_STMTSTATUS_SORT, 0);
rc = Tcl_EvalObjEx(interp, pScript, 0);
if( rc==TCL_CONTINUE ){
rc = TCL_OK;
@@ -1796,6 +1897,10 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
** flush the statement cache and try the statement again.
*/
rc2 = sqlite3_reset(pStmt);
+ pDb->nStep = sqlite3_stmt_status(pStmt,
+ SQLITE_STMTSTATUS_FULLSCAN_STEP, 1);
+ pDb->nSort = sqlite3_stmt_status(pStmt,
+ SQLITE_STMTSTATUS_SORT, 1);
if( SQLITE_OK!=rc2 ){
/* If a run-time error occurs, report the error and stop reading
** the SQL
@@ -1821,7 +1926,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
pPreStmt->pStmt = pStmt;
pPreStmt->nSql = len;
pPreStmt->zSql = sqlite3_sql(pStmt);
- assert( strlen(pPreStmt->zSql)==len );
+ assert( strlen30(pPreStmt->zSql)==len );
assert( 0==memcmp(pPreStmt->zSql, zSql, len) );
}
@@ -1870,7 +1975,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
}
/*
- ** $db function NAME SCRIPT
+ ** $db function NAME [-argcount N] SCRIPT
**
** Create a new SQL function called NAME. Whenever that function is
** called, invoke SCRIPT to evaluate the function.
@@ -1879,12 +1984,26 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
SqlFunc *pFunc;
Tcl_Obj *pScript;
char *zName;
- if( objc!=4 ){
- Tcl_WrongNumArgs(interp, 2, objv, "NAME SCRIPT");
+ int nArg = -1;
+ if( objc==6 ){
+ const char *z = Tcl_GetString(objv[3]);
+ int n = strlen30(z);
+ if( n>2 && strncmp(z, "-argcount",n)==0 ){
+ if( Tcl_GetIntFromObj(interp, objv[4], &nArg) ) return TCL_ERROR;
+ if( nArg<0 ){
+ Tcl_AppendResult(interp, "number of arguments must be non-negative",
+ (char*)0);
+ return TCL_ERROR;
+ }
+ }
+ pScript = objv[5];
+ }else if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "NAME [-argcount N] SCRIPT");
return TCL_ERROR;
+ }else{
+ pScript = objv[3];
}
zName = Tcl_GetStringFromObj(objv[2], 0);
- pScript = objv[3];
pFunc = findSqlFunc(pDb, zName);
if( pFunc==0 ) return TCL_ERROR;
if( pFunc->pScript ){
@@ -1893,7 +2012,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
pFunc->pScript = pScript;
Tcl_IncrRefCount(pScript);
pFunc->useEvalObjv = safeToUseEvalObjv(interp, pScript);
- rc = sqlite3_create_function(pDb->db, zName, -1, SQLITE_UTF8,
+ rc = sqlite3_create_function(pDb->db, zName, nArg, SQLITE_UTF8,
pFunc, tclSqlFunc, 0, 0);
if( rc!=SQLITE_OK ){
rc = TCL_ERROR;
@@ -2110,6 +2229,92 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
break;
}
+ /* $db restore ?DATABASE? FILENAME
+ **
+ ** Open a database file named FILENAME. Transfer the content
+ ** of FILENAME into the local database DATABASE (default: "main").
+ */
+ case DB_RESTORE: {
+ const char *zSrcFile;
+ const char *zDestDb;
+ sqlite3 *pSrc;
+ sqlite3_backup *pBackup;
+ int nTimeout = 0;
+
+ if( objc==3 ){
+ zDestDb = "main";
+ zSrcFile = Tcl_GetString(objv[2]);
+ }else if( objc==4 ){
+ zDestDb = Tcl_GetString(objv[2]);
+ zSrcFile = Tcl_GetString(objv[3]);
+ }else{
+ Tcl_WrongNumArgs(interp, 2, objv, "?DATABASE? FILENAME");
+ return TCL_ERROR;
+ }
+ rc = sqlite3_open_v2(zSrcFile, &pSrc, SQLITE_OPEN_READONLY, 0);
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, "cannot open source database: ",
+ sqlite3_errmsg(pSrc), (char*)0);
+ sqlite3_close(pSrc);
+ return TCL_ERROR;
+ }
+ pBackup = sqlite3_backup_init(pDb->db, zDestDb, pSrc, "main");
+ if( pBackup==0 ){
+ Tcl_AppendResult(interp, "restore failed: ",
+ sqlite3_errmsg(pDb->db), (char*)0);
+ sqlite3_close(pSrc);
+ return TCL_ERROR;
+ }
+ while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
+ || rc==SQLITE_BUSY ){
+ if( rc==SQLITE_BUSY ){
+ if( nTimeout++ >= 3 ) break;
+ sqlite3_sleep(100);
+ }
+ }
+ sqlite3_backup_finish(pBackup);
+ if( rc==SQLITE_DONE ){
+ rc = TCL_OK;
+ }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
+ Tcl_AppendResult(interp, "restore failed: source database busy",
+ (char*)0);
+ rc = TCL_ERROR;
+ }else{
+ Tcl_AppendResult(interp, "restore failed: ",
+ sqlite3_errmsg(pDb->db), (char*)0);
+ rc = TCL_ERROR;
+ }
+ sqlite3_close(pSrc);
+ break;
+ }
+
+ /*
+ ** $db status (step|sort)
+ **
+ ** Display SQLITE_STMTSTATUS_FULLSCAN_STEP or
+ ** SQLITE_STMTSTATUS_SORT for the most recent eval.
+ */
+ case DB_STATUS: {
+ int v;
+ const char *zOp;
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "(step|sort)");
+ return TCL_ERROR;
+ }
+ zOp = Tcl_GetString(objv[2]);
+ if( strcmp(zOp, "step")==0 ){
+ v = pDb->nStep;
+ }else if( strcmp(zOp, "sort")==0 ){
+ v = pDb->nSort;
+ }else{
+ Tcl_AppendResult(interp, "bad argument: should be step or sort",
+ (char*)0);
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(v));
+ break;
+ }
+
/*
** $db timeout MILLESECONDS
**
@@ -2194,16 +2399,17 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
** 2005 O'Reilly Open Source Convention (OSCON).
*/
case DB_TRANSACTION: {
- int inTrans;
Tcl_Obj *pScript;
- const char *zBegin = "BEGIN";
+ const char *zBegin = "SAVEPOINT _tcl_transaction";
+ const char *zEnd;
if( objc!=3 && objc!=4 ){
Tcl_WrongNumArgs(interp, 2, objv, "[TYPE] SCRIPT");
return TCL_ERROR;
}
- if( objc==3 ){
- pScript = objv[2];
- } else {
+
+ if( pDb->nTransaction ){
+ zBegin = "SAVEPOINT _tcl_transaction";
+ }else if( pDb->nTransaction==0 && objc==4 ){
static const char *TTYPE_strs[] = {
"deferred", "exclusive", "immediate", 0
};
@@ -2220,24 +2426,91 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
case TTYPE_EXCLUSIVE: zBegin = "BEGIN EXCLUSIVE"; break;
case TTYPE_IMMEDIATE: zBegin = "BEGIN IMMEDIATE"; break;
}
- pScript = objv[3];
}
- inTrans = !sqlite3_get_autocommit(pDb->db);
- if( !inTrans ){
- (void)sqlite3_exec(pDb->db, zBegin, 0, 0, 0);
+ pScript = objv[objc-1];
+
+ pDb->disableAuth++;
+ rc = sqlite3_exec(pDb->db, zBegin, 0, 0, 0);
+ pDb->disableAuth--;
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), 0);
+ return TCL_ERROR;
}
+
+ pDb->nTransaction++;
rc = Tcl_EvalObjEx(interp, pScript, 0);
- if( !inTrans ){
- const char *zEnd;
- if( rc==TCL_ERROR ){
- zEnd = "ROLLBACK";
- } else {
+ pDb->nTransaction--;
+
+ if( rc!=TCL_ERROR ){
+ if( pDb->nTransaction ){
+ zEnd = "RELEASE _tcl_transaction";
+ }else{
zEnd = "COMMIT";
}
- if( sqlite3_exec(pDb->db, zEnd, 0, 0, 0) ){
- sqlite3_exec(pDb->db, "ROLLBACK", 0, 0, 0);
+ }else{
+ if( pDb->nTransaction ){
+ zEnd = "ROLLBACK TO _tcl_transaction ; RELEASE _tcl_transaction";
+ }else{
+ zEnd = "ROLLBACK";
+ }
+ }
+
+ pDb->disableAuth++;
+ if( sqlite3_exec(pDb->db, zEnd, 0, 0, 0) ){
+ /* This is a tricky scenario to handle. The most likely cause of an
+ ** error is that the exec() above was an attempt to commit the
+ ** top-level transaction that returned SQLITE_BUSY. Or, less likely,
+ ** that an IO-error has occured. In either case, throw a Tcl exception
+ ** and try to rollback the transaction.
+ **
+ ** But it could also be that the user executed one or more BEGIN,
+ ** COMMIT, SAVEPOINT, RELEASE or ROLLBACK commands that are confusing
+ ** this method's logic. Not clear how this would be best handled.
+ */
+ if( rc!=TCL_ERROR ){
+ Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), 0);
+ rc = TCL_ERROR;
}
+ sqlite3_exec(pDb->db, "ROLLBACK", 0, 0, 0);
}
+ pDb->disableAuth--;
+
+ break;
+ }
+
+ /*
+ ** $db unlock_notify ?script?
+ */
+ case DB_UNLOCK_NOTIFY: {
+#ifndef SQLITE_ENABLE_UNLOCK_NOTIFY
+ Tcl_AppendResult(interp, "unlock_notify not available in this build", 0);
+ rc = TCL_ERROR;
+#else
+ if( objc!=2 && objc!=3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
+ rc = TCL_ERROR;
+ }else{
+ void (*xNotify)(void **, int) = 0;
+ void *pNotifyArg = 0;
+
+ if( pDb->pUnlockNotify ){
+ Tcl_DecrRefCount(pDb->pUnlockNotify);
+ pDb->pUnlockNotify = 0;
+ }
+
+ if( objc==3 ){
+ xNotify = DbUnlockNotify;
+ pNotifyArg = (void *)pDb;
+ pDb->pUnlockNotify = objv[2];
+ Tcl_IncrRefCount(pDb->pUnlockNotify);
+ }
+
+ if( sqlite3_unlock_notify(pDb->db, xNotify, pNotifyArg) ){
+ Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), 0);
+ rc = TCL_ERROR;
+ }
+ }
+#endif
break;
}
@@ -2321,8 +2594,21 @@ static int DbMain(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
int i;
const char *zFile;
const char *zVfs = 0;
- int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
+ int flags;
Tcl_DString translatedFilename;
+
+ /* In normal use, each TCL interpreter runs in a single thread. So
+ ** by default, we can turn of mutexing on SQLite database connections.
+ ** However, for testing purposes it is useful to have mutexes turned
+ ** on. So, by default, mutexes default off. But if compiled with
+ ** SQLITE_TCL_DEFAULT_FULLMUTEX then mutexes default on.
+ */
+#ifdef SQLITE_TCL_DEFAULT_FULLMUTEX
+ flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
+#else
+ flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX;
+#endif
+
if( objc==2 ){
zArg = Tcl_GetStringFromObj(objv[1], 0);
if( strcmp(zArg,"-version")==0 ){
@@ -2368,9 +2654,19 @@ static int DbMain(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
if( Tcl_GetBooleanFromObj(interp, objv[i+1], &b) ) return TCL_ERROR;
if( b ){
flags |= SQLITE_OPEN_NOMUTEX;
+ flags &= ~SQLITE_OPEN_FULLMUTEX;
}else{
flags &= ~SQLITE_OPEN_NOMUTEX;
}
+ }else if( strcmp(zArg, "-fullmutex")==0 ){
+ int b;
+ if( Tcl_GetBooleanFromObj(interp, objv[i+1], &b) ) return TCL_ERROR;
+ if( b ){
+ flags |= SQLITE_OPEN_FULLMUTEX;
+ flags &= ~SQLITE_OPEN_NOMUTEX;
+ }else{
+ flags &= ~SQLITE_OPEN_FULLMUTEX;
+ }
}else{
Tcl_AppendResult(interp, "unknown option: ", zArg, (char*)0);
return TCL_ERROR;
@@ -2379,7 +2675,7 @@ static int DbMain(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
if( objc<3 || (objc&1)!=1 ){
Tcl_WrongNumArgs(interp, 1, objv,
"HANDLE FILENAME ?-vfs VFSNAME? ?-readonly BOOLEAN? ?-create BOOLEAN?"
- " ?-nomutex BOOLEAN?"
+ " ?-nomutex BOOLEAN? ?-fullmutex BOOLEAN?"
#ifdef SQLITE_HAS_CODEC
" ?-key CODECKEY?"
#endif
@@ -2458,12 +2754,21 @@ EXTERN int Sqlite3_Init(Tcl_Interp *interp){
EXTERN int Tclsqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); }
EXTERN int Sqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
EXTERN int Tclsqlite3_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
+EXTERN int Sqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }
+EXTERN int Tclsqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }
+EXTERN int Sqlite3_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK; }
+EXTERN int Tclsqlite3_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK;}
+
#ifndef SQLITE_3_SUFFIX_ONLY
EXTERN int Sqlite_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); }
EXTERN int Tclsqlite_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); }
EXTERN int Sqlite_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
EXTERN int Tclsqlite_SafeInit(Tcl_Interp *interp){ return TCL_OK; }
+EXTERN int Sqlite_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }
+EXTERN int Tclsqlite_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }
+EXTERN int Sqlite_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK; }
+EXTERN int Tclsqlite_SafeUnload(Tcl_Interp *interp, int flags){ return TCL_OK;}
#endif
#ifdef TCLSH
@@ -2516,6 +2821,12 @@ static char zMainloop[] =
#define TCLSH_MAIN main /* Needed to fake out mktclapp */
int TCLSH_MAIN(int argc, char **argv){
Tcl_Interp *interp;
+
+ /* Call sqlite3_shutdown() once before doing anything else. This is to
+ ** test that sqlite3_shutdown() can be safely called by a process before
+ ** sqlite3_initialize() is. */
+ sqlite3_shutdown();
+
Tcl_FindExecutable(argv[0]);
interp = Tcl_CreateInterp();
Sqlite3_Init(interp);
@@ -2536,6 +2847,7 @@ int TCLSH_MAIN(int argc, char **argv){
extern int Sqlitetest_autoext_Init(Tcl_Interp*);
extern int Sqlitetest_func_Init(Tcl_Interp*);
extern int Sqlitetest_hexio_Init(Tcl_Interp*);
+ extern int Sqlitetest_init_Init(Tcl_Interp*);
extern int Sqlitetest_malloc_Init(Tcl_Interp*);
extern int Sqlitetest_mutex_Init(Tcl_Interp*);
extern int Sqlitetestschema_Init(Tcl_Interp*);
@@ -2544,6 +2856,7 @@ int TCLSH_MAIN(int argc, char **argv){
extern int SqlitetestThread_Init(Tcl_Interp*);
extern int SqlitetestOnefile_Init();
extern int SqlitetestOsinst_Init(Tcl_Interp*);
+ extern int Sqlitetestbackup_Init(Tcl_Interp*);
Md5_Init(interp);
Sqliteconfig_Init(interp);
@@ -2560,6 +2873,7 @@ int TCLSH_MAIN(int argc, char **argv){
Sqlitetest_autoext_Init(interp);
Sqlitetest_func_Init(interp);
Sqlitetest_hexio_Init(interp);
+ Sqlitetest_init_Init(interp);
Sqlitetest_malloc_Init(interp);
Sqlitetest_mutex_Init(interp);
Sqlitetestschema_Init(interp);
@@ -2567,6 +2881,7 @@ int TCLSH_MAIN(int argc, char **argv){
SqlitetestThread_Init(interp);
SqlitetestOnefile_Init(interp);
SqlitetestOsinst_Init(interp);
+ Sqlitetestbackup_Init(interp);
#ifdef SQLITE_SSE
Sqlitetestsse_Init(interp);
@@ -2586,7 +2901,7 @@ int TCLSH_MAIN(int argc, char **argv){
}
if( TCLSH==1 && Tcl_EvalFile(interp, argv[1])!=TCL_OK ){
const char *zInfo = Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY);
- if( zInfo==0 ) zInfo = interp->result;
+ if( zInfo==0 ) zInfo = Tcl_GetStringResult(interp);
fprintf(stderr,"%s: %s\n", *argv, zInfo);
return 1;
}