diff options
author | drh <drh@noemail.net> | 2019-02-28 17:29:19 +0000 |
---|---|---|
committer | drh <drh@noemail.net> | 2019-02-28 17:29:19 +0000 |
commit | c06ede105dd2789a595b59ab2a6b7add3396f9e8 (patch) | |
tree | ba130868f864e8c9fd221485c707c676a350bc5d /src/tclsqlite.c | |
parent | 4ddf7f42115eb93c489f966e035dec00af57aa39 (diff) | |
download | sqlite-c06ede105dd2789a595b59ab2a6b7add3396f9e8.tar.gz sqlite-c06ede105dd2789a595b59ab2a6b7add3396f9e8.zip |
Add the new "bind_fallback" method to the "sqlite3" object in the TCL
interface.
FossilOrigin-Name: c7f70b6d96338dba201e005104e7f7148c1a8cd767ab05e35b44617c4c797bc5
Diffstat (limited to 'src/tclsqlite.c')
-rw-r--r-- | src/tclsqlite.c | 127 |
1 files changed, 99 insertions, 28 deletions
diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 1b0086c7d..a78d5676c 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -159,6 +159,7 @@ struct SqliteDb { char *zTraceV2; /* The trace_v2 callback routine */ char *zProfile; /* The profile callback routine */ char *zProgress; /* The progress callback routine */ + char *zBindFallback; /* Callback to invoke on a binding miss */ char *zAuth; /* The authorization callback routine */ int disableAuth; /* Disable the authorizer if it exists */ char *zNull; /* Text to substitute for an SQL NULL value */ @@ -549,6 +550,9 @@ static void SQLITE_TCLAPI DbDeleteCmd(void *db){ if( pDb->zProfile ){ Tcl_Free(pDb->zProfile); } + if( pDb->zBindFallback ){ + Tcl_Free(pDb->zBindFallback); + } if( pDb->zAuth ){ Tcl_Free(pDb->zAuth); } @@ -1301,6 +1305,8 @@ static int dbPrepareAndBind( int iParm = 0; /* Next free entry in apParm */ char c; int i; + int needResultReset = 0; /* Need to invoke Tcl_ResetResult() */ + int rc = SQLITE_OK; /* Value to return */ Tcl_Interp *interp = pDb->interp; *ppPreStmt = 0; @@ -1388,6 +1394,25 @@ static int dbPrepareAndBind( const char *zVar = sqlite3_bind_parameter_name(pStmt, i); if( zVar!=0 && (zVar[0]=='$' || zVar[0]==':' || zVar[0]=='@') ){ Tcl_Obj *pVar = Tcl_GetVar2Ex(interp, &zVar[1], 0, 0); + if( pVar==0 && pDb->zBindFallback!=0 ){ + Tcl_Obj *pCmd; + int rx; + pCmd = Tcl_NewStringObj(pDb->zBindFallback, -1); + Tcl_IncrRefCount(pCmd); + Tcl_ListObjAppendElement(interp, pCmd, Tcl_NewStringObj(zVar,-1)); + if( needResultReset ) Tcl_ResetResult(interp); + needResultReset = 1; + rx = Tcl_EvalObjEx(interp, pCmd, TCL_EVAL_DIRECT); + Tcl_DecrRefCount(pCmd); + if( rx==TCL_OK ){ + pVar = Tcl_GetObjResult(interp); + }else if( rx==TCL_ERROR ){ + rc = TCL_ERROR; + break; + }else{ + pVar = 0; + } + } if( pVar ){ int n; u8 *data; @@ -1423,12 +1448,14 @@ static int dbPrepareAndBind( }else{ sqlite3_bind_null(pStmt, i); } + if( needResultReset ) Tcl_ResetResult(pDb->interp); } } pPreStmt->nParm = iParm; *ppPreStmt = pPreStmt; + if( needResultReset && rc==TCL_OK ) Tcl_ResetResult(pDb->interp); - return TCL_OK; + return rc; } /* @@ -1887,35 +1914,36 @@ static int SQLITE_TCLAPI DbObjCmd( int choice; int rc = TCL_OK; static const char *DB_strs[] = { - "authorizer", "backup", "busy", - "cache", "changes", "close", - "collate", "collation_needed", "commit_hook", - "complete", "copy", "deserialize", - "enable_load_extension", "errorcode", "eval", - "exists", "function", "incrblob", - "interrupt", "last_insert_rowid", "nullvalue", - "onecolumn", "preupdate", "profile", - "progress", "rekey", "restore", - "rollback_hook", "serialize", "status", - "timeout", "total_changes", "trace", - "trace_v2", "transaction", "unlock_notify", - "update_hook", "version", "wal_hook", - 0 + "authorizer", "backup", "bind_fallback", + "busy", "cache", "changes", + "close", "collate", "collation_needed", + "commit_hook", "complete", "copy", + "deserialize", "enable_load_extension", "errorcode", + "eval", "exists", "function", + "incrblob", "interrupt", "last_insert_rowid", + "nullvalue", "onecolumn", "preupdate", + "profile", "progress", "rekey", + "restore", "rollback_hook", "serialize", + "status", "timeout", "total_changes", + "trace", "trace_v2", "transaction", + "unlock_notify", "update_hook", "version", + "wal_hook", 0 }; enum DB_enum { - 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_DESERIALIZE, - 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_PREUPDATE, DB_PROFILE, - DB_PROGRESS, DB_REKEY, DB_RESTORE, - DB_ROLLBACK_HOOK, DB_SERIALIZE, DB_STATUS, - DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE, - DB_TRACE_V2, DB_TRANSACTION, DB_UNLOCK_NOTIFY, - DB_UPDATE_HOOK, DB_VERSION, DB_WAL_HOOK + DB_AUTHORIZER, DB_BACKUP, DB_BIND_FALLBACK, + DB_BUSY, DB_CACHE, DB_CHANGES, + DB_CLOSE, DB_COLLATE, DB_COLLATION_NEEDED, + DB_COMMIT_HOOK, DB_COMPLETE, DB_COPY, + DB_DESERIALIZE, 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_PREUPDATE, + DB_PROFILE, DB_PROGRESS, DB_REKEY, + DB_RESTORE, DB_ROLLBACK_HOOK, DB_SERIALIZE, + DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES, + DB_TRACE, DB_TRACE_V2, DB_TRANSACTION, + DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, DB_VERSION, + DB_WAL_HOOK }; /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */ @@ -2037,6 +2065,49 @@ static int SQLITE_TCLAPI DbObjCmd( break; } + /* $db bind_fallback ?CALLBACK? + ** + ** When resolving bind parameters in an SQL statement, if the parameter + ** cannot be associated with a TCL variable then invoke CALLBACK with a + ** single argument that is the name of the parameter and use the return + ** value of the CALLBACK as the binding. If CALLBACK returns something + ** other than TCL_OK or TCL_ERROR then bind a NULL. + ** + ** If CALLBACK is an empty string, then revert to the default behavior + ** which is to set the binding to NULL. + ** + ** If CALLBACK returns an error, that causes the statement execution to + ** abort. Hence, to configure a connection so that it throws an error + ** on an attempt to bind an unknown variable, do something like this: + ** + ** proc bind_error {name} {error "no such variable: $name"} + ** db bind_fallback bind_error + */ + case DB_BIND_FALLBACK: { + if( objc>3 ){ + Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?"); + return TCL_ERROR; + }else if( objc==2 ){ + if( pDb->zBindFallback ){ + Tcl_AppendResult(interp, pDb->zBindFallback, (char*)0); + } + }else{ + char *zCallback; + int len; + if( pDb->zBindFallback ){ + Tcl_Free(pDb->zBindFallback); + } + zCallback = Tcl_GetStringFromObj(objv[2], &len); + if( zCallback && len>0 ){ + pDb->zBindFallback = Tcl_Alloc( len + 1 ); + memcpy(pDb->zBindFallback, zCallback, len+1); + }else{ + pDb->zBindFallback = 0; + } + } + break; + } + /* $db busy ?CALLBACK? ** ** Invoke the given callback if an SQL statement attempts to open |