If recursive-triggers are enabled, fire DELETE triggers if database rows are removed as a result of OR REPLACE conflict resolution.

FossilOrigin-Name: 85cb0c94a63eda5f059ebe40887c7af9b4869893
This commit is contained in:
dan 2009-09-08 15:55:15 +00:00
parent 345ba7db59
commit 2283d46cd6
6 changed files with 255 additions and 90 deletions

View File

@ -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

View File

@ -1 +1 @@
4ab8c841f818326b0b04b95e3edd828c77f109d9
85cb0c94a63eda5f059ebe40887c7af9b4869893

View 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;
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; 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);
}
sqlite3VdbeJumpHere(v, addr);
/* 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);
}
}
/* 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);
}
/*

View 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;
}

View 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,

View 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