]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
If recursive-triggers are enabled, fire DELETE triggers if database rows are removed...
authordan <dan@noemail.net>
Tue, 8 Sep 2009 15:55:15 +0000 (15:55 +0000)
committerdan <dan@noemail.net>
Tue, 8 Sep 2009 15:55:15 +0000 (15:55 +0000)
FossilOrigin-Name: 85cb0c94a63eda5f059ebe40887c7af9b4869893

manifest
manifest.uuid
src/delete.c
src/insert.c
src/sqliteInt.h
test/triggerC.test

index 5d455053430af68571cb526da6900af9d1b376af..29b6ef18a215255c73f88f4f299cbadd739f1156 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,8 +1,5 @@
------BEGIN PGP SIGNED MESSAGE-----
-Hash: SHA1
-
-C Additional\ssimplifications\sin\ssupport\sof\sstructural\stesting.
-D 2009-09-08T13:40:17
+C If\srecursive-triggers\sare\senabled,\sfire\sDELETE\striggers\sif\sdatabase\srows\sare\sremoved\sas\sa\sresult\sof\sOR\sREPLACE\sconflict\sresolution.
+D 2009-09-08T15:55:16
 F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
 F Makefile.in 73ddeec9dd10b85876c5c2ce1fdce627e1dcc7f8
 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
@@ -116,7 +113,7 @@ F src/build.c df8dfff696329c192240e3d532f9dad0ef5abace
 F src/callback.c f49c305dc94b78da948953c392963929c0e70f9b
 F src/complete.c 5ad5c6cd4548211867c204c41a126d73a9fbcea0
 F src/date.c ab5f7137656652a48434d64f96bdcdc823bb23b3
-F src/delete.c 6b95963dabd558d45385e9b5be1fb4aa7ba7fa62
+F src/delete.c 4c9b899246a12795ae7f145ad7c5c3ac563fa05f
 F src/expr.c 2605f0f161442e3153e0c41e987525260e9ad306
 F src/fault.c dc88c821842157460750d2d61a8a8b4197d047ff
 F src/func.c e536218d193b8d326aab91120bc4c6f28aa2b606
@@ -124,7 +121,7 @@ F src/global.c 271952d199a8cc59d4ce840b3bbbfd2f30c8ba32
 F src/hash.c ebcaa921ffd9d86f7ea5ae16a0a29d1c871130a7
 F src/hash.h 35b216c13343d0b4f87d9f21969ac55ad72174e1
 F src/hwtime.h 4a1d45f4cae1f402ea19686acf24acf4f0cb53cb
-F src/insert.c 06fe504934bdd3b3a0fa0e11ccd6506b57114c52
+F src/insert.c 5cf80f9b4222c2145cab299e9b829385846b6937
 F src/journal.c e00df0c0da8413ab6e1bb7d7cab5665d4a9000d0
 F src/legacy.c 303b4ffcf1ae652fcf5ef635846c563c254564f6
 F src/lempar.c 0c4d1ab0a5ef2b0381eb81a732c54f68f27a574d
@@ -166,7 +163,7 @@ F src/select.c a7a075456d4e640ffd7d0a33202d306c69c88f72
 F src/shell.c db2643650b9268df89a4bedca3f1c6d9e786f1bb
 F src/sqlite.h.in e5949b46f9a05aadde22848f92fae5c9ba87ee0e
 F src/sqlite3ext.h 1db7d63ab5de4b3e6b83dd03d1a4e64fef6d2a17
-F src/sqliteInt.h 6c03c685f37c2403ae78c4eb34f9ee9082748270
+F src/sqliteInt.h b39de08df1442d48a6ea85c227c609e696a85b89
 F src/sqliteLimit.h be44f7f46c14bb4c21870074b1e6f1ac0abd6701
 F src/status.c 237b193efae0cf6ac3f0817a208de6c6c6ef6d76
 F src/table.c cc86ad3d6ad54df7c63a3e807b5783c90411a08d
@@ -685,7 +682,7 @@ F test/trigger8.test 30cb0530bd7c4728055420e3f739aa00412eafa4
 F test/trigger9.test 5b0789f1c5c4600961f8e68511b825b87be53e31
 F test/triggerA.test 0718ad2d9bfef27c7af00e636df79bee6b988da7
 F test/triggerB.test 56780c031b454abac2340dbb3b71ac5c56c3d7fe
-F test/triggerC.test cc43b4a62f447a0b0ec76ce511758c460c049c83
+F test/triggerC.test 3e13e9a87939797115343b261a3f893c71304106
 F test/types.test 9a825ec8eea4e965d7113b74c76a78bb5240f2ac
 F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84
 F test/types3.test a0f66bf12f80fad89493535474f7a6d16fa58150
@@ -753,14 +750,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
 F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P b271e16621831957468a1d3925174aac73f58891
-R 8c612cb15dd060090bb2d4d886dee584
-U drh
-Z a9d8abd000fb942a5d5935b1dbcc5c31
------BEGIN PGP SIGNATURE-----
-Version: GnuPG v1.4.6 (GNU/Linux)
-
-iD8DBQFKpl7EoxKgR168RlERArvgAJ9VIhI4NUVMwROuivJoGqBtHQo34ACdFY4R
-tYb9IFwgX3mkui92z9PMIsY=
-=tFOy
------END PGP SIGNATURE-----
+P 4ab8c841f818326b0b04b95e3edd828c77f109d9
+R 3fb4417640a30cdf072206af6c01b085
+U dan
+Z 8bcac683a9d824da75eea0a85386a639
index 10a03f931eb6bc8c1424ed32a039054007308553..15344aef712c9d563837d540aa1921924e423ae5 100644 (file)
@@ -1 +1 @@
-4ab8c841f818326b0b04b95e3edd828c77f109d9
\ No newline at end of file
+85cb0c94a63eda5f059ebe40887c7af9b4869893
\ No newline at end of file
index ae4a8b58467f2ba80e12cb93815a33b7be0b3b2d..f0ca8ab4714a4eb616375c3daf586df3837bb172 100644 (file)
@@ -338,9 +338,9 @@ void sqlite3DeleteFrom(
 
 #ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION
   /* Special case: A DELETE without a WHERE clause deletes everything.
-  ** It is easier just to erase the whole table.  Note, however, that
-  ** this means that the row change count will be incorrect.
-  */
+  ** It is easier just to erase the whole table. Prior to version 3.6.5,
+  ** this optimization caused the row change count (the value returned by 
+  ** API function sqlite3_count_changes) to be set incorrectly.  */
   if( rcauth==SQLITE_OK && pWhere==0 && !pTrigger && !IsVirtual(pTab) ){
     assert( !isView );
     sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt,
@@ -357,7 +357,6 @@ void sqlite3DeleteFrom(
   {
     int iRowSet = ++pParse->nMem;   /* Register for rowset of rows to delete */
     int iRowid = ++pParse->nMem;    /* Used for storing rowid values. */
-    int regOld = pParse->nMem + 1;  /* Start of array for old.* (if triggers) */
     int regRowid;                   /* Actual register containing rowids */
 
     /* Collect rowids of every row to be deleted.
@@ -387,56 +386,19 @@ void sqlite3DeleteFrom(
 
     addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, end, iRowid);
 
-    /* If there are triggers, populate an array of registers with the
-    ** data required by the old.* references in the trigger bodies.  */
-    if( pTrigger ){
-      u32 mask = 0;         /* Mask of OLD.* columns in use */
-      pParse->nMem += pTab->nCol;
-
-      /* Open the pseudo-table used to store OLD if there are triggers. */
-      mask = sqlite3TriggerOldmask(
-          pParse, pTrigger, TK_DELETE, 0, pTab, OE_Default);
-
-      /* If the record is no longer present in the table, jump to the
-      ** next iteration of the loop through the contents of the fifo.
-      */
-      sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, iRowid);
-
-      /* Populate the OLD.* pseudo-table */
-      assert( regOld==iRowid+1 );
-      for(i=0; i<pTab->nCol; i++){
-        if( mask==0xffffffff || mask&(1<<i) ){
-          sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regOld+i);
-          sqlite3ColumnDefault(v, pTab, i, regOld+i);
-        }
-      }
-      sqlite3VdbeAddOp2(v, OP_Affinity, regOld, pTab->nCol);
-      sqlite3TableAffinityStr(v, pTab);
-
-      sqlite3CodeRowTrigger(pParse, pTrigger, 
-          TK_DELETE, 0, TRIGGER_BEFORE, pTab, -1, iRowid, OE_Default, addr
-      );
-    }
-
-    if( !isView ){
-      /* Delete the row */
+    /* Delete the row */
 #ifndef SQLITE_OMIT_VIRTUALTABLE
-      if( IsVirtual(pTab) ){
-        const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
-        sqlite3VtabMakeWritable(pParse, pTab);
-        sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iRowid, pVTab, P4_VTAB);
-      }else
+    if( IsVirtual(pTab) ){
+      const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
+      sqlite3VtabMakeWritable(pParse, pTab);
+      sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iRowid, pVTab, P4_VTAB);
+    }else
 #endif
-      {
-        sqlite3GenerateRowDelete(pParse, pTab, iCur, iRowid, pParse->nested==0);
-      }
+    {
+      int count = (pParse->nested==0);    /* True to count changes */
+      sqlite3GenerateRowDelete(pParse, pTab, iCur, iRowid, count, pTrigger, OE_Default);
     }
 
-    /* Code the AFTER triggers. This is a no-op if there are no triggers. */
-    sqlite3CodeRowTrigger(pParse, 
-      pTrigger, TK_DELETE, 0, TRIGGER_AFTER, pTab, -1, iRowid, OE_Default, addr
-    );
-
     /* End of the delete loop */
     sqlite3VdbeAddOp2(v, OP_Goto, 0, addr);
     sqlite3VdbeResolveLabel(v, end);
@@ -458,8 +420,7 @@ void sqlite3DeleteFrom(
     sqlite3AutoincrementEnd(pParse);
   }
 
-  /*
-  ** Return the number of rows that were deleted. If this routine is 
+  /* Return the number of rows that were deleted. If this routine is 
   ** generating code because of a call to sqlite3NestedParse(), do not
   ** invoke the callback function.
   */
@@ -492,28 +453,88 @@ delete_from_cleanup:
 **   3.  The record number of the row to be deleted must be stored in
 **       memory cell iRowid.
 **
-** This routine pops the top of the stack to remove the record number
-** and then generates code to remove both the table record and all index
-** entries that point to that record.
+** This routine generates code to remove both the table record and all 
+** index entries that point to that record.
 */
 void sqlite3GenerateRowDelete(
   Parse *pParse,     /* Parsing context */
   Table *pTab,       /* Table containing the row to be deleted */
   int iCur,          /* Cursor number for the table */
   int iRowid,        /* Memory cell that contains the rowid to delete */
-  int count          /* Increment the row change counter */
+  int count,         /* If non-zero, increment the row change counter */
+  Trigger *pTrigger, /* List of triggers to (potentially) fire */
+  int onconf         /* Default ON CONFLICT policy for triggers */
 ){
-  int addr;
-  Vdbe *v;
-
-  v = pParse->pVdbe;
-  addr = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, iRowid);
-  sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, 0);
-  sqlite3VdbeAddOp2(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0));
-  if( count ){
-    sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC);
+  Vdbe *v = pParse->pVdbe;        /* Vdbe */
+  int iOld;                       /* First register in OLD.* array */
+  int iLabel;                     /* Label resolved to end of generated code */
+
+  /* Vdbe is guaranteed to have been allocated by this stage. */
+  assert( v );
+
+  /* Seek cursor iCur to the row to delete. If this row no longer exists 
+  ** (this can happen if a trigger program has already deleted it), do
+  ** not attempt to delete it or fire any DELETE triggers.  */
+  iLabel = sqlite3VdbeMakeLabel(v);
+  sqlite3VdbeAddOp3(v, OP_NotExists, iCur, iLabel, iRowid);
+  /* If there are any triggers to fire, allocate a range of registers to
+  ** use for the old.* references in the triggers.  */
+  if( pTrigger ){
+    u32 mask;                     /* Mask of OLD.* columns in use */
+    int iCol;                     /* Iterator used while populating OLD.* */
+
+    /* TODO: Could use temporary registers here. Also could attempt to
+    ** avoid copying the contents of the rowid register.  */
+    mask = sqlite3TriggerOldmask(pParse, pTrigger, TK_DELETE, 0, pTab, onconf);
+    iOld = pParse->nMem+1;
+    pParse->nMem += (1 + pTab->nCol);
+
+    /* Populate the OLD.* pseudo-table register array. These values will be 
+    ** used by any BEFORE and AFTER triggers that exist.  */
+    sqlite3VdbeAddOp2(v, OP_Copy, iRowid, iOld);
+    for(iCol=0; iCol<pTab->nCol; iCol++){
+      if( mask==0xffffffff || mask&(1<<iCol) ){
+        int iTarget = iOld + iCol + 1;
+        sqlite3VdbeAddOp3(v, OP_Column, iCur, iCol, iTarget);
+        sqlite3ColumnDefault(v, pTab, iCol, iTarget);
+      }
+    }
+
+    /* Invoke any BEFORE trigger programs */
+    sqlite3CodeRowTrigger(pParse, pTrigger, 
+        TK_DELETE, 0, TRIGGER_BEFORE, pTab, -1, iOld, onconf, iLabel
+    );
+
+    /* Seek the cursor to the row to be deleted again. It may be that
+    ** the BEFORE triggers coded above have already removed the row
+    ** being deleted. Do not attempt to delete the row a second time, and 
+    ** do not fire AFTER triggers.  */
+    sqlite3VdbeAddOp3(v, OP_NotExists, iCur, iLabel, iRowid);
+  }
+
+  /* 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).  */ 
+  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);
+    }
   }
-  sqlite3VdbeJumpHere(v, addr);
+
+  /* Invoke AFTER triggers. */
+  if( pTrigger ){
+    sqlite3CodeRowTrigger(pParse, pTrigger, 
+        TK_DELETE, 0, TRIGGER_AFTER, pTab, -1, iOld, onconf, iLabel
+    );
+  }
+
+  /* Jump here if the row had already been deleted before any BEFORE
+  ** trigger programs were invoked. Or if a trigger program throws a 
+  ** RAISE(IGNORE) exception.  */
+  sqlite3VdbeResolveLabel(v, iLabel);
 }
 
 /*
index 3eba2612ec83d90342e3305731ac639211940765..a1db400e1eb0e2a255cf06b7e426316ddb9c78d0 100644 (file)
@@ -1241,7 +1241,26 @@ void sqlite3GenerateConstraintChecks(
           break;
         }
         case OE_Replace: {
-          sqlite3GenerateRowIndexDelete(pParse, pTab, baseCur, 0);
+          /* If there are DELETE triggers on this table and the
+          ** recursive-triggers flag is set, call GenerateRowDelete() to
+          ** remove the conflicting row from the the table. This will fire
+          ** the triggers and remove both the table and index b-tree entries.
+          **
+          ** Otherwise, if there are no triggers or the recursive-triggers
+          ** flag is not set, call GenerateRowIndexDelete(). This removes
+          ** the index b-tree entries only. The table b-tree entry will be 
+          ** replaced by the new entry when it is inserted.  */
+          Trigger *pTrigger = 0;
+          if( pParse->db->flags&SQLITE_RecTriggers ){
+            pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
+          }
+          if( pTrigger ){
+            sqlite3GenerateRowDelete(
+                pParse, pTab, baseCur, regRowid, 0, pTrigger, OE_Replace
+            );
+          }else{
+            sqlite3GenerateRowIndexDelete(pParse, pTab, baseCur, 0);
+          }
           seenReplace = 1;
           break;
         }
@@ -1299,7 +1318,6 @@ void sqlite3GenerateConstraintChecks(
       else if( onError==OE_Fail ) onError = OE_Abort;
     }
     
-
     /* Check to see if the new index entry will be unique */
     regR = sqlite3GetTempReg(pParse);
     sqlite3VdbeAddOp2(v, OP_SCopy, regOldRowid, regR);
@@ -1342,8 +1360,14 @@ void sqlite3GenerateConstraintChecks(
         break;
       }
       default: {
+        Trigger *pTrigger = 0;
         assert( onError==OE_Replace );
-        sqlite3GenerateRowDelete(pParse, pTab, baseCur, regR, 0);
+        if( pParse->db->flags&SQLITE_RecTriggers ){
+          pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
+        }
+        sqlite3GenerateRowDelete(
+            pParse, pTab, baseCur, regR, 0, pTrigger, OE_Replace
+        );
         seenReplace = 1;
         break;
       }
index 6cfe702e38faed5d890d51590375e062afb88129..45742738090896321a890d2e5086682b836c3cb8 100644 (file)
@@ -2647,7 +2647,7 @@ int sqlite3ExprIsConstantNotJoin(Expr*);
 int sqlite3ExprIsConstantOrFunction(Expr*);
 int sqlite3ExprIsInteger(Expr*, int*);
 int sqlite3IsRowid(const char*);
-void sqlite3GenerateRowDelete(Parse*, Table*, int, int, int);
+void sqlite3GenerateRowDelete(Parse*, Table*, int, int, int, Trigger *, int);
 void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int*);
 int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int);
 void sqlite3GenerateConstraintChecks(Parse*,Table*,int,int,
index 08507be77762ab00aa2b3c90eeb07febd0d7bb4a..426d6108c8f3a32033127195f7e359694c2111af 100644 (file)
@@ -17,6 +17,24 @@ ifcapable {!trigger} {
   return
 }
 
+#-------------------------------------------------------------------------
+# Test organization:
+#
+# triggerC-1.*: Haphazardly designed trigger related tests that were useful
+#               during an upgrade of the triggers sub-system.
+# 
+# triggerC-2.*:
+#
+# triggerC-3.*:
+#
+# triggerC-4.*:
+#
+# triggerC-5.*: Test that when recursive triggers are enabled DELETE 
+#               triggers are fired when rows are deleted as part of OR
+#               REPLACE conflict resolution. And that they are not fired
+#               if recursive triggers are not enabled.
+#
+
 # Enable recursive triggers for this file.
 #
 execsql { PRAGMA recursive_triggers = on }
@@ -531,4 +549,116 @@ foreach {n insert log} {
   } [join $log " "]
 } 
 
+#-------------------------------------------------------------------------
+# This block of tests, triggerC-5.*, test that DELETE triggers are fired
+# if a row is deleted as a result of OR REPLACE conflict resolution.
+#
+do_test triggerC-5.1.0 {
+  execsql {
+    DROP TABLE IF EXISTS t5;
+    CREATE TABLE t5(a INTEGER PRIMARY KEY, b);
+    CREATE UNIQUE INDEX t5i ON t5(b);
+    INSERT INTO t5 VALUES(1, 'a');
+    INSERT INTO t5 VALUES(2, 'b');
+    INSERT INTO t5 VALUES(3, 'c');
+
+    CREATE TABLE t5g(a, b, c);
+    CREATE TRIGGER t5t BEFORE DELETE ON t5 BEGIN
+      INSERT INTO t5g VALUES(old.a, old.b, (SELECT count(*) FROM t5));
+    END;
+  }
+} {}
+foreach {n dml t5g t5} {
+  1 "DELETE FROM t5 WHERE a=2"                        {2 b 3} {1 a 3 c}
+  2 "INSERT OR REPLACE INTO t5 VALUES(2, 'd')"        {2 b 3} {1 a 2 d 3 c}
+  3 "UPDATE OR REPLACE t5 SET a = 2 WHERE a = 3"      {2 b 3} {1 a 2 c}
+  4 "INSERT OR REPLACE INTO t5 VALUES(4, 'b')"        {2 b 3} {1 a 3 c 4 b}
+  5 "UPDATE OR REPLACE t5 SET b = 'b' WHERE b = 'c'"  {2 b 3} {1 a 3 b}
+  6 "INSERT OR REPLACE INTO t5 VALUES(2, 'c')"        {2 b 3 3 c 2} {1 a 2 c}
+  7 "UPDATE OR REPLACE t5 SET a=1, b='b' WHERE a = 3" {1 a 3 2 b 2} {1 b}
+} {
+  do_test triggerC-5.1.$n {
+    execsql "
+      BEGIN;
+        $dml ;
+        SELECT * FROM t5g;
+        SELECT * FROM t5;
+      ROLLBACK;
+    "
+  } [concat $t5g $t5]
+}
+do_test triggerC-5.2.0 {
+  execsql {
+    DROP TRIGGER t5t;
+    CREATE TRIGGER t5t AFTER DELETE ON t5 BEGIN
+      INSERT INTO t5g VALUES(old.a, old.b, (SELECT count(*) FROM t5));
+    END;
+  }
+} {}
+foreach {n dml t5g t5} {
+  1 "DELETE FROM t5 WHERE a=2"                        {2 b 2} {1 a 3 c}
+  2 "INSERT OR REPLACE INTO t5 VALUES(2, 'd')"        {2 b 2} {1 a 2 d 3 c}
+  3 "UPDATE OR REPLACE t5 SET a = 2 WHERE a = 3"      {2 b 2} {1 a 2 c}
+  4 "INSERT OR REPLACE INTO t5 VALUES(4, 'b')"        {2 b 2} {1 a 3 c 4 b}
+  5 "UPDATE OR REPLACE t5 SET b = 'b' WHERE b = 'c'"  {2 b 2} {1 a 3 b}
+  6 "INSERT OR REPLACE INTO t5 VALUES(2, 'c')"        {2 b 2 3 c 1} {1 a 2 c}
+  7 "UPDATE OR REPLACE t5 SET a=1, b='b' WHERE a = 3" {1 a 2 2 b 1} {1 b}
+} {
+  do_test triggerC-5.2.$n {
+    execsql "
+      BEGIN;
+        $dml ;
+        SELECT * FROM t5g;
+        SELECT * FROM t5;
+      ROLLBACK;
+    "
+  } [concat $t5g $t5]
+}
+do_test triggerC-5.3.0 {
+  execsql { PRAGMA recursive_triggers = off }
+} {}
+foreach {n dml t5g t5} {
+  1 "DELETE FROM t5 WHERE a=2"                        {2 b 2} {1 a 3 c}
+  2 "INSERT OR REPLACE INTO t5 VALUES(2, 'd')"        {} {1 a 2 d 3 c}
+  3 "UPDATE OR REPLACE t5 SET a = 2 WHERE a = 3"      {} {1 a 2 c}
+  4 "INSERT OR REPLACE INTO t5 VALUES(4, 'b')"        {} {1 a 3 c 4 b}
+  5 "UPDATE OR REPLACE t5 SET b = 'b' WHERE b = 'c'"  {} {1 a 3 b}
+  6 "INSERT OR REPLACE INTO t5 VALUES(2, 'c')"        {} {1 a 2 c}
+  7 "UPDATE OR REPLACE t5 SET a=1, b='b' WHERE a = 3" {} {1 b}
+} {
+  do_test triggerC-5.3.$n {
+    execsql "
+      BEGIN;
+        $dml ;
+        SELECT * FROM t5g;
+        SELECT * FROM t5;
+      ROLLBACK;
+    "
+  } [concat $t5g $t5]
+}
+do_test triggerC-5.3.8 {
+  execsql { PRAGMA recursive_triggers = on }
+} {}
+
+#-------------------------------------------------------------------------
+# This block of tests, triggerC-6.*, tests that "PRAGMA recursive_triggers"
+# statements return the current value of the recursive triggers flag.
+#
+do_test triggerC-6.1 {
+  execsql { PRAGMA recursive_triggers }
+} {1}
+do_test triggerC-6.2 {
+  execsql { 
+    PRAGMA recursive_triggers = off;
+    PRAGMA recursive_triggers;
+  }
+} {0}
+do_test triggerC-6.3 {
+  execsql { 
+    PRAGMA recursive_triggers = on;
+    PRAGMA recursive_triggers;
+  }
+} {1}
+
+
 finish_test