Reinsert the experimental sqlite_commit_hook() API. (CVS 1179)

FossilOrigin-Name: 72bc84f2f18f6eeb279a4ad670310e85d154f663
diff --git a/src/main.c b/src/main.c
index 6386495..2b4a396 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.144 2003/12/06 21:43:56 drh Exp $
+** $Id: main.c,v 1.145 2004/01/15 02:44:03 drh Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -976,6 +976,24 @@
   return pOld;
 }
 
+/*** EXPERIMENTAL ***
+**
+** Register a function to be invoked when a transaction comments.
+** If either function returns non-zero, then the commit becomes a
+** rollback.
+*/
+void *sqlite_commit_hook(
+  sqlite *db,               /* Attach the hook to this database */
+  int (*xCallback)(void*),  /* Function to invoke on each commit */
+  void *pArg                /* Argument to the function */
+){
+  void *pOld = db->pCommitArg;
+  db->xCommitCallback = xCallback;
+  db->pCommitArg = pArg;
+  return pOld;
+}
+
+
 /*
 ** This routine is called to create a connection to a database BTree
 ** driver.  If zFilename is the name of a file, then that file is
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 6f87121..e493a13 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.53 2003/10/18 09:37:26 danielk1977 Exp $
+** @(#) $Id: sqlite.h.in,v 1.54 2004/01/15 02:44:03 drh Exp $
 */
 #ifndef _SQLITE_H_
 #define _SQLITE_H_
@@ -753,9 +753,26 @@
 ** query is immediately terminated and any database changes rolled back. If the
 ** query was part of a larger transaction, then the transaction is not rolled
 ** back and remains active. The sqlite_exec() call returns SQLITE_ABORT. 
+**
+******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ******
 */
 void sqlite_progress_handler(sqlite*, int, int(*)(void*), void*);
 
+/*
+** Register a callback function to be invoked whenever a new transaction
+** is committed.  The pArg argument is passed through to the callback.
+** callback.  If the callback function returns non-zero, then the commit
+** is converted into a rollback.
+**
+** If another function was previously registered, its pArg value is returned.
+** Otherwise NULL is returned.
+**
+** Registering a NULL function disables the callback.
+**
+******* THIS IS AN EXPERIMENTAL API AND IS SUBJECT TO CHANGE ******
+*/
+void *sqlite_commit_hook(sqlite*, int(*)(void*), void*);
+
 #ifdef __cplusplus
 }  /* End of the 'extern "C"' block */
 #endif
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index c90190a..9894786 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.207 2004/01/07 03:04:27 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.208 2004/01/15 02:44:03 drh Exp $
 */
 #include "config.h"
 #include "sqlite.h"
@@ -322,6 +322,8 @@
   int nTable;                   /* Number of tables in the database */
   void *pBusyArg;               /* 1st Argument to the busy callback */
   int (*xBusyCallback)(void *,const char*,int);  /* The busy callback */
+  void *pCommitArg;             /* Argument to xCommitCallback() */   
+  int (*xCommitCallback)(void*);/* Invoked at every commit. */
   Hash aFunc;                   /* All functions that can be in SQL exprs */
   int lastRowid;                /* ROWID of most recent insert */
   int priorNewRowid;            /* Last randomly generated ROWID */
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index c2f4cfd..7070630 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -11,7 +11,7 @@
 *************************************************************************
 ** A TCL Interface to SQLite
 **
-** $Id: tclsqlite.c,v 1.53 2003/12/19 12:32:46 drh Exp $
+** $Id: tclsqlite.c,v 1.54 2004/01/15 02:44:03 drh Exp $
 */
 #ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */
 
@@ -51,6 +51,7 @@
   sqlite *db;           /* The "real" database structure */
   Tcl_Interp *interp;   /* The interpreter used for this database */
   char *zBusy;          /* The busy callback routine */
+  char *zCommit;        /* The commit hook callback routine */
   char *zTrace;         /* The trace callback routine */
   char *zProgress;      /* The progress callback routine */
   char *zAuth;          /* The authorization callback routine */
@@ -358,6 +359,23 @@
 }
 
 /*
+** This routine is called when a transaction is committed.  The
+** TCL script in pDb->zCommit is executed.  If it returns non-zero or
+** if it throws an exception, the transaction is rolled back instead
+** of being committed.
+*/
+static int DbCommitHandler(void *cd){
+  SqliteDb *pDb = (SqliteDb*)cd;
+  int rc;
+
+  rc = Tcl_Eval(pDb->interp, pDb->zCommit);
+  if( rc!=TCL_OK || atoi(Tcl_GetStringResult(pDb->interp)) ){
+    return 1;
+  }
+  return 0;
+}
+
+/*
 ** This routine is called to evaluate an SQL function implemented
 ** using TCL script.
 */
@@ -470,17 +488,17 @@
   int choice;
   static const char *DB_strs[] = {
     "authorizer",         "busy",              "changes",
-    "close",              "complete",          "errorcode",
-    "eval",               "function",          "last_insert_rowid",
-    "onecolumn",          "timeout",            "trace",
-    "progress",           0
+    "close",              "commit_hook",       "complete",
+    "errorcode",          "eval",              "function",
+    "last_insert_rowid",  "onecolumn",         "progress",
+    "timeout",            "trace",             0
   };
   enum DB_enum {
     DB_AUTHORIZER,        DB_BUSY,             DB_CHANGES,
-    DB_CLOSE,             DB_COMPLETE,         DB_ERRORCODE,
-    DB_EVAL,              DB_FUNCTION,         DB_LAST_INSERT_ROWID,
-    DB_ONECOLUMN,         DB_TIMEOUT,          DB_TRACE,            
-    DB_PROGRESS
+    DB_CLOSE,             DB_COMMIT_HOOK,      DB_COMPLETE,
+    DB_ERRORCODE,         DB_EVAL,             DB_FUNCTION,
+    DB_LAST_INSERT_ROWID, DB_ONECOLUMN,        DB_PROGRESS,
+    DB_TIMEOUT,           DB_TRACE,            
   };
 
   if( objc<2 ){
@@ -649,6 +667,43 @@
     break;
   }
 
+  /*    $db commit_hook ?CALLBACK?
+  **
+  ** Invoke the given callback just before committing every SQL transaction.
+  ** If the callback throws an exception or returns non-zero, then the
+  ** transaction is aborted.  If CALLBACK is an empty string, the callback
+  ** is disabled.
+  */
+  case DB_COMMIT_HOOK: {
+    if( objc>3 ){
+      Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+    }else if( objc==2 ){
+      if( pDb->zCommit ){
+        Tcl_AppendResult(interp, pDb->zCommit, 0);
+      }
+    }else{
+      char *zCommit;
+      int len;
+      if( pDb->zCommit ){
+        Tcl_Free(pDb->zCommit);
+      }
+      zCommit = Tcl_GetStringFromObj(objv[2], &len);
+      if( zCommit && len>0 ){
+        pDb->zCommit = Tcl_Alloc( len + 1 );
+        strcpy(pDb->zCommit, zCommit);
+      }else{
+        pDb->zCommit = 0;
+      }
+      if( pDb->zCommit ){
+        pDb->interp = interp;
+        sqlite_commit_hook(pDb->db, DbCommitHandler, pDb);
+      }else{
+        sqlite_commit_hook(pDb->db, 0, 0);
+      }
+    }
+    break;
+  }
+
   /*    $db complete SQL
   **
   ** Return TRUE if SQL is a complete SQL statement.  Return FALSE if
diff --git a/src/vdbe.c b/src/vdbe.c
index 505ff5f..64c8fe0 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -43,7 +43,7 @@
 ** in this file for details.  If in doubt, do not deviate from existing
 ** commenting and indentation practices when changing or adding code.
 **
-** $Id: vdbe.c,v 1.250 2004/01/14 21:59:23 drh Exp $
+** $Id: vdbe.c,v 1.251 2004/01/15 02:44:03 drh Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -2245,6 +2245,13 @@
 */
 case OP_Commit: {
   int i;
+  if( db->xCommitCallback!=0 ){
+    if( sqliteSafetyOff(db) ) goto abort_due_to_misuse; 
+    if( db->xCommitCallback(db->pCommitArg)!=0 ){
+      rc = SQLITE_CONSTRAINT;
+    }
+    if( sqliteSafetyOn(db) ) goto abort_due_to_misuse;
+  }
   for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
     if( db->aDb[i].inTrans ){
       rc = sqliteBtreeCommit(db->aDb[i].pBt);