aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/delete.c4
-rw-r--r--src/resolve.c4
-rw-r--r--src/sqliteInt.h12
-rw-r--r--src/trigger.c43
-rw-r--r--src/update.c63
5 files changed, 92 insertions, 34 deletions
diff --git a/src/delete.c b/src/delete.c
index 367b13d5b..5b3088828 100644
--- a/src/delete.c
+++ b/src/delete.c
@@ -496,7 +496,9 @@ void sqlite3GenerateRowDelete(
/* TODO: Could use temporary registers here. Also could attempt to
** avoid copying the contents of the rowid register. */
- mask = sqlite3TriggerOldmask(pParse, pTrigger, 0, pTab, onconf);
+ mask = sqlite3TriggerColmask(
+ pParse, pTrigger, 0, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onconf
+ );
mask |= sqlite3FkOldmask(pParse, pTab);
iOld = pParse->nMem+1;
pParse->nMem += (1 + pTab->nCol);
diff --git a/src/resolve.c b/src/resolve.c
index d913a24c8..0a59be7c4 100644
--- a/src/resolve.c
+++ b/src/resolve.c
@@ -264,6 +264,10 @@ static int lookupName(
testcase( iCol==31 );
testcase( iCol==32 );
pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
+ }else{
+ testcase( iCol==31 );
+ testcase( iCol==32 );
+ pParse->newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
}
pExpr->iColumn = (i16)iCol;
pExpr->pTab = pTab;
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 334a39a0f..aae1c3c66 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -2059,15 +2059,16 @@ struct AutoincInfo {
** The Parse.pTriggerPrg list never contains two entries with the same
** values for both pTrigger and orconf.
**
-** The TriggerPrg.oldmask variable is set to a mask of old.* columns
+** The TriggerPrg.aColmask[0] variable is set to a mask of old.* columns
** accessed (or set to 0 for triggers fired as a result of INSERT
-** statements).
+** statements). Similarly, the TriggerPrg.aColmask[1] variable is set to
+** a mask of new.* columns used by the program.
*/
struct TriggerPrg {
Trigger *pTrigger; /* Trigger this program was coded from */
int orconf; /* Default ON CONFLICT policy */
SubProgram *pProgram; /* Program implementing pTrigger/orconf */
- u32 oldmask; /* Mask of old.* columns accessed */
+ u32 aColmask[2]; /* Masks of old.*, new.* columns accessed */
TriggerPrg *pNext; /* Next entry in Parse.pTriggerPrg list */
};
@@ -2139,6 +2140,7 @@ struct Parse {
Parse *pToplevel; /* Parse structure for main program (or NULL) */
Table *pTriggerTab; /* Table triggers are being coded for */
u32 oldmask; /* Mask of old.* columns referenced */
+ u32 newmask; /* Mask of new.* columns referenced */
u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */
u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */
u8 disableTriggers; /* True to disable triggers */
@@ -2736,7 +2738,7 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, int);
TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*);
void sqlite3DeleteTrigger(sqlite3*, Trigger*);
void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*);
- u32 sqlite3TriggerOldmask(Parse*,Trigger*,ExprList*,Table*,int);
+ u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int);
# define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p))
#else
# define sqlite3TriggersExist(B,C,D,E,F) 0
@@ -2747,7 +2749,7 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, int);
# define sqlite3CodeRowTriggerDirect(A,B,C,D,E,F)
# define sqlite3TriggerList(X, Y) 0
# define sqlite3ParseToplevel(p) p
-# define sqlite3TriggerOldmask(A,B,C,D,E) 0
+# define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0
#endif
int sqlite3JoinType(Parse*, Token*, Token*, Token*);
diff --git a/src/trigger.c b/src/trigger.c
index b1256190c..51969ce23 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -811,7 +811,8 @@ static TriggerPrg *codeRowTrigger(
pProgram->nRef = 1;
pPrg->pTrigger = pTrigger;
pPrg->orconf = orconf;
- pPrg->oldmask = 0xffffffff;
+ pPrg->aColmask[0] = 0xffffffff;
+ pPrg->aColmask[1] = 0xffffffff;
/* Allocate and populate a new Parse context to use for coding the
** trigger sub-program. */
@@ -872,7 +873,8 @@ static TriggerPrg *codeRowTrigger(
pProgram->nMem = pSubParse->nMem;
pProgram->nCsr = pSubParse->nTab;
pProgram->token = (void *)pTrigger;
- pPrg->oldmask = pSubParse->oldmask;
+ pPrg->aColmask[0] = pSubParse->oldmask;
+ pPrg->aColmask[1] = pSubParse->newmask;
sqlite3VdbeDelete(v);
}
@@ -1032,28 +1034,36 @@ void sqlite3CodeRowTrigger(
}
/*
-** Triggers fired by UPDATE or DELETE statements may access values stored
-** in the old.* pseudo-table. This function returns a 32-bit bitmask
-** indicating which columns of the old.* table actually are used by
-** triggers. This information may be used by the caller to avoid having
-** to load the entire old.* record into memory when executing an UPDATE
-** or DELETE command.
+** Triggers may access values stored in the old.* or new.* pseudo-table.
+** This function returns a 32-bit bitmask indicating which columns of the
+** old.* or new.* tables actually are used by triggers. This information
+** may be used by the caller, for example, to avoid having to load the entire
+** old.* record into memory when executing an UPDATE or DELETE command.
**
** Bit 0 of the returned mask is set if the left-most column of the
-** table may be accessed using an old.<col> reference. Bit 1 is set if
+** table may be accessed using an [old|new].<col> reference. Bit 1 is set if
** the second leftmost column value is required, and so on. If there
** are more than 32 columns in the table, and at least one of the columns
** with an index greater than 32 may be accessed, 0xffffffff is returned.
**
-** It is not possible to determine if the old.rowid column is accessed
-** by triggers. The caller must always assume that it is.
+** It is not possible to determine if the old.rowid or new.rowid column is
+** accessed by triggers. The caller must always assume that it is.
**
-** There is no equivalent function for new.* references.
+** Parameter isNew must be either 1 or 0. If it is 0, then the mask returned
+** applies to the old.* table. If 1, the new.* table.
+**
+** Parameter tr_tm must be a mask with one or both of the TRIGGER_BEFORE
+** and TRIGGER_AFTER bits set. Values accessed by BEFORE triggers are only
+** included in the returned mask if the TRIGGER_BEFORE bit is set in the
+** tr_tm parameter. Similarly, values accessed by AFTER triggers are only
+** included in the returned mask if the TRIGGER_AFTER bit is set in tr_tm.
*/
-u32 sqlite3TriggerOldmask(
+u32 sqlite3TriggerColmask(
Parse *pParse, /* Parse context */
Trigger *pTrigger, /* List of triggers on table pTab */
ExprList *pChanges, /* Changes list for any UPDATE OF triggers */
+ int isNew, /* 1 for new.* ref mask, 0 for old.* ref mask */
+ int tr_tm, /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */
Table *pTab, /* The table to code triggers from */
int orconf /* Default ON CONFLICT policy for trigger steps */
){
@@ -1061,12 +1071,15 @@ u32 sqlite3TriggerOldmask(
u32 mask = 0;
Trigger *p;
+ assert( isNew==1 || isNew==0 );
for(p=pTrigger; p; p=p->pNext){
- if( p->op==op && checkColumnOverlap(p->pColumns,pChanges) ){
+ if( p->op==op && (tr_tm&p->tr_tm)
+ && checkColumnOverlap(p->pColumns,pChanges)
+ ){
TriggerPrg *pPrg;
pPrg = getRowTrigger(pParse, p, pTab, orconf);
if( pPrg ){
- mask |= pPrg->oldmask;
+ mask |= pPrg->aColmask[isNew];
}
}
}
diff --git a/src/update.c b/src/update.c
index c23601592..c927059aa 100644
--- a/src/update.c
+++ b/src/update.c
@@ -111,14 +111,15 @@ void sqlite3Update(
AuthContext sContext; /* The authorization context */
NameContext sNC; /* The name-context to resolve expressions in */
int iDb; /* Database containing the table being updated */
- int j1; /* Addresses of jump instructions */
int okOnePass; /* True for one-pass algorithm without the FIFO */
int hasFK; /* True if foreign key processing is required */
#ifndef SQLITE_OMIT_TRIGGER
- int isView; /* Trying to update a view */
- Trigger *pTrigger; /* List of triggers on pTab, if required */
+ int isView; /* True when updating a view (INSTEAD OF trigger) */
+ Trigger *pTrigger; /* List of triggers on pTab, if required */
+ int tmask; /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */
#endif
+ int newmask;
/* Register Allocations */
int regRowCount = 0; /* A count of rows changed */
@@ -146,11 +147,13 @@ void sqlite3Update(
** updated is a view.
*/
#ifndef SQLITE_OMIT_TRIGGER
- pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, 0);
+ pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, &tmask);
isView = pTab->pSelect!=0;
+ assert( pTrigger || tmask==0 );
#else
# define pTrigger 0
# define isView 0
+# define tmask 0
#endif
#ifdef SQLITE_OMIT_VIEW
# undef isView
@@ -160,7 +163,7 @@ void sqlite3Update(
if( sqlite3ViewGetColumnNames(pParse, pTab) ){
goto update_cleanup;
}
- if( sqlite3IsReadOnly(pParse, pTab, (pTrigger?1:0)) ){
+ if( sqlite3IsReadOnly(pParse, pTab, tmask) ){
goto update_cleanup;
}
aXRef = sqlite3DbMallocRaw(db, sizeof(int) * pTab->nCol );
@@ -388,7 +391,9 @@ void sqlite3Update(
** with the required old.* column data. */
if( hasFK || pTrigger ){
u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0);
- oldmask |= sqlite3TriggerOldmask(pParse, pTrigger, pChanges, pTab, onError);
+ oldmask |= sqlite3TriggerColmask(pParse,
+ pTrigger, pChanges, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onError
+ );
for(i=0; i<pTab->nCol; i++){
if( aXRef[i]<0 || oldmask==0xffffffff || (oldmask & (1<<i)) ){
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regOld+i);
@@ -405,24 +410,42 @@ void sqlite3Update(
/* 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. */
+ ** made by triggers.
+ **
+ ** If there are one or more BEFORE triggers, then do not populate the
+ ** registers associated with columns that are (a) not modified by
+ ** this UPDATE statement and (b) not accessed by new.* references. The
+ ** values for registers not modified by the UPDATE must be reloaded from
+ ** the database after the BEFORE triggers are fired anyway (as the trigger
+ ** may have modified them). So not loading those that are not going to
+ ** be used eliminates some redundant opcodes.
+ */
+ newmask = sqlite3TriggerColmask(
+ pParse, pTrigger, pChanges, 1, TRIGGER_BEFORE, pTab, onError
+ );
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 ){
+ if( j>=0 ){
+ sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regNew+i);
+ }else if( 0==(tmask&TRIGGER_BEFORE) || i>31 || (newmask&(1<<i)) ){
+ /* This branch loads the value of a column that will not be changed
+ ** into a register. This is done if there are no BEFORE triggers, or
+ ** if there are one or more BEFORE triggers that use this value via
+ ** a new.* reference in a trigger program.
+ */
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i);
sqlite3ColumnDefault(v, pTab, i, regNew+i);
- }else{
- sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regNew+i);
}
}
}
/* Fire any BEFORE UPDATE triggers. This happens before constraints are
- ** verified. One could argue that this is wrong. */
- if( pTrigger ){
+ ** verified. One could argue that this is wrong.
+ */
+ if( tmask&TRIGGER_BEFORE ){
sqlite3VdbeAddOp2(v, OP_Affinity, regNew, pTab->nCol);
sqlite3TableAffinityStr(v, pTab);
sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
@@ -432,11 +455,25 @@ void sqlite3Update(
** case, jump to the next row. No updates or AFTER triggers are
** required. This behaviour - what happens when the row being updated
** is deleted or renamed by a BEFORE trigger - is left undefined in the
- ** documentation. */
+ ** documentation.
+ */
sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid);
+
+ /* If it did not delete it, the row-trigger may still have modified
+ ** some of the columns of the row being updated. Load the values for
+ ** all columns not modified by the update statement into their
+ ** registers in case this has happened.
+ */
+ for(i=0; i<pTab->nCol; i++){
+ if( aXRef[i]<0 && i!=pTab->iPKey ){
+ sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i);
+ sqlite3ColumnDefault(v, pTab, i, regNew+i);
+ }
+ }
}
if( !isView ){
+ int j1; /* Address of jump instruction */
/* Do constraint checks. */
sqlite3GenerateConstraintChecks(pParse, pTab, iCur, regNewRowid,