]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the experimental sqlite3_preupdate_hook() API.
authordan <dan@noemail.net>
Tue, 1 Mar 2011 18:42:07 +0000 (18:42 +0000)
committerdan <dan@noemail.net>
Tue, 1 Mar 2011 18:42:07 +0000 (18:42 +0000)
FossilOrigin-Name: 6145d7b89f83500318713779c60f79a7ab2098ba

18 files changed:
install-sh [changed mode: 0755->0644]
manifest
manifest.uuid
src/delete.c
src/insert.c
src/main.c
src/sqlite.h.in
src/sqliteInt.h
src/tclsqlite.c
src/update.c
src/vdbe.c
src/vdbeInt.h
src/vdbeapi.c
src/vdbeaux.c
test/hook.test
test/progress.test [changed mode: 0644->0755]
test/tclsqlite.test
tool/mkopts.tcl [changed mode: 0644->0755]

old mode 100755 (executable)
new mode 100644 (file)
index 1d2716b2dbcc1c828da1af4b7f549b6db2d98fe9..2608f605805280e6c4316407db37498fd24c81f3 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,8 +1,5 @@
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA1
-
-C Comment\sout\ssome\scode\sin\sos_unix.c\sthat\sonly\sruns\son\sMacOSX\swith\nSQLITE_ENABLE_LOCKING_STYLE.
-D 2011-02-25T03:25:07.454
+C Add\sthe\sexperimental\ssqlite3_preupdate_hook()\sAPI.
+D 2011-03-01T18:42:07
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -101,7 +98,7 @@ F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
 F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0
 F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
 F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
-F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
+F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F main.mk 54190fab7cdba523e311c274c95ea480f32abfb5
 F mkdll.sh 7d09b23c05d56532e9d44a50868eb4b12ff4f74a
@@ -131,7 +128,7 @@ F src/callback.c a1d1b1c9c85415dff013af033e2fed9c8382d33b
 F src/complete.c dc1d136c0feee03c2f7550bafc0d29075e36deac
 F src/ctime.c 7deec4534f3b5a0c3b4a4cbadf809d321f64f9c4
 F src/date.c 1548fdac51377e4e7833251de878b4058c148e1b
-F src/delete.c 7ed8a8c8b5f748ece92df173d7e0f7810c899ebd
+F src/delete.c 3c0925e958a77804a004222baa1a2a8ad855306b
 F src/expr.c 8e2c607b3be87a35c75a1f5dac50c10666b083c0
 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
 F src/fkey.c 17950a28f28b23e8ad3feaac5fc88c324d2f600a
@@ -140,12 +137,12 @@ F src/global.c 02335177cf6946fe5525c6f0755cf181140debf3
 F src/hash.c 458488dcc159c301b8e7686280ab209f1fb915af
 F src/hash.h 2894c932d84d9f892d4b4023a75e501f83050970
 F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
-F src/insert.c a4995747c062256582a90b4f87f716e11b067050
+F src/insert.c c56a64b1488921794c3855d54acfe4674c7cea0c
 F src/journal.c 552839e54d1bf76fb8f7abe51868b66acacf6a0e
 F src/legacy.c a199d7683d60cef73089e892409113e69c23a99f
 F src/lempar.c 7f026423f4d71d989e719a743f98a1cbd4e6d99e
 F src/loadext.c 8af9fcc75708d60b88636ccba38b4a7b3c155c3e
-F src/main.c 93d0d967d6898fc0408ece248342342e312aa753
+F src/main.c 9ab948225b8c362cdd6902c447abbf218607e0f3
 F src/malloc.c 92d59a007d7a42857d4e9454aa25b6b703286be1
 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
 F src/mem1.c 00bd8265c81abb665c48fea1e0c234eb3b922206
@@ -180,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 ccb23cc9378874c7c72682b739f311474a80848d
+F src/sqlite.h.in 3a8a9f25a45735879cec195a2c129b48e34ebf8e
 F src/sqlite3ext.h c90bd5507099f62043832d73f6425d8d5c5da754
-F src/sqliteInt.h 4290fff17fabc6e07fc4338233df0e39e6350ca1
+F src/sqliteInt.h 3a262a299fa5b1f6916157ec2302e8987e50bac9
 F src/sqliteLimit.h a17dcd3fb775d63b64a43a55c54cb282f9726f44
 F src/status.c 4997380fbb915426fef9e500b4872e79c99267fc
 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
-F src/tclsqlite.c 879bf8a23d99fc0e99d9177fe1b48896bc796d65
+F src/tclsqlite.c 20a3bf120f3bba1914f0e5132dbe937dee135f36
 F src/test1.c 9020310c7617234b33fd1c3064f89524db25f290
 F src/test2.c 80d323d11e909cf0eb1b6fbb4ac22276483bcf31
 F src/test3.c 056093cfef69ff4227a6bdb9108564dc7f45e4bc
@@ -228,15 +225,15 @@ F src/test_vfs.c 2ed8853c1e51ac6f9ea091f7ce4e0d618bba8b86
 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
 F src/tokenize.c 604607d6813e9551cf5189d899e0a25c12681080
 F src/trigger.c b8bedb9c0084ceb51a40f54fcca2ce048c8de852
-F src/update.c 227e6cd512108b84f69421fc6c7aa1b83d60d6e0
+F src/update.c 1b9a82ede7df15e76ed86c6a3cbe4ce0f21eaa9b
 F src/utf.c 1baeeac91707a4df97ccc6141ec0f808278af685
 F src/util.c ab1c92426494f499f42b9e307537b03e923d75c1
 F src/vacuum.c 924bd1bcee2dfb05376f79845bd3b4cec7b54b2f
-F src/vdbe.c 34305497d81daafdb1e500bfaa21d044c64503de
+F src/vdbe.c 4330cc94597b9c95853c5e8ff87776f1e6ceaa3f
 F src/vdbe.h 4de0efb4b0fdaaa900cf419b35c458933ef1c6d2
-F src/vdbeInt.h 6e6f28e9bccc6c703dca1372fd661c57b5c15fb0
-F src/vdbeapi.c 8e9324fd35eb70d0b5904bd1af40f2598744dc4d
-F src/vdbeaux.c 5936a596324ad9f9aba02bdee8c8080d2a3264e1
+F src/vdbeInt.h 41b8e337332f279228196039ab86acd353ad0c6c
+F src/vdbeapi.c c407f3f0e21d5de68b1269bfa12315ef7ed6e3fd
+F src/vdbeaux.c 874e16966eb6e58183ee2746abb4e4c5238f2350
 F src/vdbeblob.c 18955f0ee6b133cd08e1592010cb9a6b11e9984c
 F src/vdbemem.c 0fa2ed786cd207d5b988afef3562a8e663a75b50
 F src/vdbetrace.c 3ba13bc32bdf16d2bdea523245fd16736bed67b5
@@ -474,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 f04c3412463f8ec117c1c704c74ca0f627ce733a
+F test/hook.test 5fd01c30f47896d88bed8a09bf7773cff218e8ff
 F test/icu.test 70df4faca133254c042d02ae342c0a141f2663f4
 F test/in.test 19b642bb134308980a92249750ea4ce3f6c75c2d
 F test/in2.test 5d4c61d17493c832f7d2d32bef785119e87bde75
@@ -600,7 +597,7 @@ F test/permutations.test 5b2a4cb756ffb2407cb4743163668d1d769febb6
 F test/pragma.test fdfc09067ea104a0c247a1a79d8093b56656f850
 F test/pragma2.test 5364893491b9231dd170e3459bfc2e2342658b47
 F test/printf.test 05970cde31b1a9f54bd75af60597be75a5c54fea
-F test/progress.test 5b075c3c790c7b2a61419bc199db87aaf48b8301
+F test/progress.test 5b075c3c790c7b2a61419bc199db87aaf48b8301 x
 F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
 F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
 F test/quota.test ddafe133653093eb9a99ccd6264884ae43f9c9b8
@@ -670,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 8c154101e704170c2be10f137a5499ac2c6da8d3
+F test/tclsqlite.test 1ce9b6340d6d412420634e129a2e3722c651056a
 F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c
 F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a
 F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05
@@ -887,7 +884,7 @@ F tool/genfkey.test 4196a8928b78f51d54ef58e99e99401ab2f0a7e5
 F tool/lemon.c dfd81a51b6e27e469ba21d01a75ddf092d429027
 F tool/lempar.c 01ca97f87610d1dac6d8cd96ab109ab1130e76dc
 F tool/mkkeywordhash.c d2e6b4a5965e23afb80fbe74bb54648cd371f309
-F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
+F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e x
 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
 F tool/mksqlite3c.tcl cf44512a48112b1ba09590548660a5a6877afdb3
 F tool/mksqlite3h.tcl d76c226a5e8e1f3b5f6593bcabe5e98b3b1ec9ff
@@ -912,14 +909,10 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P af4756184a255f5d8a5cd276bf9f2fc3b38d9169
-R 86eab7753acb1a027109135cae01654a
-U drh
-Z 475e4dbe6e8fc13f5f8796d1c18d588c
------BEGIN PGP SIGNATURE-----
-Version: GnuPG v1.4.6 (GNU/Linux)
-
-iD8DBQFNZyEhoxKgR168RlERAohiAKCDC55xIULyuJJBl/uNZNCMqVF+ZgCeK05+
-18x49YRlHX+qQrvL/cMA0OY=
-=QUmN
------END PGP SIGNATURE-----
+P 4e50b0362ab6604a4b6c9f4ad849ec1733d6ce1a
+R 0e3c3fa7441b432fef74ccddd445caf6
+T *branch * experimental
+T *sym-experimental *
+T -sym-trunk *
+U dan
+Z 1b63beda3df1a2343540c0064b366df4
index a2c2d650a211e3c405a72de5585f6e6fb0359219..c59fca196cc90133d8b8db64e6329f4405398707 100644 (file)
@@ -1 +1 @@
-4e50b0362ab6604a4b6c9f4ad849ec1733d6ce1a
\ No newline at end of file
+6145d7b89f83500318713779c60f79a7ab2098ba
\ No newline at end of file
index bd7ac3d1f38c9b2fffe9d178f0a65921a2c34597..c7a686a8bd48c52a41c9822e720f71d693ac78ed 100644 (file)
@@ -531,13 +531,18 @@ void sqlite3GenerateRowDelete(
 
   /* Delete the index and table entries. Skip this step if pTab is really
   ** a view (in which case the only effect of the DELETE statement is to
-  ** fire the INSTEAD OF triggers).  */ 
+  ** fire the INSTEAD OF triggers).  
+  **
+  ** If variable 'count' is non-zero, then this OP_Delete instruction should
+  ** invoke the update-hook. The pre-update-hook, on the other hand should
+  ** be invoked unless table pTab is a system table. The difference is that
+  ** the update-hook is not invoked for rows removed by REPLACE, but the 
+  ** pre-update-hook is.
+  */ 
   if( pTab->pSelect==0 ){
     sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, 0);
     sqlite3VdbeAddOp2(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0));
-    if( count ){
-      sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC);
-    }
+    sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC);
   }
 
   /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
index adf6ef2ed41eb2ad38b224c5cc32c91c4f61772d..98f00bfe3d438de5da75bdb425095ecd8ceb58ca 100644 (file)
@@ -1287,9 +1287,17 @@ void sqlite3GenerateConstraintChecks(
           sqlite3GenerateRowDelete(
               pParse, pTab, baseCur, regRowid, 0, pTrigger, OE_Replace
           );
-        }else if( pTab->pIndex ){
-          sqlite3MultiWrite(pParse);
-          sqlite3GenerateRowIndexDelete(pParse, pTab, baseCur, 0);
+        }else{
+          /* This OP_Delete opcode fires the pre-update-hook only. It does
+          ** not modify the b-tree. It is more efficient to let the coming
+          ** OP_Insert replace the existing entry than it is to delete the
+          ** existing entry and then insert a new one. */
+          sqlite3VdbeAddOp2(v, OP_Delete, baseCur, OPFLAG_ISNOOP);
+          sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC);
+          if( pTab->pIndex ){
+            sqlite3MultiWrite(pParse);
+            sqlite3GenerateRowIndexDelete(pParse, pTab, baseCur, 0);
+          }
         }
         seenReplace = 1;
         break;
index 833b9812b0cc6f2b0b3365637b8ebabd83f6a7cf..3976f7ebc30a6c74eb9b1427f662ec2d547bc5f2 100644 (file)
@@ -1271,6 +1271,25 @@ void *sqlite3_rollback_hook(
   return pRet;
 }
 
+/*
+** Register a callback to be invoked each time a row is updated,
+** inserted or deleted using this database connection.
+*/
+void *sqlite3_preupdate_hook(
+  sqlite3 *db,              /* Attach the hook to this database */
+  void(*xCallback)(         /* Callback function */
+    void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64),
+  void *pArg                /* First callback argument */
+){
+  void *pRet;
+  sqlite3_mutex_enter(db->mutex);
+  pRet = db->pPreUpdateArg;
+  db->xPreUpdateCallback = xCallback;
+  db->pPreUpdateArg = pArg;
+  sqlite3_mutex_leave(db->mutex);
+  return pRet;
+}
+
 #ifndef SQLITE_OMIT_WAL
 /*
 ** The sqlite3_wal_hook() callback registered by sqlite3_wal_autocheckpoint().
index 3400c6c9169256a6f1d0ba31a453c46628def2db..413031ecfaee351989756ba3945ffb0370fd7eca 100644 (file)
@@ -6344,6 +6344,30 @@ int sqlite3_wal_checkpoint_v2(
 #define SQLITE_CHECKPOINT_FULL    1
 #define SQLITE_CHECKPOINT_RESTART 2
 
+void *sqlite3_preupdate_hook(
+  sqlite3 *db,
+  void(*xPreUpdate)(
+    void *pCtx,                   /* Copy of third arg to preupdate_hook() */
+    sqlite3 *db,                  /* Database handle */
+    int op,                       /* SQLITE_UPDATE, DELETE or INSERT */
+    char const *zDb,              /* Database name */
+    char const *zName,            /* Table name */
+    sqlite3_int64 iKey1,          /* Rowid of row about to be deleted/updated */
+    sqlite3_int64 iKey2           /* New rowid value (for a rowid UPDATE) */
+  ),
+  void*
+);
+
+/*
+** 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.
+*/
+int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **);
+int sqlite3_preupdate_modified(sqlite3 *, int, int *);
+int sqlite3_preupdate_count(sqlite3 *);
+
 
 /*
 ** Undo the hack that converts floating point types to integer for
index 2987dcd483727bbba28ef60f1b2757d28d78eb12..25419368489c2a8fecb38b185a5820839b4372a9 100644 (file)
@@ -619,6 +619,7 @@ typedef struct LookasideSlot LookasideSlot;
 typedef struct Module Module;
 typedef struct NameContext NameContext;
 typedef struct Parse Parse;
+typedef struct PreUpdate PreUpdate;
 typedef struct RowSet RowSet;
 typedef struct Savepoint Savepoint;
 typedef struct Select Select;
@@ -827,6 +828,11 @@ struct sqlite3 {
   void (*xRollbackCallback)(void*); /* Invoked at every commit. */
   void *pUpdateArg;
   void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64);
+  void *pPreUpdateArg;          /* First argument to xPreUpdateCallback */
+  void (*xPreUpdateCallback)(   /* Registered using sqlite3_preupdate_hook() */
+    void*,sqlite3*,int,char const*,char const*,sqlite3_int64,sqlite3_int64
+  );
+  PreUpdate *pPreUpdate;        /* Context for active pre-update callback */
 #ifndef SQLITE_OMIT_WAL
   int (*xWalCallback)(void *, sqlite3 *, const char *, int);
   void *pWalArg;
@@ -2254,6 +2260,7 @@ struct AuthContext {
 #define OPFLAG_APPEND        0x08    /* This is likely to be an append */
 #define OPFLAG_USESEEKRESULT 0x10    /* Try to avoid a seek in BtreeInsert() */
 #define OPFLAG_CLEARCACHE    0x20    /* Clear pseudo-table cache in OP_Column */
+#define OPFLAG_ISNOOP        0x40    /* OP_Delete does pre-update-hook only */
 
 /*
  * Each trigger present in the database schema is stored as an instance of
index 57f38d78d165b1966fad76a5d993607b7416f3e1..91c6094fa91fe2614ca71ad94aa78739381ad9c5 100644 (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;
   }
 
index 8bf58d76669a8b4c597514bed12b97a2827df708..ea8f95ad566e864db7583fb2b846d824df72f2f0 100644 (file)
@@ -490,9 +490,16 @@ void sqlite3Update(
     j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regOldRowid);
     sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx);
   
-    /* If changing the record number, delete the old record.  */
-    if( hasFK || chngRowid ){
-      sqlite3VdbeAddOp2(v, OP_Delete, iCur, 0);
+    /* If changing the rowid value, or if there are foreign key constraints
+    ** to process, delete the old record. Otherwise, add a noop OP_Delete
+    ** to invoke the pre-update hook.
+    */
+    sqlite3VdbeAddOp3(v, OP_Delete, iCur,
+        OPFLAG_ISUPDATE | ((hasFK || chngRowid) ? 0 : OPFLAG_ISNOOP),
+        regNewRowid
+    );
+    if( !pParse->nested ){
+      sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC);
     }
     sqlite3VdbeJumpHere(v, j1);
 
index 00ed1438b584e392f0b6f186baff00ff0c0a0975..c5492eedc8857a032e9606744eea4aa3f324b34e 100644 (file)
@@ -3868,6 +3868,24 @@ case OP_InsertInt: {
     iKey = pOp->p3;
   }
 
+  if( pOp->p4.z && (db->xPreUpdateCallback || db->xUpdateCallback) ){
+    assert( pC->isTable );
+    assert( pC->iDb>=0 );
+    zDb = db->aDb[pC->iDb].zName;
+    zTbl = pOp->p4.z;
+    op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
+  }
+
+  /* Invoke the pre-update hook, if any */
+  if( db->xPreUpdateCallback 
+   && pOp->p4.z 
+   && (!(pOp->p5 & OPFLAG_ISUPDATE) || pC->rowidIsValid==0)
+  ){
+    sqlite3VdbePreUpdateHook(p, pC,
+      pC->rowidIsValid ? op : SQLITE_INSERT, zDb, zTbl, iKey, iKey
+    );
+  }
+
   if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++;
   if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = iKey;
   if( pData->flags & MEM_Null ){
@@ -3893,17 +3911,12 @@ case OP_InsertInt: {
 
   /* Invoke the update-hook if required. */
   if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z ){
-    zDb = db->aDb[pC->iDb].zName;
-    zTbl = pOp->p4.z;
-    op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT);
-    assert( pC->isTable );
     db->xUpdateCallback(db->pUpdateArg, op, zDb, zTbl, iKey);
-    assert( pC->iDb>=0 );
   }
   break;
 }
 
-/* Opcode: Delete P1 P2 * P4 *
+/* Opcode: Delete P1 P2 P3 P4 *
 **
 ** Delete the record at which the P1 cursor is currently pointing.
 **
@@ -3918,30 +3931,31 @@ case OP_InsertInt: {
 ** P1 must not be pseudo-table.  It has to be a real table with
 ** multiple rows.
 **
-** If P4 is not NULL, then it is the name of the table that P1 is
-** pointing to.  The update hook will be invoked, if it exists.
-** If P4 is not NULL then the P1 cursor must have been positioned
-** using OP_NotFound prior to invoking this opcode.
+** If P4 is not NULL then, either the update or pre-update hook, or both,
+** may be invoked. The P1 cursor must have been positioned using OP_NotFound 
+** prior to invoking this opcode in this case. Specifically, if one is 
+** configured, the pre-update hook is invoked if P4 is not NULL. The 
+** update-hook is invoked if one is configured, P4 is not NULL, and the 
+** OPFLAG_NCHANGE flag is set in P2.
+**
+** If the OPFLAG_ISUPDATE flag is set in P2, then P3 contains the address
+** of the memory cell that contains the value that the rowid of the row will
+** be set to by the update.
 */
 case OP_Delete: {
   i64 iKey;
   VdbeCursor *pC;
+  const char *zDb;
+  const char *zTbl;
+  int opflags;
 
+  opflags = pOp->p2;
   iKey = 0;
   assert( pOp->p1>=0 && pOp->p1<p->nCursor );
   pC = p->apCsr[pOp->p1];
   assert( pC!=0 );
   assert( pC->pCursor!=0 );  /* Only valid for real tables, no pseudotables */
 
-  /* If the update-hook will be invoked, set iKey to the rowid of the
-  ** row being deleted.
-  */
-  if( db->xUpdateCallback && pOp->p4.z ){
-    assert( pC->isTable );
-    assert( pC->rowidIsValid );  /* lastRowid set by previous OP_NotFound */
-    iKey = pC->lastRowid;
-  }
-
   /* The OP_Delete opcode always follows an OP_NotExists or OP_Last or
   ** OP_Column on the same table without any intervening operations that
   ** might move or invalidate the cursor.  Hence cursor pC is always pointing
@@ -3953,18 +3967,42 @@ case OP_Delete: {
   rc = sqlite3VdbeCursorMoveto(pC);
   if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error;
 
+  /* If the update-hook or pre-update-hook will be invoked, set iKey to 
+  ** the rowid of the row being deleted. Set zDb and zTab as well.
+  */
+  if( pOp->p4.z && (db->xPreUpdateCallback || db->xUpdateCallback) ){
+    assert( pC->iDb>=0 );
+    assert( pC->isTable );
+    assert( pC->rowidIsValid );  /* lastRowid set by previous OP_NotFound */
+    iKey = pC->lastRowid;
+    zDb = db->aDb[pC->iDb].zName;
+    zTbl = pOp->p4.z;
+  }
+
+  /* Invoke the pre-update-hook if required. */
+  if( db->xPreUpdateCallback && pOp->p4.z ){
+    assert( !(opflags & OPFLAG_ISUPDATE) || (aMem[pOp->p3].flags & MEM_Int) );
+    sqlite3VdbePreUpdateHook(p, pC,
+        (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, 
+        zDb, zTbl, iKey,
+        (opflags & OPFLAG_ISUPDATE) ? aMem[pOp->p3].u.i : iKey
+    );
+  }
+
+  if( opflags & OPFLAG_ISNOOP ) break;
+
   sqlite3BtreeSetCachedRowid(pC->pCursor, 0);
   rc = sqlite3BtreeDelete(pC->pCursor);
   pC->cacheStatus = CACHE_STALE;
 
-  /* Invoke the update-hook if required. */
-  if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z ){
-    const char *zDb = db->aDb[pC->iDb].zName;
-    const char *zTbl = pOp->p4.z;
-    db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, zTbl, iKey);
-    assert( pC->iDb>=0 );
+  /* Update the change-counter and invoke the update-hook if required. */
+  if( opflags & OPFLAG_NCHANGE ){
+    p->nChange++;
+    assert( pOp->p4.z );
+    if( rc==SQLITE_OK && db->xUpdateCallback ){
+      db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, zTbl, iKey);
+    }
   }
-  if( pOp->p2 & OPFLAG_NCHANGE ) p->nChange++;
   break;
 }
 /* Opcode: ResetCount * * * * *
index b42729d23645d6f7949270d099ad6a8715b354ea..a764d23665756f63c60fba377a8aa0724118514e 100644 (file)
@@ -331,6 +331,17 @@ struct Vdbe {
 #define VDBE_MAGIC_HALT     0x519c2973    /* VDBE has completed execution */
 #define VDBE_MAGIC_DEAD     0xb606c3c8    /* The VDBE has been deallocated */
 
+/*
+** Structure used to store the context required by the 
+** sqlite3_preupdate_*() API functions.
+*/
+struct PreUpdate {
+  VdbeCursor *pCsr;               /* Cursor to read old values from */
+  int op;                         /* One of SQLITE_INSERT, UPDATE, DELETE */
+  u8 *aRecord;                    /* old.* database record */
+  UnpackedRecord *pUnpacked;      /* Unpacked version of aRecord[] */
+};
+
 /*
 ** Function prototypes
 */
@@ -387,6 +398,8 @@ int sqlite3VdbeCloseStatement(Vdbe *, int);
 void sqlite3VdbeFrameDelete(VdbeFrame*);
 int sqlite3VdbeFrameRestore(VdbeFrame *);
 void sqlite3VdbeMemStoreType(Mem *pMem);
+void sqlite3VdbePreUpdateHook(
+    Vdbe *, VdbeCursor *, int, const char*, const char*, i64, i64);
 
 #ifdef SQLITE_DEBUG
 void sqlite3VdbeMemPrepareToChange(Vdbe*,Mem*);
index 5578b868e1e87f2c5c1942b1ebcc71aeeb49b8e6..8b1f2e21bbcc1429481817720ef6111023c014b0 100644 (file)
@@ -673,6 +673,26 @@ int sqlite3_data_count(sqlite3_stmt *pStmt){
   return pVm->nResColumn;
 }
 
+/*
+** Return a pointer to static memory containing an SQL NULL value.
+*/
+static const Mem *columnNullValue(void){
+  /* Even though the Mem structure contains an element
+  ** of type i64, on certain architecture (x86) with certain compiler
+  ** switches (-Os), gcc may align this Mem object on a 4-byte boundary
+  ** instead of an 8-byte one. This all works fine, except that when
+  ** running with SQLITE_DEBUG defined the SQLite code sometimes assert()s
+  ** that a Mem structure is located on an 8-byte boundary. To prevent
+  ** this assert() from failing, when building with SQLITE_DEBUG defined
+  ** using gcc, force nullMem to be 8-byte aligned using the magical
+  ** __attribute__((aligned(8))) macro.  */
+  static const Mem nullMem 
+#if defined(SQLITE_DEBUG) && defined(__GNUC__)
+  __attribute__((aligned(8))) 
+#endif
+    = {0, "", (double)0, {0}, 0, MEM_Null, SQLITE_NULL, 0, 0, 0 };
+  return &nullMem;
+}
 
 /*
 ** Check to see if column iCol of the given statement is valid.  If
@@ -692,27 +712,13 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){
     pOut = &pVm->pResultSet[i];
   }else{
     /* If the value passed as the second argument is out of range, return
-    ** a pointer to the following static Mem object which contains the
-    ** value SQL NULL. Even though the Mem structure contains an element
-    ** of type i64, on certain architecture (x86) with certain compiler
-    ** switches (-Os), gcc may align this Mem object on a 4-byte boundary
-    ** instead of an 8-byte one. This all works fine, except that when
-    ** running with SQLITE_DEBUG defined the SQLite code sometimes assert()s
-    ** that a Mem structure is located on an 8-byte boundary. To prevent
-    ** this assert() from failing, when building with SQLITE_DEBUG defined
-    ** using gcc, force nullMem to be 8-byte aligned using the magical
-    ** __attribute__((aligned(8))) macro.  */
-    static const Mem nullMem 
-#if defined(SQLITE_DEBUG) && defined(__GNUC__)
-      __attribute__((aligned(8))) 
-#endif
-      = {0, "", (double)0, {0}, 0, MEM_Null, SQLITE_NULL, 0, 0, 0 };
-
+    ** a pointer to a static Mem object that contains the value SQL NULL. 
+    */
     if( pVm && ALWAYS(pVm->db) ){
       sqlite3_mutex_enter(pVm->db->mutex);
       sqlite3Error(pVm->db, SQLITE_RANGE, 0);
     }
-    pOut = (Mem*)&nullMem;
+    pOut = (Mem*)columnNullValue();
   }
   return pOut;
 }
@@ -1322,3 +1328,78 @@ int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){
   if( resetFlag ) pVdbe->aCounter[op-1] = 0;
   return v;
 }
+
+int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
+  PreUpdate *p = db->pPreUpdate;
+  int rc = SQLITE_OK;
+
+  if( !p || p->op==SQLITE_INSERT ){
+    rc = SQLITE_MISUSE_BKPT;
+    goto preupdate_old_out;
+  }
+  if( iIdx>=p->pCsr->nField || iIdx<0 ){
+    rc = SQLITE_RANGE;
+    goto preupdate_old_out;
+  }
+
+  if( p->pUnpacked==0 ){
+    KeyInfo keyinfo;
+    u32 nRecord;
+    u8 *aRecord;
+
+    memset(&keyinfo, 0, sizeof(KeyInfo));
+    keyinfo.db = db;
+    keyinfo.enc = ENC(db);
+    keyinfo.nField = p->pCsr->nField;
+
+    rc = sqlite3BtreeDataSize(p->pCsr->pCursor, &nRecord);
+    if( rc!=SQLITE_OK ) goto preupdate_old_out;
+    aRecord = sqlite3DbMallocRaw(db, nRecord);
+    if( !aRecord ) goto preupdate_old_out;
+    rc = sqlite3BtreeData(p->pCsr->pCursor, 0, nRecord, aRecord);
+    if( rc!=SQLITE_OK ){
+      sqlite3DbFree(db, aRecord);
+      goto preupdate_old_out;
+    }
+
+    p->pUnpacked = sqlite3VdbeRecordUnpack(&keyinfo, nRecord, aRecord, 0, 0);
+    p->aRecord = aRecord;
+  }
+
+  if( iIdx>=p->pUnpacked->nField ){
+    *ppValue = (sqlite3_value *)columnNullValue();
+  }else{
+    *ppValue = &p->pUnpacked->aMem[iIdx];
+    sqlite3VdbeMemStoreType(*ppValue);
+  }
+
+ preupdate_old_out:
+  sqlite3Error(db, rc, 0);
+  return sqlite3ApiExit(db, rc);
+}
+
+int sqlite3_preupdate_count(sqlite3 *db){
+  PreUpdate *p = db->pPreUpdate;
+  return (p ? p->pCsr->nField : 0);
+}
+
+int sqlite3_preupdate_modified(sqlite3 *db, int iIdx, int *pbMod){
+  PreUpdate *p = db->pPreUpdate;
+  int rc = SQLITE_OK;
+
+  if( !p || p->op!=SQLITE_UPDATE ){
+    rc = SQLITE_MISUSE_BKPT;
+    goto preupdate_mod_out;
+  }
+  if( iIdx>=p->pCsr->nField || iIdx<0 ){
+    rc = SQLITE_RANGE;
+    goto preupdate_mod_out;
+  }
+  *pbMod = 1;
+
+ preupdate_mod_out:
+  sqlite3Error(db, rc, 0);
+  return sqlite3ApiExit(db, rc);
+}
+
+
index 64ff48991950ebd78e2ae2418b893829712f92bc..31cf669144d15fd82c4742a5ac7011252895941e 100644 (file)
@@ -2830,7 +2830,7 @@ void sqlite3VdbeDeleteUnpackedRecord(UnpackedRecord *p){
     ** strings and blobs static.  And none of the elements are
     ** ever transformed, so there is never anything to delete.
     */
-    if( NEVER(pMem->zMalloc) ) sqlite3VdbeMemRelease(pMem);
+    if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem);
   }
   if( p->flags & UNPACKED_NEED_FREE ){
     sqlite3DbFree(p->pKeyInfo->db, p);
@@ -3162,3 +3162,35 @@ void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){
     v->expmask |= ((u32)1 << (iVar-1));
   }
 }
+
+/*
+** Invoke the pre-update hook. If this is an UPDATE or DELETE pre-update call,
+** then cursor passed as the second argument should point to the row about
+** to be update or deleted. If the application calls sqlite3_preupdate_old(),
+** the required value will be read from the row the cursor points to.
+*/
+void sqlite3VdbePreUpdateHook(
+  Vdbe *v,                        /* Vdbe pre-update hook is invoked by */
+  VdbeCursor *pCsr,               /* Cursor to grab old.* values from */
+  int op,                         /* SQLITE_INSERT, UPDATE or DELETE */
+  const char *zDb,                /* Database name */
+  const char *zTbl,               /* Table name */
+  i64 iKey1,                      /* Initial key value */
+  i64 iKey2                       /* Final key value */
+){
+  sqlite3 *db = v->db;
+
+  PreUpdate preupdate;
+  memset(&preupdate, 0, sizeof(PreUpdate));
+
+  preupdate.pCsr = pCsr;
+  preupdate.op = op;
+  db->pPreUpdate = &preupdate;
+  db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2);
+  db->pPreUpdate = 0;
+  sqlite3DbFree(db, preupdate.aRecord);
+  if( preupdate.pUnpacked ){
+    sqlite3VdbeDeleteUnpackedRecord(preupdate.pUnpacked);
+  }
+}
+
index 6496d41e137bbaf909967c7d5fc2876657411bb2..827ce08b3d0d9c8d4228b26ab899d34b84dbd868 100644 (file)
@@ -21,6 +21,7 @@
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
+set ::testprefix hook
 
 do_test hook-1.2 {
   db commit_hook
@@ -361,4 +362,270 @@ do_test hook-6.2 {
 } {COMMIT ROLLBACK}
 unset ::hooks
 
+#----------------------------------------------------------------------------
+# The following tests - hook-7.* - test the pre-update hook.
+#
+# 7.1.1 - INSERT statement.
+# 7.1.2 - INSERT INTO ... SELECT statement.
+# 7.1.3 - REPLACE INTO ... (rowid conflict)
+# 7.1.4 - REPLACE INTO ... (other index conflicts)
+# 7.1.5 - REPLACE INTO ... (both rowid and other index conflicts)
+#
+# 7.2.1 - DELETE statement.
+# 7.2.2 - DELETE statement that uses the truncate optimization.
+#
+# 7.3.1 - UPDATE statement.
+# 7.3.2 - UPDATE statement that modifies the rowid.
+# 7.3.3 - UPDATE OR REPLACE ... (rowid conflict).
+# 7.3.4 - UPDATE OR REPLACE ... (other index conflicts)
+# 7.3.4 - UPDATE OR REPLACE ... (both rowid and other index conflicts)
+#
+# 7.4.1 - Test that the pre-update-hook is invoked only once if a row being
+#         deleted is removed by a BEFORE trigger.
+#
+# 7.4.2 - Test that the pre-update-hook is invoked if a BEFORE trigger 
+#         removes a row being updated. In this case the update hook should
+#         be invoked with SQLITE_INSERT as the opcode when inserting the
+#         new version of the row.
+#         
+# TODO: Short records (those created before a column is added to a table 
+#       using ALTER TABLE)
+#
+
+proc do_preupdate_test {tn sql x} {
+  set X [list]
+  foreach elem $x {lappend X $elem}
+  uplevel do_test $tn [list "
+    set ::preupdate \[list\]
+    execsql { $sql }
+    set ::preupdate
+  "] [list $X]
+}
+
+proc preupdate_hook {args} {
+  set type [lindex $args 0]
+  eval lappend ::preupdate $args
+  if {$type != "SQLITE_INSERT"} {
+    for {set i 0} {$i < [db preupdate count]} {incr i} {
+      lappend ::preupdate [db preupdate old $i]
+    }
+  }
+}
+
+db close
+forcedelete test.db
+sqlite3 db test.db
+db preupdate hook preupdate_hook
+
+# Set up a schema to use for tests 7.1.* to 7.3.*.
+do_execsql_test 7.0 { 
+  CREATE TABLE t1(a, b); 
+  CREATE TABLE t2(x, y); 
+  CREATE TABLE t3(i, j, UNIQUE(i));
+
+  INSERT INTO t2 VALUES('a', 'b');
+  INSERT INTO t2 VALUES('c', 'd');
+
+  INSERT INTO t3 VALUES(4, 16);
+  INSERT INTO t3 VALUES(5, 25);
+  INSERT INTO t3 VALUES(6, 36);
+} 
+
+do_preupdate_test 7.1.1 {
+  INSERT INTO t1 VALUES('x', 'y')
+} {INSERT main t1 1 1}
+
+# 7.1.2.1 does not use the xfer optimization. 7.1.2.2 does.
+do_preupdate_test 7.1.2.1 {
+  INSERT INTO t1 SELECT y, x FROM t2;
+} {INSERT main t1 2 2 INSERT main t1 3 3}
+do_preupdate_test 7.1.2.2 {
+  INSERT INTO t1 SELECT * FROM t2;
+} {INSERT main t1 4 4 INSERT main t1 5 5}
+
+do_preupdate_test 7.1.3 {
+  REPLACE INTO t1(rowid, a, b) VALUES(1, 1, 1);
+} {
+  DELETE main t1 1 1   x y
+  INSERT main t1 1 1
+}
+
+do_preupdate_test 7.1.4 {
+  REPLACE INTO t3 VALUES(4, NULL);
+} {
+  DELETE main t3 1 1   4 16
+  INSERT main t3 4 4
+}
+
+do_preupdate_test 7.1.5 {
+  REPLACE INTO t3(rowid, i, j) VALUES(2, 6, NULL);
+} {
+  DELETE main t3 2 2  5 25
+  DELETE main t3 3 3  6 36
+  INSERT main t3 2 2
+}
+
+do_execsql_test 7.2.0 { SELECT rowid FROM t1 } {1 2 3 4 5}
+
+do_preupdate_test 7.2.1 {
+  DELETE FROM t1 WHERE rowid = 3
+} {
+  DELETE main t1 3 3  d c
+}
+do_preupdate_test 7.2.2 {
+  DELETE FROM t1
+} {
+  DELETE main t3 1 1   1 1
+  DELETE main t3 2 2   b a
+  DELETE main t3 4 4   a b
+  DELETE main t3 5 5   c d
+}
+
+do_execsql_test 7.3.0 { 
+  DELETE FROM t1;
+  DELETE FROM t2;
+  DELETE FROM t3;
+
+  INSERT INTO t2 VALUES('a', 'b');
+  INSERT INTO t2 VALUES('c', 'd');
+
+  INSERT INTO t3 VALUES(4, 16);
+  INSERT INTO t3 VALUES(5, 25);
+  INSERT INTO t3 VALUES(6, 36);
+}
+
+do_preupdate_test 7.3.1 {
+  UPDATE t2 SET y = y||y;
+} {
+  UPDATE main t2 1 1   a b
+  UPDATE main t2 2 2   c d
+}
+
+do_preupdate_test 7.3.2 {
+  UPDATE t2 SET rowid = rowid-1;
+} {
+  UPDATE main t2 1 0   a bb
+  UPDATE main t2 2 1   c dd
+}
+
+do_preupdate_test 7.3.3 {
+  UPDATE OR REPLACE t2 SET rowid = 1 WHERE x = 'a'
+} {
+  DELETE main t2 1 1   c dd
+  UPDATE main t2 0 1   a bb
+}
+
+do_preupdate_test 7.3.4.1 {
+  UPDATE OR REPLACE t3 SET i = 5 WHERE i = 6
+} {
+  DELETE main t3 2 2   5 25
+  UPDATE main t3 3 3   6 36
+}
+
+do_execsql_test 7.3.4.2 {
+  INSERT INTO t3 VALUES(10, 100);
+  SELECT rowid, * FROM t3;
+} {1 4 16   3 5 36   4 10 100}
+
+do_preupdate_test 7.3.5 {
+  UPDATE OR REPLACE t3 SET rowid = 1, i = 5 WHERE j = 100;
+} {
+  DELETE main t3 1 1    4  16
+  DELETE main t3 3 3    5  36
+  UPDATE main t3 4 1   10 100
+}
+
+do_execsql_test 7.4.1.0 {
+  CREATE TABLE t4(a, b);
+  INSERT INTO t4 VALUES('a', 1);
+  INSERT INTO t4 VALUES('b', 2);
+  INSERT INTO t4 VALUES('c', 3);
+
+  CREATE TRIGGER t4t BEFORE DELETE ON t4 BEGIN
+    DELETE FROM t4 WHERE b = 1;
+  END;
+}
+
+do_preupdate_test 7.4.1.1 {
+  DELETE FROM t4 WHERE b = 3
+} {
+  DELETE main t4 1 1   a 1
+  DELETE main t4 3 3   c 3
+}
+
+do_execsql_test 7.4.1.2 {
+  INSERT INTO t4(rowid, a, b) VALUES(1, 'a', 1);
+  INSERT INTO t4(rowid, a, b) VALUES(3, 'c', 3);
+}
+do_preupdate_test 7.4.1.3 {
+  DELETE FROM t4 WHERE b = 1
+} {
+  DELETE main t4 1 1   a 1
+}
+
+do_execsql_test 7.4.2.0 {
+  CREATE TABLE t5(a, b);
+  INSERT INTO t5 VALUES('a', 1);
+  INSERT INTO t5 VALUES('b', 2);
+  INSERT INTO t5 VALUES('c', 3);
+
+  CREATE TRIGGER t5t BEFORE UPDATE ON t5 BEGIN
+    DELETE FROM t5 WHERE b = 1;
+  END;
+}
+do_preupdate_test 7.4.2.1 {
+  UPDATE t5 SET b = 4 WHERE a = 'c'
+} {
+  DELETE main t5 1 1   a 1
+  UPDATE main t5 3 3   c 3
+}
+
+do_execsql_test 7.4.2.2 {
+  INSERT INTO t5(rowid, a, b) VALUES(1, 'a', 1);
+}
+
+do_preupdate_test 7.4.2.3 {
+  UPDATE t5 SET b = 5 WHERE a = 'a'
+} {
+  DELETE main t5 1 1   a 1
+}
+
+do_execsql_test 7.5.1.0 {
+  CREATE TABLE t7(a, b);
+  INSERT INTO t7 VALUES('one', 'two');
+  INSERT INTO t7 VALUES('three', 'four');
+  ALTER TABLE t7 ADD COLUMN c DEFAULT NULL;
+}
+
+do_preupdate_test 7.5.1.1 {
+  DELETE FROM t7 WHERE a = 'one'
+} {
+  DELETE main t7 1 1   one two {}
+}
+
+do_preupdate_test 7.5.1.2 {
+  UPDATE t7 SET b = 'five'
+} {
+  UPDATE main t7 2 2   three four {}
+}
+
+do_execsql_test 7.5.2.0 {
+  CREATE TABLE t8(a, b);
+  INSERT INTO t8 VALUES('one', 'two');
+  INSERT INTO t8 VALUES('three', 'four');
+  ALTER TABLE t8 ADD COLUMN c DEFAULT 'xxx';
+}
+
+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
+}
+
 finish_test
old mode 100644 (file)
new mode 100755 (executable)
index 8db04ebfa42f01ca4a06ae34865386221c27c4c1..c9d562beb566ba6247c38fe87bec0450a2ad4bca 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, 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, unlock_notify, update_hook, version, or wal_hook}}
 do_test tcl-1.2.1 {
   set v [catch {db cache bogus} msg]
   lappend v $msg
old mode 100644 (file)
new mode 100755 (executable)