1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-07 02:42:48 +03:00

Add the experimental sqlite3_preupdate_hook() API.

FossilOrigin-Name: 6145d7b89f83500318713779c60f79a7ab2098ba
This commit is contained in:
dan
2011-03-01 18:42:07 +00:00
parent 30f776fadb
commit 46c47d4677
18 changed files with 737 additions and 117 deletions

View File

@@ -122,6 +122,7 @@ struct SqliteDb {
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 *pPreUpdateHook; /* Pre-update hook script (if any) */
Tcl_Obj *pRollbackHook; /* Rollback hook script (if any) */
Tcl_Obj *pWalHook; /* WAL hook script (if any) */
Tcl_Obj *pUnlockNotify; /* Unlock notify script (if any) */
@@ -483,6 +484,9 @@ static void DbDeleteCmd(void *db){
if( pDb->pUpdateHook ){
Tcl_DecrRefCount(pDb->pUpdateHook);
}
if( pDb->pPreUpdateHook ){
Tcl_DecrRefCount(pDb->pPreUpdateHook);
}
if( pDb->pRollbackHook ){
Tcl_DecrRefCount(pDb->pRollbackHook);
}
@@ -649,6 +653,40 @@ static void DbUnlockNotify(void **apArg, int nArg){
}
#endif
/*
** Pre-update hook callback.
*/
static void DbPreUpdateHandler(
void *p,
sqlite3 *db,
int op,
const char *zDb,
const char *zTbl,
sqlite_int64 iKey1,
sqlite_int64 iKey2
){
SqliteDb *pDb = (SqliteDb *)p;
Tcl_Obj *pCmd;
static const char *azStr[] = {"DELETE", "INSERT", "UPDATE"};
assert( (SQLITE_DELETE-1)/9 == 0 );
assert( (SQLITE_INSERT-1)/9 == 1 );
assert( (SQLITE_UPDATE-1)/9 == 2 );
assert( pDb->pPreUpdateHook );
assert( db==pDb->db );
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
pCmd = Tcl_DuplicateObj(pDb->pPreUpdateHook);
Tcl_IncrRefCount(pCmd);
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(azStr[(op-1)/9], -1));
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zDb, -1));
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zTbl, -1));
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(iKey1));
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(iKey2));
Tcl_EvalObjEx(pDb->interp, pCmd, TCL_EVAL_DIRECT);
Tcl_DecrRefCount(pCmd);
}
static void DbUpdateHandler(
void *p,
int op,
@@ -658,14 +696,18 @@ static void DbUpdateHandler(
){
SqliteDb *pDb = (SqliteDb *)p;
Tcl_Obj *pCmd;
static const char *azStr[] = {"DELETE", "INSERT", "UPDATE"};
assert( (SQLITE_DELETE-1)/9 == 0 );
assert( (SQLITE_INSERT-1)/9 == 1 );
assert( (SQLITE_UPDATE-1)/9 == 2 );
assert( pDb->pUpdateHook );
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
pCmd = Tcl_DuplicateObj(pDb->pUpdateHook);
Tcl_IncrRefCount(pCmd);
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(
( (op==SQLITE_INSERT)?"INSERT":(op==SQLITE_UPDATE)?"UPDATE":"DELETE"), -1));
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(azStr[(op-1)/9], -1));
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zDb, -1));
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewStringObj(zTbl, -1));
Tcl_ListObjAppendElement(0, pCmd, Tcl_NewWideIntObj(rowid));
@@ -1550,6 +1592,44 @@ static int DbEvalNextCmd(
return rc;
}
/*
** This function is used by the implementations of the following database
** handle sub-commands:
**
** $db update_hook ?SCRIPT?
** $db wal_hook ?SCRIPT?
** $db commit_hook ?SCRIPT?
** $db preupdate hook ?SCRIPT?
*/
static void DbHookCmd(
Tcl_Interp *interp, /* Tcl interpreter */
SqliteDb *pDb, /* Database handle */
Tcl_Obj *pArg, /* SCRIPT argument (or NULL) */
Tcl_Obj **ppHook /* Pointer to member of SqliteDb */
){
sqlite3 *db = pDb->db;
if( *ppHook ){
Tcl_SetObjResult(interp, *ppHook);
if( pArg ){
Tcl_DecrRefCount(*ppHook);
*ppHook = 0;
}
}
if( pArg ){
assert( !(*ppHook) );
if( Tcl_GetCharLength(pArg)>0 ){
*ppHook = pArg;
Tcl_IncrRefCount(*ppHook);
}
}
sqlite3_preupdate_hook(db, (pDb->pPreUpdateHook?DbPreUpdateHandler:0), pDb);
sqlite3_update_hook(db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
sqlite3_rollback_hook(db, (pDb->pRollbackHook?DbRollbackHandler:0), pDb);
sqlite3_wal_hook(db, (pDb->pWalHook?DbWalHandler:0), pDb);
}
/*
** The "sqlite" command below creates a new Tcl command for each
** connection it opens to an SQLite database. This routine is invoked
@@ -1575,6 +1655,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
"errorcode", "eval", "exists",
"function", "incrblob", "interrupt",
"last_insert_rowid", "nullvalue", "onecolumn",
"preupdate",
"profile", "progress", "rekey",
"restore", "rollback_hook", "status",
"timeout", "total_changes", "trace",
@@ -1589,6 +1670,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
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_STATUS,
DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
@@ -2763,6 +2845,71 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
break;
}
case DB_PREUPDATE: {
static const char *azSub[] = {"count", "hook", "modified", "old", 0};
enum DbPreupdateSubCmd {
PRE_COUNT, PRE_HOOK, PRE_MODIFIED, PRE_OLD
};
int iSub;
if( objc<3 ){
Tcl_WrongNumArgs(interp, 2, objv, "SUB-COMMAND ?ARGS?");
}
if( Tcl_GetIndexFromObj(interp, objv[2], azSub, "sub-command", 0, &iSub) ){
return TCL_ERROR;
}
switch( (enum DbPreupdateSubCmd)iSub ){
case PRE_COUNT: {
int nCol = sqlite3_preupdate_count(pDb->db);
Tcl_SetObjResult(interp, Tcl_NewIntObj(nCol));
break;
}
case PRE_HOOK: {
if( objc>4 ){
Tcl_WrongNumArgs(interp, 2, objv, "hook ?SCRIPT?");
return TCL_ERROR;
}
DbHookCmd(interp, pDb, (objc==4 ? objv[3] : 0), &pDb->pPreUpdateHook);
break;
}
case PRE_MODIFIED:
case PRE_OLD: {
int iIdx;
if( objc!=4 ){
Tcl_WrongNumArgs(interp, 3, objv, "INDEX");
return TCL_ERROR;
}
if( Tcl_GetIntFromObj(interp, objv[3], &iIdx) ){
return TCL_ERROR;
}
if( iSub==PRE_MODIFIED ){
int iRes;
rc = sqlite3_preupdate_modified(pDb->db, iIdx, &iRes);
if( rc==SQLITE_OK ) Tcl_SetObjResult(interp, Tcl_NewIntObj(iRes));
}else{
sqlite3_value *pValue;
assert( iSub==PRE_OLD );
rc = sqlite3_preupdate_old(pDb->db, iIdx, &pValue);
if( rc==SQLITE_OK ){
Tcl_Obj *pObj = Tcl_NewStringObj(sqlite3_value_text(pValue), -1);
Tcl_SetObjResult(interp, pObj);
}
}
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, sqlite3_errmsg(pDb->db), 0);
return TCL_ERROR;
}
}
}
break;
}
/*
** $db wal_hook ?script?
** $db update_hook ?script?
@@ -2771,42 +2918,21 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
case DB_WAL_HOOK:
case DB_UPDATE_HOOK:
case DB_ROLLBACK_HOOK: {
sqlite3 *db = pDb->db;
/* set ppHook to point at pUpdateHook or pRollbackHook, depending on
** whether [$db update_hook] or [$db rollback_hook] was invoked.
*/
Tcl_Obj **ppHook;
if( choice==DB_UPDATE_HOOK ){
ppHook = &pDb->pUpdateHook;
}else if( choice==DB_WAL_HOOK ){
ppHook = &pDb->pWalHook;
}else{
ppHook = &pDb->pRollbackHook;
}
if( objc!=2 && objc!=3 ){
if( choice==DB_WAL_HOOK ) ppHook = &pDb->pWalHook;
if( choice==DB_UPDATE_HOOK ) ppHook = &pDb->pUpdateHook;
if( choice==DB_ROLLBACK_HOOK ) ppHook = &pDb->pRollbackHook;
if( objc>3 ){
Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
return TCL_ERROR;
}
if( *ppHook ){
Tcl_SetObjResult(interp, *ppHook);
if( objc==3 ){
Tcl_DecrRefCount(*ppHook);
*ppHook = 0;
}
}
if( objc==3 ){
assert( !(*ppHook) );
if( Tcl_GetCharLength(objv[2])>0 ){
*ppHook = objv[2];
Tcl_IncrRefCount(*ppHook);
}
}
sqlite3_update_hook(pDb->db, (pDb->pUpdateHook?DbUpdateHandler:0), pDb);
sqlite3_rollback_hook(pDb->db,(pDb->pRollbackHook?DbRollbackHandler:0),pDb);
sqlite3_wal_hook(pDb->db,(pDb->pWalHook?DbWalHandler:0),pDb);
DbHookCmd(interp, pDb, (objc==3 ? objv[2] : 0), ppHook);
break;
}