mirror of
https://github.com/sqlite/sqlite.git
synced 2025-11-06 15:49:35 +03:00
Changes to support recursive triggers.
FossilOrigin-Name: 9b9c19211593d5ff7b39254a29c284560a8bcedb
This commit is contained in:
255
src/update.c
255
src/update.c
@@ -120,21 +120,15 @@ void sqlite3Update(
|
||||
int isView; /* Trying to update a view */
|
||||
Trigger *pTrigger; /* List of triggers on pTab, if required */
|
||||
#endif
|
||||
int iBeginAfterTrigger = 0; /* Address of after trigger program */
|
||||
int iEndAfterTrigger = 0; /* Exit of after trigger program */
|
||||
int iBeginBeforeTrigger = 0; /* Address of before trigger program */
|
||||
int iEndBeforeTrigger = 0; /* Exit of before trigger program */
|
||||
u32 old_col_mask = 0; /* Mask of OLD.* columns in use */
|
||||
u32 new_col_mask = 0; /* Mask of NEW.* columns in use */
|
||||
|
||||
int newIdx = -1; /* index of trigger "new" temp table */
|
||||
int oldIdx = -1; /* index of trigger "old" temp table */
|
||||
|
||||
/* Register Allocations */
|
||||
int regRowCount = 0; /* A count of rows changed */
|
||||
int regOldRowid; /* The old rowid */
|
||||
int regNewRowid; /* The new rowid */
|
||||
int regData; /* New data for the row */
|
||||
int regNew;
|
||||
int regOld;
|
||||
int regRowSet = 0; /* Rowset of rows to be updated */
|
||||
|
||||
memset(&sContext, 0, sizeof(sContext));
|
||||
@@ -175,14 +169,6 @@ void sqlite3Update(
|
||||
if( aXRef==0 ) goto update_cleanup;
|
||||
for(i=0; i<pTab->nCol; i++) aXRef[i] = -1;
|
||||
|
||||
/* If there are FOR EACH ROW triggers, allocate cursors for the
|
||||
** special OLD and NEW tables
|
||||
*/
|
||||
if( pTrigger ){
|
||||
newIdx = pParse->nTab++;
|
||||
oldIdx = pParse->nTab++;
|
||||
}
|
||||
|
||||
/* Allocate a cursors for the main database table and for all indices.
|
||||
** The index cursors might not be used, but if they are used they
|
||||
** need to occur right after the database cursor. So go ahead and
|
||||
@@ -268,24 +254,7 @@ void sqlite3Update(
|
||||
aRegIdx[j] = reg;
|
||||
}
|
||||
|
||||
/* Allocate a block of register used to store the change record
|
||||
** sent to sqlite3GenerateConstraintChecks(). There are either
|
||||
** one or two registers for holding the rowid. One rowid register
|
||||
** is used if chngRowid is false and two are used if chngRowid is
|
||||
** true. Following these are pTab->nCol register holding column
|
||||
** data.
|
||||
*/
|
||||
regOldRowid = regNewRowid = pParse->nMem + 1;
|
||||
pParse->nMem += pTab->nCol + 1;
|
||||
if( chngRowid ){
|
||||
regNewRowid++;
|
||||
pParse->nMem++;
|
||||
}
|
||||
regData = regNewRowid+1;
|
||||
|
||||
|
||||
/* Begin generating code.
|
||||
*/
|
||||
/* Begin generating code. */
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
if( v==0 ) goto update_cleanup;
|
||||
if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
|
||||
@@ -302,40 +271,27 @@ void sqlite3Update(
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Start the view context
|
||||
*/
|
||||
/* Allocate required registers. */
|
||||
regOldRowid = regNewRowid = ++pParse->nMem;
|
||||
if( pTrigger ){
|
||||
regOld = pParse->nMem + 1;
|
||||
pParse->nMem += pTab->nCol;
|
||||
}
|
||||
if( chngRowid || pTrigger ){
|
||||
regNewRowid = ++pParse->nMem;
|
||||
}
|
||||
regNew = pParse->nMem + 1;
|
||||
pParse->nMem += pTab->nCol;
|
||||
|
||||
/* Start the view context. */
|
||||
if( isView ){
|
||||
sqlite3AuthContextPush(pParse, &sContext, pTab->zName);
|
||||
}
|
||||
|
||||
/* Generate the code for triggers.
|
||||
*/
|
||||
if( pTrigger ){
|
||||
int iGoto;
|
||||
|
||||
/* Create pseudo-tables for NEW and OLD
|
||||
*/
|
||||
sqlite3VdbeAddOp3(v, OP_OpenPseudo, oldIdx, 0, pTab->nCol);
|
||||
sqlite3VdbeAddOp3(v, OP_OpenPseudo, newIdx, 0, pTab->nCol);
|
||||
|
||||
iGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
|
||||
addr = sqlite3VdbeMakeLabel(v);
|
||||
iBeginBeforeTrigger = sqlite3VdbeCurrentAddr(v);
|
||||
if( sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
|
||||
TRIGGER_BEFORE, pTab, newIdx, oldIdx, onError, addr,
|
||||
&old_col_mask, &new_col_mask) ){
|
||||
goto update_cleanup;
|
||||
}
|
||||
iEndBeforeTrigger = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
|
||||
iBeginAfterTrigger = sqlite3VdbeCurrentAddr(v);
|
||||
if( sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
|
||||
TRIGGER_AFTER, pTab, newIdx, oldIdx, onError, addr,
|
||||
&old_col_mask, &new_col_mask) ){
|
||||
goto update_cleanup;
|
||||
}
|
||||
iEndAfterTrigger = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
|
||||
sqlite3VdbeJumpHere(v, iGoto);
|
||||
}
|
||||
/* If there are any triggers, set old_col_mask and new_col_mask. */
|
||||
sqlite3TriggerUses(pParse,
|
||||
pTrigger, TK_UPDATE, pChanges, pTab, onError, &old_col_mask, &new_col_mask
|
||||
);
|
||||
|
||||
/* If we are trying to update a view, realize that view into
|
||||
** a ephemeral table.
|
||||
@@ -374,7 +330,7 @@ void sqlite3Update(
|
||||
|
||||
/* Initialize the count of updated rows
|
||||
*/
|
||||
if( db->flags & SQLITE_CountRows && !pParse->trigStack ){
|
||||
if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab ){
|
||||
regRowCount = ++pParse->nMem;
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
|
||||
}
|
||||
@@ -407,11 +363,6 @@ void sqlite3Update(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Jump back to this point if a trigger encounters an IGNORE constraint. */
|
||||
if( pTrigger ){
|
||||
sqlite3VdbeResolveLabel(v, addr);
|
||||
}
|
||||
|
||||
/* Top of the update loop */
|
||||
if( okOnePass ){
|
||||
@@ -422,139 +373,87 @@ void sqlite3Update(
|
||||
addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet, 0, regOldRowid);
|
||||
}
|
||||
|
||||
/* Make cursor iCur point to the record that is being updated. If
|
||||
** this record does not exist for some reason (deleted by a trigger,
|
||||
** for example, then jump to the next iteration of the RowSet loop. */
|
||||
sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid);
|
||||
|
||||
/* If there are triggers on this table, populate an array of registers
|
||||
** with the required old.* column data. */
|
||||
if( pTrigger ){
|
||||
int regRowid;
|
||||
int regRow;
|
||||
int regCols;
|
||||
|
||||
/* Make cursor iCur point to the record that is being updated.
|
||||
*/
|
||||
sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid);
|
||||
|
||||
/* Generate the OLD table
|
||||
*/
|
||||
regRowid = sqlite3GetTempReg(pParse);
|
||||
regRow = sqlite3GetTempReg(pParse);
|
||||
sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regRowid);
|
||||
if( !old_col_mask ){
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regRow);
|
||||
}else{
|
||||
sqlite3VdbeAddOp2(v, OP_RowData, iCur, regRow);
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, oldIdx, regRow, regRowid);
|
||||
|
||||
/* Generate the NEW table
|
||||
*/
|
||||
if( chngRowid ){
|
||||
sqlite3ExprCodeAndCache(pParse, pRowidExpr, regRowid);
|
||||
sqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid);
|
||||
}else{
|
||||
sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regRowid);
|
||||
}
|
||||
regCols = sqlite3GetTempRange(pParse, pTab->nCol);
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( i==pTab->iPKey ){
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regCols+i);
|
||||
continue;
|
||||
}
|
||||
j = aXRef[i];
|
||||
if( (i<32 && (new_col_mask&((u32)1<<i))!=0) || new_col_mask==0xffffffff ){
|
||||
if( j<0 ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regCols+i);
|
||||
sqlite3ColumnDefault(v, pTab, i, -1);
|
||||
}else{
|
||||
sqlite3ExprCodeAndCache(pParse, pChanges->a[j].pExpr, regCols+i);
|
||||
}
|
||||
if( aXRef[i]<0 || old_col_mask==0xffffffff || (old_col_mask & (1<<i)) ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regOld+i);
|
||||
sqlite3ColumnDefault(v, pTab, i, regOld+i);
|
||||
}else{
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regCols+i);
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regOld+i);
|
||||
}
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regCols, pTab->nCol, regRow);
|
||||
if( !isView ){
|
||||
sqlite3TableAffinityStr(v, pTab);
|
||||
sqlite3ExprCacheAffinityChange(pParse, regCols, pTab->nCol);
|
||||
}
|
||||
sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol);
|
||||
/* if( pParse->nErr ) goto update_cleanup; */
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, newIdx, regRow, regRowid);
|
||||
sqlite3ReleaseTempReg(pParse, regRowid);
|
||||
sqlite3ReleaseTempReg(pParse, regRow);
|
||||
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginBeforeTrigger);
|
||||
sqlite3VdbeJumpHere(v, iEndBeforeTrigger);
|
||||
}
|
||||
|
||||
if( !isView ){
|
||||
/* Loop over every record that needs updating. We have to load
|
||||
** the old data for each record to be updated because some columns
|
||||
** might not change and we will need to copy the old value.
|
||||
** Also, the old data is needed to delete the old index entries.
|
||||
** So make the cursor point at the old record.
|
||||
*/
|
||||
sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid);
|
||||
/* If the record number will change, set register regNewRowid to
|
||||
** contain the new value. If the record number is not being modified,
|
||||
** then regNewRowid is the same register as regOldRowid, which is
|
||||
** already populated. */
|
||||
assert( chngRowid || pTrigger || regOldRowid==regNewRowid );
|
||||
if( chngRowid ){
|
||||
sqlite3ExprCode(pParse, pRowidExpr, regNewRowid);
|
||||
sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid);
|
||||
}else if( pTrigger ){
|
||||
sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid);
|
||||
}
|
||||
|
||||
/* If the record number will change, push the record number as it
|
||||
** will be after the update. (The old record number is currently
|
||||
** on top of the stack.)
|
||||
*/
|
||||
if( chngRowid ){
|
||||
sqlite3ExprCode(pParse, pRowidExpr, regNewRowid);
|
||||
sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid);
|
||||
}
|
||||
|
||||
/* Compute new data for this record.
|
||||
*/
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( i==pTab->iPKey ){
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regData+i);
|
||||
continue;
|
||||
}
|
||||
/* Populate the array of registers beginning at regNew with the new
|
||||
** row data. This array is used to check constaints, create the new
|
||||
** table and index records, and as the values for any new.* references
|
||||
** made by triggers. */
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( i==pTab->iPKey ){
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i);
|
||||
}else{
|
||||
j = aXRef[i];
|
||||
if( j<0 ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regData+i);
|
||||
sqlite3ColumnDefault(v, pTab, i, regData+i);
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i);
|
||||
sqlite3ColumnDefault(v, pTab, i, regNew+i);
|
||||
}else{
|
||||
sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regData+i);
|
||||
sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regNew+i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Do constraint checks
|
||||
*/
|
||||
/* Fire any BEFORE UPDATE triggers. This happens before constraints are
|
||||
** verified. One could argue that this is wrong. */
|
||||
sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
|
||||
TRIGGER_BEFORE, pTab, regNew, regOld, onError, addr);
|
||||
|
||||
if( !isView ){
|
||||
|
||||
/* Do constraint checks. */
|
||||
sqlite3GenerateConstraintChecks(pParse, pTab, iCur, regNewRowid,
|
||||
aRegIdx, chngRowid, 1,
|
||||
onError, addr, 0);
|
||||
aRegIdx, chngRowid, 1, onError, addr, 0);
|
||||
|
||||
/* Delete the old indices for the current record.
|
||||
*/
|
||||
/* Delete the index entries associated with the current record. */
|
||||
j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regOldRowid);
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx);
|
||||
|
||||
/* If changing the record number, delete the old record.
|
||||
*/
|
||||
|
||||
/* If changing the record number, delete the old record. */
|
||||
if( chngRowid ){
|
||||
sqlite3VdbeAddOp2(v, OP_Delete, iCur, 0);
|
||||
}
|
||||
sqlite3VdbeJumpHere(v, j1);
|
||||
|
||||
/* Create the new index entries and the new record.
|
||||
*/
|
||||
sqlite3CompleteInsertion(pParse, pTab, iCur, regNewRowid,
|
||||
aRegIdx, 1, -1, 0, 0);
|
||||
|
||||
/* Create the new index entries and the new record. */
|
||||
sqlite3CompleteInsertion(pParse, pTab, iCur, regNewRowid, aRegIdx,1,-1,0,0);
|
||||
}
|
||||
|
||||
/* Increment the row counter
|
||||
*/
|
||||
if( db->flags & SQLITE_CountRows && !pParse->trigStack){
|
||||
if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab){
|
||||
sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1);
|
||||
}
|
||||
|
||||
/* If there are triggers, close all the cursors after each iteration
|
||||
** through the loop. The fire the after triggers.
|
||||
*/
|
||||
if( pTrigger ){
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginAfterTrigger);
|
||||
sqlite3VdbeJumpHere(v, iEndAfterTrigger);
|
||||
}
|
||||
sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
|
||||
TRIGGER_AFTER, pTab, regNew, regOld, onError, addr);
|
||||
|
||||
/* Repeat the above with the next record to be updated, until
|
||||
** all record selected by the WHERE clause have been updated.
|
||||
@@ -569,16 +468,12 @@ void sqlite3Update(
|
||||
}
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Close, iCur, 0);
|
||||
if( pTrigger ){
|
||||
sqlite3VdbeAddOp2(v, OP_Close, newIdx, 0);
|
||||
sqlite3VdbeAddOp2(v, OP_Close, oldIdx, 0);
|
||||
}
|
||||
|
||||
/* Update the sqlite_sequence table by storing the content of the
|
||||
** maximum rowid counter values recorded while inserting into
|
||||
** autoincrement tables.
|
||||
*/
|
||||
if( pParse->nested==0 && pParse->trigStack==0 ){
|
||||
if( pParse->nested==0 && pParse->pTriggerTab==0 ){
|
||||
sqlite3AutoincrementEnd(pParse);
|
||||
}
|
||||
|
||||
@@ -587,7 +482,7 @@ void sqlite3Update(
|
||||
** generating code because of a call to sqlite3NestedParse(), do not
|
||||
** invoke the callback function.
|
||||
*/
|
||||
if( db->flags & SQLITE_CountRows && !pParse->trigStack && pParse->nested==0 ){
|
||||
if( (db->flags&SQLITE_CountRows) && !pParse->pTriggerTab && !pParse->nested ){
|
||||
sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
|
||||
sqlite3VdbeSetNumCols(v, 1);
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC);
|
||||
|
||||
Reference in New Issue
Block a user