Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -8096,13 +8096,16 @@ ** the cursor is left pointing at an arbitrary location after the delete. ** But if that bit is set, then the cursor is left in a state such that ** the next call to BtreeNext() or BtreePrev() moves it to the same row ** as it would have been on if the call to BtreeDelete() had been omitted. ** -** The BTREE_IDXDELETE bit of flags indicates that this is a delete of -** an index entry where the corresponding table row has already been deleted. -** The BTREE_IDXDELETE bit is a hint that is not used by this implementation, +** The BTREE_AUXDELETE bit of flags indicates that is one of several deletes +** associated with a single table entry and its indexes. Only one of those +** deletes is considered the "primary" delete. The primary delete occurs +** on a cursor that is not a BTREE_FORDELETE cursor. All but one delete +** operation on non-FORDELETE cursors is tagged with the AUXDELETE flag. +** The BTREE_AUXDELETE bit is a hint that is not used by this implementation, ** but which might be used by alternative storage engines. */ int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ Btree *p = pCur->pBtree; BtShared *pBt = p->pBt; @@ -8121,11 +8124,11 @@ assert( pCur->curFlags & BTCF_WriteFlag ); assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); assert( !hasReadConflicts(p, pCur->pgnoRoot) ); assert( pCur->aiIdx[pCur->iPage]apPage[pCur->iPage]->nCell ); assert( pCur->eState==CURSOR_VALID ); - assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_IDXDELETE))==0 ); + assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 ); iCellDepth = pCur->iPage; iCellIdx = pCur->aiIdx[iCellDepth]; pPage = pCur->apPage[iCellDepth]; pCell = findCell(pPage, iCellIdx); Index: src/btree.h ================================================================== --- src/btree.h +++ src/btree.h @@ -247,11 +247,11 @@ int sqlite3BtreeCursorRestore(BtCursor*, int*); int sqlite3BtreeDelete(BtCursor*, u8 flags); /* Allowed flags for the 2nd argument to sqlite3BtreeDelete() */ #define BTREE_SAVEPOSITION 0x02 /* Leave cursor pointing at NEXT or PREV */ -#define BTREE_IDXDELETE 0x04 /* this is index, table row already deleted */ +#define BTREE_AUXDELETE 0x04 /* not the primary delete operation */ int sqlite3BtreeInsert(BtCursor*, const void *pKey, i64 nKey, const void *pData, int nData, int nZero, int bias, int seekResult); int sqlite3BtreeFirst(BtCursor*, int *pRes); Index: src/delete.c ================================================================== --- src/delete.c +++ src/delete.c @@ -477,17 +477,16 @@ ** only effect this statement has is to fire the INSTEAD OF ** triggers. */ if( !isView ){ int iAddrOnce = 0; - u8 p5 = (eOnePass==ONEPASS_OFF ? 0 : OPFLAG_FORDELETE); if( eOnePass==ONEPASS_MULTI ){ iAddrOnce = sqlite3CodeOnce(pParse); VdbeCoverage(v); } testcase( IsVirtual(pTab) ); - sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, p5, iTabCur, - aToOpen, &iDataCur, &iIdxCur); + sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, OPFLAG_FORDELETE, + iTabCur, aToOpen, &iDataCur, &iIdxCur); assert( pPk || IsVirtual(pTab) || iDataCur==iTabCur ); assert( pPk || IsVirtual(pTab) || iIdxCur==iDataCur+1 ); if( eOnePass==ONEPASS_MULTI ) sqlite3VdbeJumpHere(v, iAddrOnce); } @@ -722,13 +721,15 @@ sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); if( count ){ sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_TRANSIENT); } + if( eMode!=ONEPASS_OFF ){ + sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE); + } if( iIdxNoSeek>=0 ){ sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek); - if( eMode!=ONEPASS_OFF ) p5 = OPFLAG_IDXDELETE; } if( eMode==ONEPASS_MULTI ) p5 |= OPFLAG_SAVEPOSITION; sqlite3VdbeChangeP5(v, p5); } Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -1645,11 +1645,11 @@ */ int sqlite3OpenTableAndIndices( Parse *pParse, /* Parsing context */ Table *pTab, /* Table to be opened */ int op, /* OP_OpenRead or OP_OpenWrite */ - u8 p5, /* P5 value for OP_Open* instructions */ + u8 p5, /* P5 value for OP_Open* opcodes (except on WITHOUT ROWID) */ int iBase, /* Use this for the table cursor, if there is one */ u8 *aToOpen, /* If not NULL: boolean for each table and index */ int *piDataCur, /* Write the database source cursor number here */ int *piIdxCur /* Write the first index cursor number here */ ){ @@ -1680,18 +1680,19 @@ } if( piIdxCur ) *piIdxCur = iBase; for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ int iIdxCur = iBase++; assert( pIdx->pSchema==pTab->pSchema ); - if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) && piDataCur ){ - *piDataCur = iIdxCur; - } if( aToOpen==0 || aToOpen[i+1] ){ sqlite3VdbeAddOp3(v, op, iIdxCur, pIdx->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); - sqlite3VdbeChangeP5(v, p5); VdbeComment((v, "%s", pIdx->zName)); + } + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ + if( piDataCur ) *piDataCur = iIdxCur; + }else{ + sqlite3VdbeChangeP5(v, p5); } } if( iBase>pParse->nTab ) pParse->nTab = iBase; return i; } Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -2860,11 +2860,11 @@ #define OPFLAG_SEEKEQ 0x02 /* OP_Open** cursor uses EQ seek only */ #define OPFLAG_FORDELETE 0x08 /* OP_Open should use BTREE_FORDELETE */ #define OPFLAG_P2ISREG 0x10 /* P2 to OP_Open** is a register number */ #define OPFLAG_PERMUTE 0x01 /* OP_Compare: use the permutation */ #define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete: keep cursor position */ -#define OPFLAG_IDXDELETE 0x04 /* OP_Delete: index in a DELETE op */ +#define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */ /* * Each trigger present in the database schema is stored as an instance of * struct Trigger. * Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -550,10 +550,13 @@ Op *aOp = p->aOp; /* Copy of p->aOp */ Op *pOp = aOp; /* Current operation */ #if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) Op *pOrigOp; /* Value of pOp at the top of the loop */ #endif +#ifdef SQLITE_DEBUG + int nExtraDelete = 0; /* Verifies FORDELETE and AUXDELETE flags */ +#endif int rc = SQLITE_OK; /* Value to return */ sqlite3 *db = p->db; /* The database */ u8 resetSchemaOnFault = 0; /* Reset schema after an error if positive */ u8 encoding = ENC(db); /* The database encoding */ int iCompare = 0; /* Result of last OP_Compare operation */ @@ -3390,10 +3393,13 @@ pCur = allocateCursor(p, pOp->p1, nField, iDb, CURTYPE_BTREE); if( pCur==0 ) goto no_mem; pCur->nullRow = 1; pCur->isOrdered = 1; pCur->pgnoRoot = p2; +#ifdef SQLITE_DEBUG + pCur->wrFlag = wrFlag; +#endif rc = sqlite3BtreeCursor(pX, p2, wrFlag, pKeyInfo, pCur->uc.pCursor); pCur->pKeyInfo = pKeyInfo; /* Set the VdbeCursor.isTable variable. Previous versions of ** SQLite used to check if the root-page flags were sane at this point ** and report database corruption if they were not, but this check has @@ -4347,13 +4353,15 @@ ** the next Next instruction will be a no-op. As a result, in this case ** it is ok to delete a record from within a Next loop. If ** OPFLAG_SAVEPOSITION bit of P5 is clear, then the cursor will be ** left in an undefined state. ** -** If the OPFLAG_IDXDELETE bit is set on P5, that indicates that this -** delete is on an index cursor where the corresponding table row has -** already been deleted. +** If the OPFLAG_AUXDELETE bit is set on P5, that indicates that this +** delete one of several associated with deleting a table row and all its +** associated index entries. Exactly one of those deletes is the "primary" +** delete. The others are all on OPFLAG_FORDELETE cursors or else are +** marked with the AUXDELETE flag. ** ** If the OPFLAG_NCHANGE flag of P2 (NB: P2 not P5) is set, then the row ** change count is incremented (otherwise not). ** ** P1 must not be pseudo-table. It has to be a real table with @@ -4388,14 +4396,30 @@ i64 iKey = 0; sqlite3BtreeKeySize(pC->uc.pCursor, &iKey); assert( pC->movetoTarget==iKey ); } #endif - - assert( (pOp->p5 & ~(OPFLAG_SAVEPOSITION|OPFLAG_IDXDELETE))==0 ); + + /* Only flags that can be set are SAVEPOISTION and AUXDELETE */ + assert( (pOp->p5 & ~(OPFLAG_SAVEPOSITION|OPFLAG_AUXDELETE))==0 ); assert( OPFLAG_SAVEPOSITION==BTREE_SAVEPOSITION ); - assert( OPFLAG_IDXDELETE==BTREE_IDXDELETE ); + assert( OPFLAG_AUXDELETE==BTREE_AUXDELETE ); + +#ifdef SQLITE_DEBUG + if( p->pFrame==0 ){ + if( pC->isEphemeral==0 + && (pOp->p5 & OPFLAG_AUXDELETE)==0 + && (pC->wrFlag & OPFLAG_FORDELETE)==0 + ){ + nExtraDelete++; + } + if( pOp->p2 & OPFLAG_NCHANGE ){ + nExtraDelete--; + } + } +#endif + rc = sqlite3BtreeDelete(pC->uc.pCursor, pOp->p5); pC->cacheStatus = CACHE_STALE; /* Invoke the update-hook if required. */ if( rc==SQLITE_OK && hasUpdateCallback ){ @@ -4936,16 +4960,13 @@ assert( pOp->p5==0 ); r.pKeyInfo = pC->pKeyInfo; r.nField = (u16)pOp->p3; r.default_rc = 0; r.aMem = &aMem[pOp->p2]; -#ifdef SQLITE_DEBUG - { int i; for(i=0; ideferredMoveto==0 ); pC->cacheStatus = CACHE_STALE; break; } @@ -6760,10 +6781,13 @@ vdbe_return: db->lastRowid = lastRowid; testcase( nVmStep>0 ); p->aCounter[SQLITE_STMTSTATUS_VM_STEP] += (int)nVmStep; sqlite3VdbeLeave(p); + assert( rc!=SQLITE_OK || nExtraDelete==0 + || sqlite3_strlike("DELETE%",p->zSql,0)!=0 + ); return rc; /* Jump to here if a string or blob larger than SQLITE_MAX_LENGTH ** is encountered. */ Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -80,10 +80,11 @@ u8 nullRow; /* True if pointing to a row with no data */ u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ u8 isTable; /* True for rowid tables. False for indexes */ #ifdef SQLITE_DEBUG u8 seekOp; /* Most recent seek operation on this cursor */ + u8 wrFlag; /* The wrFlag argument to sqlite3BtreeCursor() */ #endif Bool isEphemeral:1; /* True for an ephemeral table */ Bool useRandomRowid:1;/* Generate new record numbers semi-randomly */ Bool isOrdered:1; /* True if the underlying table is BTREE_UNORDERED */ Pgno pgnoRoot; /* Root page of the open btree cursor */ Index: test/fordelete.test ================================================================== --- test/fordelete.test +++ test/fordelete.test @@ -28,20 +28,52 @@ db eval { SELECT name, rootpage FROM sqlite_master } { set T($rootpage) $name } + + # For each OpenWrite instruction generated for the proposed DELETE + # statement, add the following array entries: + # + # $M() -> + # $O() -> "*" | "" + # + # The O() entry is set to "*" if the BTREE_FORDELETE flag is specified, + # or "" otherwise. + # + db eval "EXPLAIN $sql" R { + if {$R(opcode)=="OpenWrite"} { + set root $R(p2) + set csr $R(p1) + if {[info exists T($root)]} { set M($csr) $T($root) } - # Calculate the results. - set res [list] + set obj $T($root) + set O($obj) "" + if {"0x$R(p5)" & 0x08} { + set O($obj) * + } else { + set O($obj) "" + } + } + } + db eval "EXPLAIN $sql" R { - if {$R(opcode) == "OpenWrite"} { - set obj $T($R(p2)) - if {"0x$R(p5)" & 0x08} { append obj *} - lappend res $obj + if {$R(opcode) == "Delete"} { + set csr $R(p1) + if {[info exists M($csr)]} { + set idxdelete [expr {("0x$R(p5)" & 0x04) ? 1 : 0}] + if {$idxdelete} { + append O($M($csr)) "+" + } + } } } + + set res [list] + foreach {k v} [array get O] { + lappend res "${k}${v}" + } lsort $res } proc do_adp_test {tn sql res} { @@ -51,14 +83,14 @@ do_execsql_test 1.0 { CREATE TABLE t1(a PRIMARY KEY, b); } foreach {tn sql res} { - 1 { DELETE FROM t1 WHERE a=?} { sqlite_autoindex_t1_1 t1* } - 2 { DELETE FROM t1 WHERE a=? AND b=? } { sqlite_autoindex_t1_1 t1 } - 3 { DELETE FROM t1 WHERE a>? } { sqlite_autoindex_t1_1 t1* } - 4 { DELETE FROM t1 WHERE rowid=? } { sqlite_autoindex_t1_1* t1 } + 1 { DELETE FROM t1 WHERE a=?} { sqlite_autoindex_t1_1 t1*+ } + 2 { DELETE FROM t1 WHERE a=? AND b=? } { sqlite_autoindex_t1_1 t1+ } + 3 { DELETE FROM t1 WHERE a>? } { sqlite_autoindex_t1_1 t1*+ } + 4 { DELETE FROM t1 WHERE rowid=? } { sqlite_autoindex_t1_1* t1 } } { do_adp_test 1.$tn $sql $res } do_execsql_test 2.0 { @@ -66,12 +98,12 @@ CREATE INDEX t2a ON t2(a); CREATE INDEX t2b ON t2(b); CREATE INDEX t2c ON t2(c); } foreach {tn sql res} { - 1 { DELETE FROM t2 WHERE a=?} { t2* t2a t2b* t2c* } - 2 { DELETE FROM t2 WHERE a=? AND +b=?} { t2 t2a t2b* t2c* } + 1 { DELETE FROM t2 WHERE a=?} { t2*+ t2a t2b* t2c* } + 2 { DELETE FROM t2 WHERE a=? AND +b=?} { t2+ t2a t2b* t2c* } 3 { DELETE FROM t2 WHERE a=? OR b=?} { t2 t2a* t2b* t2c* } 4 { DELETE FROM t2 WHERE +a=? } { t2 t2a* t2b* t2c* } 5 { DELETE FROM t2 WHERE rowid=? } { t2 t2a* t2b* t2c* } } { do_adp_test 2.$tn $sql $res @@ -123,8 +155,56 @@ db eval { SELECT * FROM x2; } } {6 {} {} {}} + + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 4.0 { + CREATE TABLE log(x); + CREATE TABLE p1(one PRIMARY KEY, two); + + CREATE TRIGGER tr_bd BEFORE DELETE ON p1 BEGIN + INSERT INTO log VALUES('delete'); + END; + INSERT INTO p1 VALUES('a', 'A'), ('b', 'B'), ('c', 'C'); + DELETE FROM p1 WHERE one = 'a'; +} + +reset_db +do_execsql_test 4.1 { + BEGIN TRANSACTION; + CREATE TABLE tbl(a PRIMARY KEY, b, c); + CREATE TABLE log(a, b, c); + INSERT INTO "tbl" VALUES(1,2,3); + CREATE TRIGGER the_trigger BEFORE DELETE ON tbl BEGIN + INSERT INTO log VALUES(1, 2,3); + END; + COMMIT; + DELETE FROM tbl WHERE a=1; +} + +reset_db +do_execsql_test 5.1 { + PRAGMA foreign_keys = 1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + CREATE TABLE t2( + c INTEGER PRIMARY KEY, + d INTEGER DEFAULT 1 REFERENCES t1 ON DELETE SET DEFAULT + ); +} {} +do_execsql_test 5.2 { + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + INSERT INTO t2 VALUES(1, 2); + SELECT * FROM t2; +} {1 2} +do_execsql_test 5.3 { + DELETE FROM t1 WHERE a = 2; +} {} + finish_test