Add the sqlite3_rollback_hook() API. Still requires further testing. (CVS 2823)

FossilOrigin-Name: 3baa3ff32435b64e7ae7646b17a98fef9296aaa0
diff --git a/src/btree.c b/src/btree.c
index ba5e6c4..65b5594 100644
--- a/src/btree.c
+++ b/src/btree.c
@@ -9,7 +9,7 @@
 **    May you share freely, never taking more than you give.
 **
 *************************************************************************
-** $Id: btree.c,v 1.273 2005/12/09 20:02:05 drh Exp $
+** $Id: btree.c,v 1.274 2005/12/16 06:54:02 danielk1977 Exp $
 **
 ** This file implements a external (disk-based) database using BTrees.
 ** For a detailed discussion of BTrees, refer to
@@ -4906,9 +4906,11 @@
     }
   }
   rc = clearDatabasePage(pBt, (Pgno)iTable, 0, 0);
+#if 0
   if( rc ){
     sqlite3BtreeRollback(pBt);
   }
+#endif
   return rc;
 }
 
diff --git a/src/main.c b/src/main.c
index 8bc6bb8..ba0bb5f 100644
--- a/src/main.c
+++ b/src/main.c
@@ -14,7 +14,7 @@
 ** other files are for internal use by SQLite and should not be
 ** accessed by users of the library.
 **
-** $Id: main.c,v 1.310 2005/12/15 15:22:09 danielk1977 Exp $
+** $Id: main.c,v 1.311 2005/12/16 06:54:02 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -210,7 +210,14 @@
       db->aDb[i].inTrans = 0;
     }
   }
-  sqlite3ResetInternalSchema(db, 0);
+  if( db->flags&SQLITE_InternChanges ){
+    sqlite3ResetInternalSchema(db, 0);
+  }
+
+  /* If one has been configured, invoke the rollback-hook callback */
+  if( db->xRollbackCallback ){
+    db->xRollbackCallback(db->pRollbackArg);
+  }
 }
 
 /*
@@ -534,7 +541,7 @@
 /*** EXPERIMENTAL ***
 **
 ** Register a function to be invoked when a transaction comments.
-** If either function returns non-zero, then the commit becomes a
+** If the invoked function returns non-zero, then the commit becomes a
 ** rollback.
 */
 void *sqlite3_commit_hook(
@@ -552,15 +559,31 @@
 ** Register a callback to be invoked each time a row is updated,
 ** inserted or deleted using this database connection.
 */
-void sqlite3_update_hook(
+void *sqlite3_update_hook(
   sqlite3 *db,              /* Attach the hook to this database */
   void (*xCallback)(void*,int,char const *,char const *,sqlite_int64),
   void *pArg                /* Argument to the function */
 ){
+  void *pRet = db->pUpdateArg;
   db->xUpdateCallback = xCallback;
   db->pUpdateArg = pArg;
+  return pRet;
 }
 
+/*
+** Register a callback to be invoked each time a transaction is rolled
+** back by this database connection.
+*/
+void *sqlite3_rollback_hook(
+  sqlite3 *db,              /* Attach the hook to this database */
+  void (*xCallback)(void*), /* Callback function */
+  void *pArg                /* Argument to the function */
+){
+  void *pRet = db->pRollbackArg;
+  db->xRollbackCallback = xCallback;
+  db->pRollbackArg = pArg;
+  return pRet;
+}
 
 /*
 ** This routine is called to create a connection to a database BTree
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 86d75d2..966b38a 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -12,7 +12,7 @@
 ** This header file defines the interface that the SQLite library
 ** presents to client programs.
 **
-** @(#) $Id: sqlite.h.in,v 1.146 2005/12/15 15:22:09 danielk1977 Exp $
+** @(#) $Id: sqlite.h.in,v 1.147 2005/12/16 06:54:02 danielk1977 Exp $
 */
 #ifndef _SQLITE3_H_
 #define _SQLITE3_H_
@@ -1309,13 +1309,18 @@
 **
 ** The update hook is not invoked when internal system tables are
 ** modified (i.e. sqlite_master and sqlite_sequence).
+**
+** If another function was previously registered, its pArg value is returned.
+** Otherwise NULL is returned.
 */
-void sqlite3_update_hook(
+void *sqlite3_update_hook(
   sqlite3*, 
   void(*)(void *,int ,char const *,char const *,sqlite_int64),
   void*
 );
 
+void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*);
+
 /*
 ** Undo the hack that converts floating point types to integer for
 ** builds on processors without floating point support.
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 0285677..11624ca 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.438 2005/12/16 01:06:17 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.439 2005/12/16 06:54:02 danielk1977 Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -440,8 +440,10 @@
   void *pTraceArg;                          /* Argument to the trace function */
   void (*xProfile)(void*,const char*,u64);  /* Profiling function */
   void *pProfileArg;                        /* Argument to profile function */
-  void *pCommitArg;             /* Argument to xCommitCallback() */   
-  int (*xCommitCallback)(void*);/* Invoked at every commit. */
+  void *pCommitArg;                 /* Argument to xCommitCallback() */   
+  int (*xCommitCallback)(void*);    /* Invoked at every commit. */
+  void *pRollbackArg;               /* Argument to xRollbackCallback() */   
+  void (*xRollbackCallback)(void*); /* Invoked at every commit. */
   void *pUpdateArg;
   void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
   void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index b862df3..337cdef 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -11,7 +11,7 @@
 *************************************************************************
 ** A TCL Interface to SQLite
 **
-** $Id: tclsqlite.c,v 1.139 2005/12/15 15:22:10 danielk1977 Exp $
+** $Id: tclsqlite.c,v 1.140 2005/12/16 06:54:03 danielk1977 Exp $
 */
 #ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */
 
@@ -100,6 +100,7 @@
   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) */
   SqlCollate *pCollate;      /* List of SQL collation functions */
   int rc;                    /* Return code of most recent sqlite3_exec() */
   Tcl_Obj *pCollateNeeded;   /* Collation needed script */
@@ -214,6 +215,9 @@
   if( pDb->pUpdateHook ){
     Tcl_DecrRefCount(pDb->pUpdateHook);
   }
+  if( pDb->pRollbackHook ){
+    Tcl_DecrRefCount(pDb->pRollbackHook);
+  }
   if( pDb->pCollateNeeded ){
     Tcl_DecrRefCount(pDb->pCollateNeeded);
   }
@@ -304,6 +308,14 @@
   return 0;
 }
 
+static void DbRollbackHandler(void *clientData){
+  SqliteDb *pDb = (SqliteDb*)clientData;
+  assert(pDb->pRollbackHook);
+  if( TCL_OK!=Tcl_EvalObjEx(pDb->interp, pDb->pRollbackHook, 0) ){
+    Tcl_BackgroundError(pDb->interp);
+  }
+}
+
 static void DbUpdateHandler(
   void *p, 
   int op,
@@ -653,10 +665,10 @@
     "copy",               "errorcode",         "eval",
     "exists",             "function",          "last_insert_rowid",
     "nullvalue",          "onecolumn",         "profile",
-    "progress",           "rekey",             "soft_heap_limit",
-    "timeout",            "total_changes",     "trace",
-    "transaction",        "update_hook",       "version",
-    0                    
+    "progress",           "rekey",             "rollback_hook",
+    "soft_heap_limit",    "timeout",           "total_changes",
+    "trace",              "transaction",       "update_hook",       
+    "version",            0                    
   };
   enum DB_enum {
     DB_AUTHORIZER,        DB_BUSY,             DB_CACHE,
@@ -665,9 +677,10 @@
     DB_COPY,              DB_ERRORCODE,        DB_EVAL,
     DB_EXISTS,            DB_FUNCTION,         DB_LAST_INSERT_ROWID,
     DB_NULLVALUE,         DB_ONECOLUMN,        DB_PROFILE,
-    DB_PROGRESS,          DB_REKEY,            DB_SOFT_HEAP_LIMIT,
-    DB_TIMEOUT,           DB_TOTAL_CHANGES,    DB_TRACE,
-    DB_TRANSACTION,       DB_UPDATE_HOOK,      DB_VERSION
+    DB_PROGRESS,          DB_REKEY,            DB_ROLLBACK_HOOK,
+    DB_SOFT_HEAP_LIMIT,   DB_TIMEOUT,          DB_TOTAL_CHANGES,    
+    DB_TRACE,             DB_TRANSACTION,      DB_UPDATE_HOOK,      
+    DB_VERSION
   };
   /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
 
@@ -1872,28 +1885,43 @@
 
   /*
   **    $db update_hook ?script?
+  **    $db rollback_hook ?script?
   */
-  case DB_UPDATE_HOOK: {
+  case DB_UPDATE_HOOK: 
+  case DB_ROLLBACK_HOOK: {
+
+    /* 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{
+      ppHook = &pDb->pRollbackHook;
+    }
+
     if( objc!=2 && objc!=3 ){
        Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
        return TCL_ERROR;
     }
-    if( pDb->pUpdateHook ){
-      Tcl_SetObjResult(interp, pDb->pUpdateHook);
+    if( *ppHook ){
+      Tcl_SetObjResult(interp, *ppHook);
       if( objc==3 ){
-        Tcl_DecrRefCount(pDb->pUpdateHook);
-        pDb->pUpdateHook = 0;
+        Tcl_DecrRefCount(*ppHook);
+        *ppHook = 0;
       }
     }
     if( objc==3 ){
+      assert( !(*ppHook) );
       if( Tcl_GetCharLength(objv[2])>0 ){
-        pDb->pUpdateHook = objv[2];
-        Tcl_IncrRefCount(pDb->pUpdateHook);
-        sqlite3_update_hook(pDb->db, DbUpdateHandler, pDb);
-      }else{
-        sqlite3_update_hook(pDb->db, 0, 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);
+
     break;
   }
 
@@ -2160,7 +2188,7 @@
     Sqlitetest4_Init(interp);
     Sqlitetest5_Init(interp);
     Sqlitetest6_Init(interp);
-    Sqlitetestasync_Init(interp);
+    /* Sqlitetestasync_Init(interp); */
     Md5_Init(interp);
 #ifdef SQLITE_SSE
     Sqlitetestsse_Init(interp);
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index 6949f8c..a99dc46 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -1233,17 +1233,27 @@
     }
   }
 
-  /* If xFunc is not NULL, then it is one of sqlite3BtreeRollback,
+  /* If xFunc is not NULL, then it is one of 
   ** sqlite3BtreeRollbackStmt or sqlite3BtreeCommitStmt. Call it once on
   ** each backend. If an error occurs and the return code is still
   ** SQLITE_OK, set the return code to the new error value.
   */
-  for(i=0; xFunc && i<db->nDb; i++){ 
-    int rc;
-    Btree *pBt = db->aDb[i].pBt;
-    if( pBt ){
-      rc = xFunc(pBt);
-      if( p->rc==SQLITE_OK ) p->rc = rc;
+  assert(!xFunc ||
+    xFunc==sqlite3BtreeCommitStmt ||
+    xFunc==sqlite3BtreeRollbackStmt ||
+    xFunc==sqlite3BtreeRollback
+  );
+  if( xFunc==sqlite3BtreeRollback ){
+    assert( p->rc!=SQLITE_OK );
+    sqlite3RollbackAll(db);
+  }else{
+    for(i=0; xFunc && i<db->nDb; i++){ 
+      int rc;
+      Btree *pBt = db->aDb[i].pBt;
+      if( pBt ){
+        rc = xFunc(pBt);
+        if( p->rc==SQLITE_OK ) p->rc = rc;
+      }
     }
   }