]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the experimental sqlite3_transaction_hook() API.
authordan <dan@noemail.net>
Thu, 3 Mar 2011 20:05:59 +0000 (20:05 +0000)
committerdan <dan@noemail.net>
Thu, 3 Mar 2011 20:05:59 +0000 (20:05 +0000)
FossilOrigin-Name: 093d8cd8e2f3a6af5d40cf810e396f4919eb5cef

manifest
manifest.uuid
src/main.c
src/sqlite.h.in
src/sqliteInt.h
src/tclsqlite.c
src/vdbe.c
src/vdbeapi.c
src/vdbeaux.c
test/hook.test
test/tclsqlite.test

index 2608f605805280e6c4316407db37498fd24c81f3..132ab8f06894267192736e48f7f40f6588c1b196 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sthe\sexperimental\ssqlite3_preupdate_hook()\sAPI.
-D 2011-03-01T18:42:07
+C Add\sthe\sexperimental\ssqlite3_transaction_hook()\sAPI.
+D 2011-03-03T20:06:00
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -142,7 +142,7 @@ F src/journal.c 552839e54d1bf76fb8f7abe51868b66acacf6a0e
 F src/legacy.c a199d7683d60cef73089e892409113e69c23a99f
 F src/lempar.c 7f026423f4d71d989e719a743f98a1cbd4e6d99e
 F src/loadext.c 8af9fcc75708d60b88636ccba38b4a7b3c155c3e
-F src/main.c 9ab948225b8c362cdd6902c447abbf218607e0f3
+F src/main.c 1a5cafdc1bee11f4322c837487bf6fa8e54b2f50
 F src/malloc.c 92d59a007d7a42857d4e9454aa25b6b703286be1
 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
 F src/mem1.c 00bd8265c81abb665c48fea1e0c234eb3b922206
@@ -177,13 +177,13 @@ F src/resolve.c 1c0f32b64f8e3f555fe1f732f9d6f501a7f05706
 F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
 F src/select.c d24406c45dd2442eb2eeaac413439066b149c944
 F src/shell.c 649c51979812f77f97507024a4cea480c6862b8b
-F src/sqlite.h.in 3a8a9f25a45735879cec195a2c129b48e34ebf8e
+F src/sqlite.h.in 992688609e9a0274b56c156d4800be96364b3590
 F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754
-F src/sqliteInt.h 3a262a299fa5b1f6916157ec2302e8987e50bac9
+F src/sqliteInt.h 85e635183013e806069320ffa6080a77183f9225
 F src/sqliteLimit.h a17dcd3fb775d63b64a43a55c54cb282f9726f44
 F src/status.c 4997380fbb915426fef9e500b4872e79c99267fc
 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
-F src/tclsqlite.c 20a3bf120f3bba1914f0e5132dbe937dee135f36
+F src/tclsqlite.c 64aeb4109ee2b38fd820b8e8de45b02377fcddc0
 F src/test1.c 9020310c7617234b33fd1c3064f89524db25f290
 F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31
 F src/test3.c 056093cfef69ff4227a6bdb9108564dc7f45e4bc
@@ -229,11 +229,11 @@ F src/update.c 1b9a82ede7df15e76ed86c6a3cbe4ce0f21eaa9b
 F src/utf.c 1baeeac91707a4df97ccc6141ec0f808278af685
 F src/util.c ab1c92426494f499f42b9e307537b03e923d75c1
 F src/vacuum.c 924bd1bcee2dfb05376f79845bd3b4cec7b54b2f
-F src/vdbe.c 4330cc94597b9c95853c5e8ff87776f1e6ceaa3f
+F src/vdbe.c 2c523bc17f915329f3db3745fc76ed9bfc5c26bb
 F src/vdbe.h 4de0efb4b0fdaaa900cf419b35c458933ef1c6d2
 F src/vdbeInt.h 41b8e337332f279228196039ab86acd353ad0c6c
-F src/vdbeapi.c c407f3f0e21d5de68b1269bfa12315ef7ed6e3fd
-F src/vdbeaux.c 874e16966eb6e58183ee2746abb4e4c5238f2350
+F src/vdbeapi.c ea741433042d95c392b29397ac6c2b08f6106317
+F src/vdbeaux.c cc817d8597307a2be361c24f3b436a7e41fa67dc
 F src/vdbeblob.c 18955f0ee6b133cd08e1592010cb9a6b11e9984c
 F src/vdbemem.c 0fa2ed786cd207d5b988afef3562a8e663a75b50
 F src/vdbetrace.c 3ba13bc32bdf16d2bdea523245fd16736bed67b5
@@ -471,7 +471,7 @@ F test/fuzz2.test 207d0f9d06db3eaf47a6b7bfc835b8e2fc397167
 F test/fuzz3.test aec64345184d1662bd30e6a17851ff659d596dc5
 F test/fuzz_common.tcl a87dfbb88c2a6b08a38e9a070dabd129e617b45b
 F test/fuzz_malloc.test dd7001ac86d09c154a7dff064f4739c60e2b312c
-F test/hook.test 5fd01c30f47896d88bed8a09bf7773cff218e8ff
+F test/hook.test a90748e44c7ca53da46f857616fc7ce18bf7be13
 F test/icu.test 70df4faca133254c042d02ae342c0a141f2663f4
 F test/in.test 19b642bb134308980a92249750ea4ce3f6c75c2d
 F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75
@@ -667,7 +667,7 @@ F test/superlock.test 5d7a4954b0059c903f82c7b67867bc5451a7c082
 F test/sync.test ded6b39d8d8ca3c0c5518516c6371b3316d3e3a3
 F test/table.test 04ba066432430657712d167ebf28080fe878d305
 F test/tableapi.test 7262a8cbaa9965d429f1cbd2747edc185fa56516
-F test/tclsqlite.test 1ce9b6340d6d412420634e129a2e3722c651056a
+F test/tclsqlite.test 550f2268281d186b767b3ee14dd0be5f9e3ff25d
 F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c
 F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a
 F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05
@@ -909,10 +909,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P 4e50b0362ab6604a4b6c9f4ad849ec1733d6ce1a
-R 0e3c3fa7441b432fef74ccddd445caf6
-T *branch * experimental
-T *sym-experimental *
-T -sym-trunk *
+P 6145d7b89f83500318713779c60f79a7ab2098ba
+R 2617e052d0e8716cc0b428f99022eddb
 U dan
-Z 1b63beda3df1a2343540c0064b366df4
+Z 24e44e929785c6b1d1f35eb2447fe0e7
index c59fca196cc90133d8b8db64e6329f4405398707..e0c1dabe5a497681d553fcbef49a28c8bbabf9f1 100644 (file)
@@ -1 +1 @@
-6145d7b89f83500318713779c60f79a7ab2098ba
\ No newline at end of file
+093d8cd8e2f3a6af5d40cf810e396f4919eb5cef
\ No newline at end of file
index 3976f7ebc30a6c74eb9b1427f662ec2d547bc5f2..a8ac80039e5721f430b143b4b60fe797481569e9 100644 (file)
@@ -764,6 +764,26 @@ int sqlite3_close(sqlite3 *db){
   return SQLITE_OK;
 }
 
+
+/*
+** Invoke the transaction-hook.
+*/
+void sqlite3TransactionHook(sqlite3 *db, int op, int iLevel){
+  assert( op==SQLITE_BEGIN || op==SQLITE_COMMIT || op==SQLITE_ROLLBACK );
+  assert( op==SQLITE_BEGIN || iLevel<db->iOpenTrans || iLevel==0 );
+  assert( op==SQLITE_BEGIN || iLevel<db->iOpenTrans || db->iOpenTrans==0 );
+  assert( op!=SQLITE_BEGIN || iLevel==db->iOpenTrans || iLevel==0 );
+  assert( op!=SQLITE_BEGIN || iLevel==db->iOpenTrans || db->iOpenTrans==1 );
+
+  if( op==SQLITE_BEGIN && iLevel!=db->iOpenTrans ) return;
+  if( op!=SQLITE_BEGIN && iLevel>=db->iOpenTrans ) return;
+
+  if( db->xTransCallback ){
+    db->xTransCallback(db->pTransArg, op, iLevel);
+  }
+  db->iOpenTrans = iLevel + (op==SQLITE_BEGIN);
+}
+
 /*
 ** Rollback all database files.
 */
@@ -796,6 +816,10 @@ void sqlite3RollbackAll(sqlite3 *db){
   if( db->xRollbackCallback && (inTrans || !db->autoCommit) ){
     db->xRollbackCallback(db->pRollbackArg);
   }
+
+  /* If a transaction-hook is configured, invoke it now to report on 
+  ** the rollback operation. */
+  sqlite3TransactionHook(db, SQLITE_ROLLBACK, 0);
 }
 
 /*
@@ -1290,6 +1314,24 @@ void *sqlite3_preupdate_hook(
   return pRet;
 }
 
+/*
+** Register a callback to be invoked each time a transaction or savepoint
+** is opened, committed or rolled back.
+*/
+void *sqlite3_transaction_hook(
+  sqlite3 *db,                              /* Database handle */
+  void(*xCallback)(void *, int, int),       /* Callback function */
+  void *pArg                                /* First callback argument */
+){
+  void *pRet;
+  sqlite3_mutex_enter(db->mutex);
+  pRet = db->pTransArg;
+  db->xTransCallback = xCallback;
+  db->pTransArg = pArg;
+  sqlite3_mutex_leave(db->mutex);
+  return pRet;
+}
+
 #ifndef SQLITE_OMIT_WAL
 /*
 ** The sqlite3_wal_hook() callback registered by sqlite3_wal_autocheckpoint().
index 413031ecfaee351989756ba3945ffb0370fd7eca..137be57b89bab9e768dd948c51c849a274886717 100644 (file)
@@ -6344,7 +6344,15 @@ int sqlite3_wal_checkpoint_v2(
 #define SQLITE_CHECKPOINT_FULL    1
 #define SQLITE_CHECKPOINT_RESTART 2
 
-void *sqlite3_preupdate_hook(
+
+/*
+** CAPI3REF: The pre-update hook.
+** 
+** The preupdate_old() API may only be used from within an SQLITE_UPDATE or
+** SQLITE_DELETE pre-update callback. The preupdate_modified() API may only 
+** be used from within an SQLITE_UPDATE pre-update callback.
+*/
+SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook(
   sqlite3 *db,
   void(*xPreUpdate)(
     void *pCtx,                   /* Copy of third arg to preupdate_hook() */
@@ -6357,16 +6365,19 @@ void *sqlite3_preupdate_hook(
   ),
   void*
 );
+SQLITE_EXPERIMENTAL int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
+SQLITE_EXPERIMENTAL int sqlite3_preupdate_modified(sqlite3 *, int, int *);
+SQLITE_EXPERIMENTAL int sqlite3_preupdate_count(sqlite3 *);
 
 /*
-** The following APIs may only be used from within a pre-update callback. More
-** specifically, the preupdate_old() API may only be used from within an
-** SQLITE_UPDATE or SQLITE_DELETE pre-update callback. The preupdate_modified()
-** API may only be used from within an SQLITE_UPDATE pre-update callback.
+** CAPI3REF: The transaction hook.
 */
-int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
-int sqlite3_preupdate_modified(sqlite3 *, int, int *);
-int sqlite3_preupdate_count(sqlite3 *);
+SQLITE_EXPERIMENTAL
+void *sqlite3_transaction_hook(sqlite3 *, void(*)(void *, int, int), void *);
+
+#define SQLITE_BEGIN    1
+#define SQLITE_COMMIT   2
+#define SQLITE_ROLLBACK 3
 
 
 /*
index 25419368489c2a8fecb38b185a5820839b4372a9..1414fb68b55115e7c7fedf3315361439a486795a 100644 (file)
@@ -833,6 +833,9 @@ struct sqlite3 {
     void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64
   );
   PreUpdate *pPreUpdate;        /* Context for active pre-update callback */
+  void *pTransArg;              /* First argument to xTransCallback */
+  void (*xTransCallback)(void*,int,int);
+  int iOpenTrans;               /* Open transaction (xTransCallback) plus 1 */
 #ifndef SQLITE_OMIT_WAL
   int (*xWalCallback)(void *, sqlite3 *, const char *, int);
   void *pWalArg;
@@ -2748,6 +2751,7 @@ void sqlite3PrngSaveState(void);
 void sqlite3PrngRestoreState(void);
 void sqlite3PrngResetState(void);
 void sqlite3RollbackAll(sqlite3*);
+void sqlite3TransactionHook(sqlite3 *, int, int);
 void sqlite3CodeVerifySchema(Parse*, int);
 void sqlite3BeginTransaction(Parse*, int);
 void sqlite3CommitTransaction(Parse*);
index 91c6094fa91fe2614ca71ad94aa78739381ad9c5..c7e6fda0687d6b76e363061c655c2e25af63c5dc 100644 (file)
@@ -124,6 +124,7 @@ struct SqliteDb {
   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 *pTransHook;       /* Transaction hook script (if any) */
   Tcl_Obj *pWalHook;         /* WAL hook script (if any) */
   Tcl_Obj *pUnlockNotify;    /* Unlock notify script (if any) */
   SqlCollate *pCollate;      /* List of SQL collation functions */
@@ -597,6 +598,30 @@ static void DbRollbackHandler(void *clientData){
   }
 }
 
+/*
+** sqlite3_transaction_hook() callback.
+*/
+static void DbTransHandler(void *clientData, int op, int iLevel){
+  static const char *azStr[] = { "BEGIN", "COMMIT", "ROLLBACK" };
+  SqliteDb *pDb = (SqliteDb*)clientData;
+  Tcl_Interp *interp = pDb->interp;
+  Tcl_Obj *pScript;
+
+  assert(pDb->pTransHook);
+  assert( SQLITE_BEGIN==1 );
+  assert( SQLITE_COMMIT==2 );
+  assert( SQLITE_ROLLBACK==3 );
+
+  pScript = Tcl_DuplicateObj(pDb->pTransHook);
+  Tcl_IncrRefCount(pScript);
+  Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(azStr[op-1], -1));
+  Tcl_ListObjAppendElement(interp, pScript, Tcl_NewIntObj(iLevel));
+  if( TCL_OK!=Tcl_EvalObjEx(interp, pScript, 0) ){
+    Tcl_BackgroundError(interp);
+  }
+  Tcl_DecrRefCount(pScript);
+}
+
 /*
 ** This procedure handles wal_hook callbacks.
 */
@@ -1628,6 +1653,7 @@ static void DbHookCmd(
   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);
+  sqlite3_transaction_hook(db, (pDb->pTransHook?DbTransHandler:0), pDb);
 }
 
 /*
@@ -1659,7 +1685,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
     "profile",            "progress",          "rekey",
     "restore",            "rollback_hook",     "status",
     "timeout",            "total_changes",     "trace",
-    "transaction",        "unlock_notify",     "update_hook",
+    "transaction",        "transaction_hook",
+    "unlock_notify",     "update_hook",
     "version",            "wal_hook",          0
   };
   enum DB_enum {
@@ -1674,7 +1701,8 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
     DB_PROFILE,           DB_PROGRESS,         DB_REKEY,
     DB_RESTORE,           DB_ROLLBACK_HOOK,    DB_STATUS,
     DB_TIMEOUT,           DB_TOTAL_CHANGES,    DB_TRACE,
-    DB_TRANSACTION,       DB_UNLOCK_NOTIFY,    DB_UPDATE_HOOK,
+    DB_TRANSACTION,       DB_TRANSACTION_HOOK,
+    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 */
@@ -2914,10 +2942,12 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
   **    $db wal_hook ?script?
   **    $db update_hook ?script?
   **    $db rollback_hook ?script?
+  **    $db transaction_hook ?script?
   */
   case DB_WAL_HOOK: 
   case DB_UPDATE_HOOK: 
-  case DB_ROLLBACK_HOOK: {
+  case DB_ROLLBACK_HOOK:
+  case DB_TRANSACTION_HOOK: {
     sqlite3 *db = pDb->db;
 
     /* set ppHook to point at pUpdateHook or pRollbackHook, depending on 
@@ -2927,6 +2957,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
     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( choice==DB_TRANSACTION_HOOK ) ppHook = &pDb->pTransHook;
     if( objc>3 ){
        Tcl_WrongNumArgs(interp, 2, objv, "?SCRIPT?");
        return TCL_ERROR;
index c5492eedc8857a032e9606744eea4aa3f324b34e..252c53cb6348182aad587852341d436f7ea19b12 100644 (file)
@@ -2568,6 +2568,8 @@ case OP_Savepoint: {
         }else{
           db->nSavepoint++;
         }
+
+        sqlite3TransactionHook(db, SQLITE_BEGIN, db->nSavepoint);
     
         /* Link the new savepoint into the database handle's list. */
         pNew->pNext = db->pSavepoint;
@@ -2635,6 +2637,13 @@ case OP_Savepoint: {
           sqlite3ResetInternalSchema(db, 0);
           db->flags = (db->flags | SQLITE_InternChanges);
         }
+
+        assert( SAVEPOINT_ROLLBACK+1==SQLITE_ROLLBACK );
+        assert( SAVEPOINT_RELEASE+1==SQLITE_COMMIT );
+        sqlite3TransactionHook(db, p1+1, iSavepoint+1);
+        if( p1==SAVEPOINT_ROLLBACK ){
+          sqlite3TransactionHook(db, SQLITE_BEGIN, iSavepoint+1);
+        }
       }
   
       /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all 
@@ -2710,6 +2719,9 @@ case OP_AutoCommit: {
     }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){
       goto vdbe_return;
     }else{
+      if( desiredAutoCommit==0 ){
+        sqlite3TransactionHook(db, SQLITE_BEGIN, 0);
+      }
       db->autoCommit = (u8)desiredAutoCommit;
       if( sqlite3VdbeHalt(p)==SQLITE_BUSY ){
         p->pc = pc;
@@ -2796,6 +2808,7 @@ case OP_Transaction: {
         p->iStatement = db->nSavepoint + db->nStatement;
       }
       rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);
+      sqlite3TransactionHook(db, SQLITE_BEGIN, p->iStatement);
 
       /* Store the current value of the database handles deferred constraint
       ** counter. If the statement transaction needs to be rolled back,
index 8b1f2e21bbcc1429481817720ef6111023c014b0..cefca02a24783348a2421af0c47bc5f30d6ae519 100644 (file)
@@ -401,7 +401,15 @@ static int sqlite3Step(Vdbe *p){
 #endif
 
     db->activeVdbeCnt++;
-    if( p->readOnly==0 ) db->writeVdbeCnt++;
+    if( p->readOnly==0 ){ 
+      /* If this statement will open an implicit transaction, invoke the
+      ** transaction-hook here. */
+      if( db->autoCommit && db->writeVdbeCnt==0 ){
+        assert( db->nSavepoint==0 );
+        sqlite3TransactionHook(db, SQLITE_BEGIN, 0);
+      }
+      db->writeVdbeCnt++;
+    }
     p->pc = 0;
   }
 #ifndef SQLITE_OMIT_EXPLAIN
index 31cf669144d15fd82c4742a5ac7011252895941e..937c2a1998703f73b8d9fa50beba91e947d10681 100644 (file)
@@ -1845,6 +1845,12 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
   }
 #endif
 
+  /* If a transaction-hook is configured, invoke it now to report on the
+  ** successful commit operation. */
+  if( rc==SQLITE_OK ){
+    sqlite3TransactionHook(db, SQLITE_COMMIT, 0);
+  }
+
   return rc;
 }
 
@@ -1946,6 +1952,9 @@ int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){
       }
     }
     db->nStatement--;
+    assert( SAVEPOINT_ROLLBACK+1==SQLITE_ROLLBACK );
+    assert( SAVEPOINT_RELEASE+1==SQLITE_COMMIT );
+    sqlite3TransactionHook(db, eOp+1, p->iStatement);
     p->iStatement = 0;
 
     /* If the statement transaction is being rolled back, also restore the 
index 827ce08b3d0d9c8d4228b26ab899d34b84dbd868..b2e0988828a238b2f32c681a520f7d93911b7b97 100644 (file)
@@ -616,16 +616,151 @@ do_execsql_test 7.5.2.0 {
   ALTER TABLE t8 ADD COLUMN c DEFAULT 'xxx';
 }
 
+# At time of writing, these two are broken. They demonstraight that the
+# sqlite3_preupdate_old() method does not handle the case where ALTER TABLE
+# has been used to add a column with a default value other than NULL.
+#
 do_preupdate_test 7.5.2.1 {
   DELETE FROM t8 WHERE a = 'one'
 } {
   DELETE main t8 1 1   one two xxx
 }
-
 do_preupdate_test 7.5.2.2 {
   UPDATE t8 SET b = 'five'
 } {
   UPDATE main t8 2 2   three four xxx
 }
 
+#----------------------------------------------------------------------------
+# The following tests - hook-8.* - test the transaction hook.
+#
+db close
+forcedelete test.db
+sqlite3 db test.db
+
+proc transaction_hook {op iLevel} {
+  lappend ::transaction_hook $op $iLevel
+}
+db transaction_hook transaction_hook
+
+proc do_transaction_test {tn sql x} {
+  set X [list]
+  foreach elem $x {lappend X $elem}
+
+  uplevel do_test $tn [list "
+    set ::transaction_hook \[list\]
+    catchsql { $sql }
+    set ::transaction_hook
+  "] [list $X]
+}
+
+do_transaction_test 8.1.1 "CREATE TABLE t1(x)" {BEGIN 0 COMMIT 0}
+do_transaction_test 8.1.2 "BEGIN"              {BEGIN 0}
+do_transaction_test 8.1.3 "COMMIT"             {COMMIT 0}
+do_transaction_test 8.1.4 "BEGIN ; ROLLBACK"   {BEGIN 0 ROLLBACK 0}
+
+do_execsql_test 8.2.0 {
+  CREATE TABLE t2(a PRIMARY KEY, b);
+  INSERT INTO t2 VALUES(1, 'one');
+  INSERT INTO t2 VALUES(2, 'two');
+  INSERT INTO t2 VALUES(3, 'three');
+}
+do_transaction_test 8.2.1 {
+  INSERT INTO t2 VALUES(2, 'xxx')
+} {BEGIN 0 ROLLBACK 0}
+
+do_transaction_test 8.2.2 {
+  BEGIN; INSERT INTO t2 SELECT a-2, b FROM t2;
+} {BEGIN 0 BEGIN 1 ROLLBACK 1}
+
+do_transaction_test 8.2.3 {
+  INSERT OR ROLLBACK INTO t2 SELECT a-2, b FROM t2;
+} {ROLLBACK 0}
+
+do_transaction_test 8.2.4 {
+  BEGIN; INSERT INTO t2 SELECT a+3, b FROM t2;
+} {BEGIN 0 BEGIN 1 COMMIT 1}
+
+do_transaction_test 8.2.5 "COMMIT" {COMMIT 0}
+
+do_transaction_test 8.3.1 {SELECT * FROM t2} {}
+
+do_transaction_test 8.4.1 {
+  SAVEPOINT top;
+  RELEASE top;
+} {BEGIN 0 COMMIT 0}
+
+do_transaction_test 8.4.2 {
+  SAVEPOINT top;
+  ROLLBACK TO top;
+  RELEASE top;
+} {BEGIN 0 ROLLBACK 0 BEGIN 0 COMMIT 0}
+
+do_transaction_test 8.4.3 {
+  SAVEPOINT zero;
+    SAVEPOINT one;
+      SAVEPOINT two;
+        SAVEPOINT three;
+  ROLLBACK TO zero;
+    SAVEPOINT one;
+      SAVEPOINT two;
+        SAVEPOINT three;
+    ROLLBACK TO one;
+      SAVEPOINT two;
+  RELEASE zero;
+
+  SAVEPOINT zero;
+    SAVEPOINT one;
+      SAVEPOINT two;
+      RELEASE two;
+      SAVEPOINT two;
+        SAVEPOINT three;
+    RELEASE one;
+  ROLLBACK TO zero;
+  RELEASE zero;
+} {
+  BEGIN 0 BEGIN 1 BEGIN 2 BEGIN 3 ROLLBACK 0 BEGIN 0 
+  BEGIN 1 BEGIN 2 BEGIN 3 ROLLBACK 1 BEGIN 1 BEGIN 2 
+  COMMIT 0
+
+  BEGIN 0 BEGIN 1 BEGIN 2 COMMIT 2 BEGIN 2 BEGIN 3 COMMIT 1 
+  ROLLBACK 0 BEGIN 0 COMMIT 0
+}
+
+do_transaction_test 8.4.4 {
+  BEGIN;
+    SAVEPOINT zero;
+      SAVEPOINT one;
+        SAVEPOINT two;
+          SAVEPOINT three;
+    ROLLBACK TO zero;
+      SAVEPOINT one;
+        SAVEPOINT two;
+          SAVEPOINT three;
+      ROLLBACK TO one;
+        SAVEPOINT two;
+    RELEASE zero;
+  
+    SAVEPOINT zero;
+      SAVEPOINT one;
+        SAVEPOINT two;
+        RELEASE two;
+        SAVEPOINT two;
+          SAVEPOINT three;
+      RELEASE one;
+    ROLLBACK TO zero;
+    RELEASE zero;
+  COMMIT;
+} {
+  BEGIN 0
+  BEGIN 1 BEGIN 2 BEGIN 3 BEGIN 4 ROLLBACK 1 BEGIN 1 
+  BEGIN 2 BEGIN 3 BEGIN 4 ROLLBACK 2 BEGIN 2 BEGIN 3 
+  COMMIT 1
+
+  BEGIN 1 BEGIN 2 BEGIN 3 COMMIT 3 BEGIN 3 BEGIN 4 COMMIT 2 
+  ROLLBACK 1 BEGIN 1 COMMIT 1
+  COMMIT 0
+}
+
 finish_test
+
index c9d562beb566ba6247c38fe87bec0450a2ad4bca..efc3e53ab66db7d5cfd48f9bb232f358de478543 100644 (file)
@@ -35,7 +35,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, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, unlock_notify, update_hook, version, or wal_hook}}
+} {1 {bad option "bogus": must be authorizer, backup, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, status, timeout, total_changes, trace, transaction, transaction_hook, unlock_notify, update_hook, version, or wal_hook}}
 do_test tcl-1.2.1 {
   set v [catch {db cache bogus} msg]
   lappend v $msg