------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
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
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
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
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
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
#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,
{
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.
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);
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.
*/
** 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);
}
/*
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;
}
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);
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;
}
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 }
} [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