Index: src/delete.c ================================================================== --- src/delete.c +++ src/delete.c @@ -540,11 +540,11 @@ ** pre-update-hook is. */ if( pTab->pSelect==0 ){ sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, 0); sqlite3VdbeAddOp2(v, OP_Delete, iCur, (count?OPFLAG_NCHANGE:0)); - sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC); + sqlite3VdbeChangeP4(v, -1, pTab, P4_TABLE); } /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key ** to the row just deleted. */ Index: src/insert.c ================================================================== --- src/insert.c +++ src/insert.c @@ -1289,11 +1289,11 @@ /* This OP_Delete opcode fires the pre-update-hook only. It does ** not modify the b-tree. It is more efficient to let the coming ** OP_Insert replace the existing entry than it is to delete the ** existing entry and then insert a new one. */ sqlite3VdbeAddOp2(v, OP_Delete, baseCur, OPFLAG_ISNOOP); - sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC); + sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE); if( pTab->pIndex ){ sqlite3MultiWrite(pParse); sqlite3GenerateRowIndexDelete(pParse, pTab, baseCur, 0); } } @@ -1479,11 +1479,11 @@ if( useSeekResult ){ pik_flags |= OPFLAG_USESEEKRESULT; } sqlite3VdbeAddOp3(v, OP_Insert, baseCur, regRec, regRowid); if( !pParse->nested ){ - sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC); + sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE); } sqlite3VdbeChangeP5(v, pik_flags); } /* @@ -1801,11 +1801,11 @@ assert( (pDest->tabFlags & TF_Autoincrement)==0 ); } sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData); sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid); sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND); - sqlite3VdbeChangeP4(v, -1, pDest->zName, 0); + sqlite3VdbeChangeP4(v, -1, (char *)pDest, P4_TABLE); sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); for(pDestIdx=pDest->pIndex; pDestIdx; pDestIdx=pDestIdx->pNext){ for(pSrcIdx=pSrc->pIndex; ALWAYS(pSrcIdx); pSrcIdx=pSrcIdx->pNext){ if( xferCompatibleIndex(pDestIdx, pSrcIdx) ) break; } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -501,11 +501,11 @@ sqlite3VdbeAddOp3(v, OP_Delete, iCur, OPFLAG_ISUPDATE | ((hasFK || chngRowid) ? 0 : OPFLAG_ISNOOP), regNewRowid ); if( !pParse->nested ){ - sqlite3VdbeChangeP4(v, -1, pTab->zName, P4_STATIC); + sqlite3VdbeChangeP4(v, -1, pTab, P4_TABLE); } sqlite3VdbeJumpHere(v, j1); if( hasFK ){ sqlite3FkCheck(pParse, pTab, 0, regNewRowid); Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -3836,13 +3836,13 @@ ** If the OPFLAG_ISUPDATE flag is set, then this opcode is part of an ** UPDATE operation. Otherwise (if the flag is clear) then this opcode ** is part of an INSERT operation. The difference is only important to ** the update hook. ** -** Parameter P4 may point to a string containing the table-name, or -** may be NULL. If it is not NULL, then the update-hook -** (sqlite3.xUpdateCallback) is invoked following a successful insert. +** Parameter P4 may point to a Table structure, or may be NULL. If it is +** not NULL, then the update-hook (sqlite3.xUpdateCallback) is invoked +** following a successful insert. ** ** (WARNING/TODO: If P1 is a pseudo-cursor and P2 is dynamically ** allocated, then ownership of P2 is transferred to the pseudo-cursor ** and register P2 becomes ephemeral. If the cursor is changed, the ** value of register P2 will then change. Make sure this does not @@ -3863,11 +3863,11 @@ i64 iKey; /* The integer ROWID or key for the record to be inserted */ VdbeCursor *pC; /* Cursor to table into which insert is written */ int nZero; /* Number of zero-bytes to append */ int seekResult; /* Result of prior seek or 0 if no USESEEKRESULT flag */ const char *zDb; /* database name - used by the update hook */ - const char *zTbl; /* Table name - used by the opdate hook */ + Table *pTab; /* Table structure - used by update and pre-update hooks */ int op; /* Opcode for update hook: SQLITE_UPDATE or SQLITE_INSERT */ pData = &aMem[pOp->p2]; assert( pOp->p1>=0 && pOp->p1nCursor ); assert( memIsValid(pData) ); @@ -3874,10 +3874,11 @@ pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->pCursor!=0 ); assert( pC->pseudoTableReg==0 ); assert( pC->isTable ); + assert( pOp->p4type==P4_TABLE || pOp->p4type==P4_NOTUSED ); REGISTER_TRACE(pOp->p2, pData); if( pOp->opcode==OP_Insert ){ pKey = &aMem[pOp->p3]; assert( pKey->flags & MEM_Int ); @@ -3887,24 +3888,24 @@ }else{ assert( pOp->opcode==OP_InsertInt ); iKey = pOp->p3; } - if( pOp->p4.z && (db->xPreUpdateCallback || db->xUpdateCallback) ){ + if( pOp->p4type==P4_TABLE && (db->xPreUpdateCallback||db->xUpdateCallback) ){ assert( pC->isTable ); assert( pC->iDb>=0 ); zDb = db->aDb[pC->iDb].zName; - zTbl = pOp->p4.z; + pTab = pOp->p4.pTab; op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT); } /* Invoke the pre-update hook, if any */ if( db->xPreUpdateCallback - && pOp->p4.z + && pOp->p4type==P4_TABLE && (!(pOp->p5 & OPFLAG_ISUPDATE) || pC->rowidIsValid==0) ){ - sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, zTbl, iKey, pOp->p2); + sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, iKey, pOp->p2); } if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = iKey; if( pData->flags & MEM_Null ){ @@ -3928,11 +3929,11 @@ pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; /* Invoke the update-hook if required. */ if( rc==SQLITE_OK && db->xUpdateCallback && pOp->p4.z ){ - db->xUpdateCallback(db->pUpdateArg, op, zDb, zTbl, iKey); + db->xUpdateCallback(db->pUpdateArg, op, zDb, pTab->zName, iKey); } break; } /* Opcode: Delete P1 P2 P3 P4 * @@ -3948,34 +3949,35 @@ ** incremented (otherwise not). ** ** P1 must not be pseudo-table. It has to be a real table with ** multiple rows. ** -** If P4 is not NULL then, either the update or pre-update hook, or both, -** may be invoked. The P1 cursor must have been positioned using OP_NotFound -** prior to invoking this opcode in this case. Specifically, if one is -** configured, the pre-update hook is invoked if P4 is not NULL. The -** update-hook is invoked if one is configured, P4 is not NULL, and the -** OPFLAG_NCHANGE flag is set in P2. +** If P4 is not NULL then it points to a Table struture. In this case either +** the update or pre-update hook, or both, may be invoked. The P1 cursor must +** have been positioned using OP_NotFound prior to invoking this opcode in +** this case. Specifically, if one is configured, the pre-update hook is +** invoked if P4 is not NULL. The update-hook is invoked if one is configured, +** P4 is not NULL, and the OPFLAG_NCHANGE flag is set in P2. ** ** If the OPFLAG_ISUPDATE flag is set in P2, then P3 contains the address ** of the memory cell that contains the value that the rowid of the row will ** be set to by the update. */ case OP_Delete: { i64 iKey; VdbeCursor *pC; const char *zDb; - const char *zTbl; + Table *pTab; int opflags; opflags = pOp->p2; iKey = 0; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->pCursor!=0 ); /* Only valid for real tables, no pseudotables */ + assert( pOp->p4type==P4_TABLE || pOp->p4type==P4_NOTUSED ); /* The OP_Delete opcode always follows an OP_NotExists or OP_Last or ** OP_Column on the same table without any intervening operations that ** might move or invalidate the cursor. Hence cursor pC is always pointing ** to the row to be deleted and the sqlite3VdbeCursorMoveto() operation @@ -3993,19 +3995,19 @@ assert( pC->iDb>=0 ); assert( pC->isTable ); assert( pC->rowidIsValid ); /* lastRowid set by previous OP_NotFound */ iKey = pC->lastRowid; zDb = db->aDb[pC->iDb].zName; - zTbl = pOp->p4.z; + pTab = pOp->p4.pTab; } /* Invoke the pre-update-hook if required. */ if( db->xPreUpdateCallback && pOp->p4.z ){ assert( !(opflags & OPFLAG_ISUPDATE) || (aMem[pOp->p3].flags & MEM_Int) ); sqlite3VdbePreUpdateHook(p, pC, (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, - zDb, zTbl, iKey, + zDb, pTab, iKey, pOp->p3 ); } if( opflags & OPFLAG_ISNOOP ) break; @@ -4017,11 +4019,11 @@ /* Update the change-counter and invoke the update-hook if required. */ if( opflags & OPFLAG_NCHANGE ){ p->nChange++; assert( pOp->p4.z ); if( rc==SQLITE_OK && db->xUpdateCallback ){ - db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, zTbl, iKey); + db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, pTab->zName,iKey); } } break; } /* Opcode: ResetCount * * * * * Index: src/vdbe.h ================================================================== --- src/vdbe.h +++ src/vdbe.h @@ -59,10 +59,11 @@ Mem *pMem; /* Used when p4type is P4_MEM */ VTable *pVtab; /* Used when p4type is P4_VTAB */ KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */ int *ai; /* Used when p4type is P4_INTARRAY */ SubProgram *pProgram; /* Used when p4type is P4_SUBPROGRAM */ + Table *pTab; /* Used when p4type is P4_TABLE */ } p4; #ifdef SQLITE_DEBUG char *zComment; /* Comment to improve readability */ #endif #ifdef VDBE_PROFILE @@ -114,10 +115,11 @@ #define P4_REAL (-12) /* P4 is a 64-bit floating point value */ #define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ #define P4_INT32 (-14) /* P4 is a 32-bit signed integer */ #define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ #define P4_SUBPROGRAM (-18) /* P4 is a pointer to a SubProgram structure */ +#define P4_TABLE (-19) /* P4 is a pointer to a Table structure */ /* When adding a P4 argument using P4_KEYINFO, a copy of the KeyInfo structure ** is made. That copy is freed when the Vdbe is finalized. But if the ** argument is P4_KEYINFO_HANDOFF, the passed in pointer is used. It still ** gets freed when the Vdbe is finalized so it still should be obtained Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -342,10 +342,13 @@ u8 *aRecord; /* old.* database record */ KeyInfo keyinfo; UnpackedRecord *pUnpacked; /* Unpacked version of aRecord[] */ UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */ int iNewReg; /* Register for new.* values */ + i64 iKey1; /* First key value passed to hook */ + i64 iKey2; /* Second key value passed to hook */ + int iPKey; /* If not negative index of IPK column */ Mem *aNew; /* Array of new.* values */ }; /* ** Function prototypes @@ -402,11 +405,11 @@ int sqlite3VdbeCloseStatement(Vdbe *, int); void sqlite3VdbeFrameDelete(VdbeFrame*); int sqlite3VdbeFrameRestore(VdbeFrame *); void sqlite3VdbeMemStoreType(Mem *pMem); void sqlite3VdbePreUpdateHook( - Vdbe *, VdbeCursor *, int, const char*, const char*, i64, int); + Vdbe *, VdbeCursor *, int, const char*, Table *, i64, int); #ifdef SQLITE_DEBUG void sqlite3VdbeMemPrepareToChange(Vdbe*,Mem*); #endif Index: src/vdbeapi.c ================================================================== --- src/vdbeapi.c +++ src/vdbeapi.c @@ -1367,10 +1367,13 @@ if( iIdx>=p->pUnpacked->nField ){ *ppValue = (sqlite3_value *)columnNullValue(); }else{ *ppValue = &p->pUnpacked->aMem[iIdx]; + if( iIdx==p->iPKey ){ + sqlite3VdbeMemSetInt64(*ppValue, p->iKey1); + } sqlite3VdbeMemStoreType(*ppValue); } preupdate_old_out: sqlite3Error(db, rc, 0); @@ -1421,10 +1424,13 @@ } if( iIdx>=pUnpack->nField ){ pMem = (sqlite3_value *)columnNullValue(); }else{ pMem = &pUnpack->aMem[iIdx]; + if( iIdx==p->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey2); + } sqlite3VdbeMemStoreType(pMem); } }else{ /* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required ** value. Make a copy of the cell contents and return a pointer to it. @@ -1440,16 +1446,20 @@ } } assert( iIdx>=0 && iIdxpCsr->nField ); pMem = &p->aNew[iIdx]; if( pMem->flags==0 ){ - rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]); - if( rc!=SQLITE_OK ) goto preupdate_new_out; + if( iIdx==p->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey2); + }else{ + rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]); + if( rc!=SQLITE_OK ) goto preupdate_new_out; + } sqlite3VdbeMemStoreType(pMem); } } *ppValue = pMem; preupdate_new_out: sqlite3Error(db, rc, 0); return sqlite3ApiExit(db, rc); } Index: src/vdbeaux.c ================================================================== --- src/vdbeaux.c +++ src/vdbeaux.c @@ -3180,17 +3180,18 @@ void sqlite3VdbePreUpdateHook( Vdbe *v, /* Vdbe pre-update hook is invoked by */ VdbeCursor *pCsr, /* Cursor to grab old.* values from */ int op, /* SQLITE_INSERT, UPDATE or DELETE */ const char *zDb, /* Database name */ - const char *zTbl, /* Table name */ + Table *pTab, /* Modified table */ i64 iKey1, /* Initial key value */ int iReg /* Register for new.* record */ ){ sqlite3 *db = v->db; i64 iKey2; PreUpdate preupdate; + const char *zTbl = pTab->zName; assert( db->pPreUpdate==0 ); memset(&preupdate, 0, sizeof(PreUpdate)); if( op==SQLITE_UPDATE ){ iKey2 = v->aMem[iReg].u.i; @@ -3203,10 +3204,14 @@ preupdate.op = op; preupdate.iNewReg = iReg; preupdate.keyinfo.db = db; preupdate.keyinfo.enc = ENC(db); preupdate.keyinfo.nField = pCsr->nField; + preupdate.iKey1 = iKey1; + preupdate.iKey2 = iKey2; + preupdate.iPKey = pTab->iPKey; + db->pPreUpdate = &preupdate; db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); db->pPreUpdate = 0; sqlite3DbFree(db, preupdate.aRecord); if( preupdate.pUnpacked ){ Index: test/hook.test ================================================================== --- test/hook.test +++ test/hook.test @@ -620,11 +620,11 @@ INSERT INTO t8 VALUES('one', 'two'); INSERT INTO t8 VALUES('three', 'four'); ALTER TABLE t8 ADD COLUMN c DEFAULT 'xxx'; } -# At time of writing, these two are broken. They demonstraight that the +# At time of writing, these two are broken. They demonstrate that the # sqlite3_preupdate_old() method does not handle the case where ALTER TABLE # has been used to add a column with a default value other than NULL. # do_preupdate_test 7.5.2.1 { DELETE FROM t8 WHERE a = 'one' @@ -634,8 +634,23 @@ do_preupdate_test 7.5.2.2 { UPDATE t8 SET b = 'five' } { UPDATE main t8 2 2 three four xxx three five xxx } + +# This block of tests verifies that IPK values are correctly reported +# by the sqlite3_preupdate_old() and sqlite3_preupdate_new() functions. +# +do_execsql_test 7.6.1 { CREATE TABLE t9(a, b INTEGER PRIMARY KEY, c) } +do_preupdate_test 7.6.2 { + INSERT INTO t9 VALUES(1, 2, 3); + UPDATE t9 SET b = b+1, c = c+1; + DELETE FROM t9 WHERE a = 1; +} { + INSERT main t9 2 2 1 2 3 + UPDATE main t9 2 3 1 2 3 1 3 4 + DELETE main t9 3 3 1 3 4 +} + finish_test