diff --git a/manifest b/manifest index 5d45505343..29b6ef18a2 100644 --- 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 diff --git a/manifest.uuid b/manifest.uuid index 10a03f931e..15344aef71 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4ab8c841f818326b0b04b95e3edd828c77f109d9 \ No newline at end of file +85cb0c94a63eda5f059ebe40887c7af9b4869893 \ No newline at end of file diff --git a/src/delete.c b/src/delete.c index ae4a8b5846..f0ca8ab471 100644 --- a/src/delete.c +++ b/src/delete.c @@ -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; inCol; i++){ - if( mask==0xffffffff || mask&(1<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; + Vdbe *v = pParse->pVdbe; /* Vdbe */ + int iOld; /* First register in OLD.* array */ + int iLabel; /* Label resolved to end of generated code */ - 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 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; iColnCol; iCol++){ + if( mask==0xffffffff || mask&(1<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); + } + } + + /* 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); } /* diff --git a/src/insert.c b/src/insert.c index 3eba2612ec..a1db400e1e 100644 --- a/src/insert.c +++ b/src/insert.c @@ -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; } diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 6cfe702e38..4574273809 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -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, diff --git a/test/triggerC.test b/test/triggerC.test index 08507be777..426d6108c8 100644 --- a/test/triggerC.test +++ b/test/triggerC.test @@ -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