diff --git a/manifest b/manifest index 8acd41063b..d6a201bf78 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\scomparisons\sof\sROWID\sagainst\sfloating\spoint\snumbers\sso\sthat\sthey\swork\ncorrectly.\s\sTicket\s#377\sand\s#567.\s(CVS\s1178) -D 2004-01-14T21:59:23 +C Reinsert\sthe\sexperimental\ssqlite_commit_hook()\sAPI.\s(CVS\s1179) +D 2004-01-15T02:44:03 F Makefile.in 0515ff9218ad8d5a8f6220f0494b8ef94c67013b F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd @@ -36,7 +36,7 @@ F src/func.c 62cf8fae8147c0301d1c6a4a94fe0a78f7aa5b33 F src/hash.c 9b56ef3b291e25168f630d5643a4264ec011c70e F src/hash.h 3247573ab95b9dd90bcca0307a75d9a16da1ccc7 F src/insert.c 01f66866f35c986eab4a57373ca689a3255ef2df -F src/main.c 3dd3cae00bade294011da5a3cf9ff660a610c545 +F src/main.c 67af644fa7c1b8bb06032b6a9459d084cf79bb81 F src/md5.c fe4f9c9c6f71dfc26af8da63e4d04489b1430565 F src/os.c 681ec36217bc7c795d55d9a63ff79a8614ddee8c F src/os.h 257c9aef1567bb20c8b767fc27fe3ee7d89104e0 @@ -48,10 +48,10 @@ F src/printf.c 292a7bfc5a815cb6465e32b2d5c9fe9bd43b27f0 F src/random.c 19e8e00fe0df32a742f115773f57651be327cabe F src/select.c 2712bd4d311ebe7dbf8fd7596a809042657db85f F src/shell.c 3b067edc098c45caca164bcad1fa79192c3ec5ae -F src/sqlite.h.in e6cfff01fafc8a82ce82cd8c932af421dc9adb54 -F src/sqliteInt.h d9f2391451ae9636eb447dfa4dc35b70bfa3759d +F src/sqlite.h.in c70d8533cd5a5ae8af580597dbc726693ef82de9 +F src/sqliteInt.h 99df38d3c16cc727030c218a3fc61de08c071d08 F src/table.c d845cb101b5afc1f7fea083c99e3d2fa7998d895 -F src/tclsqlite.c dcd18d1f0d51ac4863d1f9059f614f903bc1fffe +F src/tclsqlite.c 85810fc4a850e2178d71aa5efe576dcd331db596 F src/test1.c e8652055d04d241d4fb437b5c33ff07d9f13b4b4 F src/test2.c 5014337d8576b731cce5b5a14bec4f0daf432700 F src/test3.c 30985ebdfaf3ee1462a9b0652d3efbdc8d9798f5 @@ -61,7 +61,7 @@ F src/trigger.c ce83e017b407d046e909d05373d7f8ee70f9f7f9 F src/update.c 24260b4fda00c9726d27699a0561d53c0dccc397 F src/util.c 64995b5949a5d377629ffd2598747bc771cade1e F src/vacuum.c 77485a64a6e4e358170f150fff681c1624a092b0 -F src/vdbe.c 763ff006f6e5a7c4dd5ba64d4967df5d49d08064 +F src/vdbe.c 802364c5b1b989ec4592aaffcbf575fa3eb0478b F src/vdbe.h 3957844e46fea71fd030e78f6a3bd2f7e320fb43 F src/vdbeInt.h eab39bc209b267271bc4afbcf4991d6c229bae9a F src/vdbeaux.c 6f2d43643f83656b2555b7ee320397805db11d4c @@ -88,6 +88,7 @@ F test/expr.test c4cc292d601019c2f2ce95093caaa5d10284b105 F test/fkey1.test d65c824459916249bee501532d6154ddab0b5db7 F test/format3.test 149cc166c97923fa60def047e90dd3fb32bba916 F test/func.test 000515779001ac6899eec4b54e65c6e2501279d4 +F test/hook.test 1a67ce0cd64a6455d016962542f2822458dccc49 F test/in.test 22de8a3eb27265aab723adc513bea0e76bef70c6 F test/index.test 9295deefbdb6dedbe01be8905f0c448fe5bd4079 F test/insert.test a17b7f7017097afb2727aa5b67ceeb7ab0a120a1 @@ -125,7 +126,7 @@ F test/sort.test ba07b107c16070208e6aab3cadea66ba079d85ba F test/subselect.test f0fea8cf9f386d416d64d152e3c65f9116d0f50f F test/table.test 371a1fc1c470982b2f68f9732f903a5d96f949c4 F test/tableapi.test d881e787779a175238b72f55b5e50d3a85ab47a6 -F test/tclsqlite.test f141303e0f2e9a616b551813e2b21bd38c5dca50 +F test/tclsqlite.test 6921477a25aa630fd8d89b1ad231f80ef0dea88e F test/temptable.test c82bd6f800f10e8cf96921af6315e5f1c21e2692 F test/tester.tcl 2671536d3650c29e7c105219f277568b0884cb58 F test/thread1.test 0c1fcc2f9bdd887225e56f48db8ddfbb3d0794ba @@ -179,7 +180,7 @@ F www/speed.tcl 2f6b1155b99d39adb185f900456d1d592c4832b3 F www/sqlite.tcl 3c83b08cf9f18aa2d69453ff441a36c40e431604 F www/tclsqlite.tcl b9271d44dcf147a93c98f8ecf28c927307abd6da F www/vdbe.tcl 9b9095d4495f37697fd1935d10e14c6015e80aa1 -P a9f25347de6d2bf843a8aebf7935e9c8a1f2319c -R 8641e6b22bbcab376fe70750a642a5f8 +P c9ac3db8e08403398ec344757385334601a59374 +R 18a9d4e8ef4c6ab1b9d09a62e3e37863 U drh -Z 7e6cc2ec80fafcf5309e5e8766ecebcd +Z 26c0ac18c73114906997f5ad3548f7ae diff --git a/manifest.uuid b/manifest.uuid index cb72c2d3fc..da91e07dd3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c9ac3db8e08403398ec344757385334601a59374 \ No newline at end of file +72bc84f2f18f6eeb279a4ad670310e85d154f663 \ No newline at end of file diff --git a/src/main.c b/src/main.c index 6386495e29..2b4a396ca7 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 @@ void *sqlite_trace(sqlite *db, void (*xTrace)(void*,const char*), void *pArg){ 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 6f8712145e..e493a13fec 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 @@ int sqlite_bind(sqlite_vm*, int idx, const char *value, int len, int copy); ** 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 c90190af21..9894786077 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 @@ struct sqlite { 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 c2f4cfd49d..70706306ad 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 @@ struct SqliteDb { 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 */ @@ -357,6 +358,23 @@ static void DbTraceHandler(void *cd, const char *zSql){ Tcl_ResetResult(pDb->interp); } +/* +** 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 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ 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 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){ 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 505ff5fe31..64c8fe0fbe 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_Transaction: { */ 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 && inDb; i++){ if( db->aDb[i].inTrans ){ rc = sqliteBtreeCommit(db->aDb[i].pBt); diff --git a/test/hook.test b/test/hook.test new file mode 100644 index 0000000000..d19dfaf8c2 --- /dev/null +++ b/test/hook.test @@ -0,0 +1,83 @@ +# 2004 Jan 14 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file implements regression tests for TCL interface to the +# SQLite library. +# +# The focus of the tests in this file is the following interface: +# +# sqlite_commit_hook +# +# $Id: hook.test,v 1.3 2004/01/15 02:44:03 drh Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_test hook-1.2 { + db commit_hook +} {} + + +do_test hook-3.1 { + set commit_cnt 0 + proc commit_hook {} { + incr ::commit_cnt + return 0 + } + db commit_hook ::commit_hook + db commit_hook +} {::commit_hook} +do_test hook-3.2 { + set commit_cnt +} {0} +do_test hook-3.3 { + execsql { + CREATE TABLE t2(a,b); + } + set commit_cnt +} {1} +do_test hook-3.4 { + execsql { + INSERT INTO t2 VALUES(1,2); + INSERT INTO t2 SELECT a+1, b+1 FROM t2; + INSERT INTO t2 SELECT a+2, b+2 FROM t2; + } + set commit_cnt +} {4} +do_test hook-3.5 { + set commit_cnt {} + proc commit_hook {} { + set ::commit_cnt [execsql {SELECT * FROM t2}] + return 0 + } + execsql { + INSERT INTO t2 VALUES(5,6); + } + set commit_cnt +} {1 2 2 3 3 4 4 5 5 6} +do_test hook-3.6 { + set commit_cnt {} + proc commit_hook {} { + set ::commit_cnt [execsql {SELECT * FROM t2}] + return 1 + } + catchsql { + INSERT INTO t2 VALUES(6,7); + } +} {1 {constraint failed}} +do_test hook-3.7 { + set commit_cnt +} {1 2 2 3 3 4 4 5 5 6 6 7} +do_test hook-3.8 { + execsql {SELECT * FROM t2} +} {1 2 2 3 3 4 4 5 5 6} + + +finish_test diff --git a/test/tclsqlite.test b/test/tclsqlite.test index 6d33259411..5b358674dd 100644 --- a/test/tclsqlite.test +++ b/test/tclsqlite.test @@ -15,7 +15,7 @@ # interface is pretty well tested. This file contains some addition # tests for fringe issues that the main test suite does not cover. # -# $Id: tclsqlite.test,v 1.16 2003/10/23 15:27:12 peter Exp $ +# $Id: tclsqlite.test,v 1.17 2004/01/15 02:44:04 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -29,7 +29,7 @@ do_test tcl-1.1 { do_test tcl-1.2 { set v [catch {db bogus} msg] lappend v $msg -} {1 {bad option "bogus": must be authorizer, busy, changes, close, complete, errorcode, eval, function, last_insert_rowid, onecolumn, timeout, trace, or progress}} +} {1 {bad option "bogus": must be authorizer, busy, changes, close, commit_hook, complete, errorcode, eval, function, last_insert_rowid, onecolumn, progress, timeout, or trace}} do_test tcl-1.3 { execsql {CREATE TABLE t1(a int, b int)} execsql {INSERT INTO t1 VALUES(10,20)}