aboutsummaryrefslogtreecommitdiff
path: root/src/trigger.c
diff options
context:
space:
mode:
authordan <dan@noemail.net>2009-08-28 18:53:45 +0000
committerdan <dan@noemail.net>2009-08-28 18:53:45 +0000
commit165921a7427ef9dd52a398c9ef7edcbd2feffae4 (patch)
treef64d9573437057095784703b9c4804c24399efc4 /src/trigger.c
parente408edac1636c656f7044616ef0e37890612d233 (diff)
downloadsqlite-165921a7427ef9dd52a398c9ef7edcbd2feffae4.tar.gz
sqlite-165921a7427ef9dd52a398c9ef7edcbd2feffae4.zip
Changes to support recursive triggers.
FossilOrigin-Name: 9b9c19211593d5ff7b39254a29c284560a8bcedb
Diffstat (limited to 'src/trigger.c')
-rw-r--r--src/trigger.c370
1 files changed, 247 insertions, 123 deletions
diff --git a/src/trigger.c b/src/trigger.c
index eb84d9b43..e59c53bb0 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -219,7 +219,7 @@ void sqlite3BeginTrigger(
/* Build the Trigger object */
pTrigger = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger));
if( pTrigger==0 ) goto trigger_cleanup;
- pTrigger->name = zName;
+ pTrigger->zName = zName;
zName = 0;
pTrigger->table = sqlite3DbStrDup(db, pTableName->a[0].zName);
pTrigger->pSchema = db->aDb[iDb].pSchema;
@@ -262,14 +262,14 @@ void sqlite3FinishTrigger(
pTrig = pParse->pNewTrigger;
pParse->pNewTrigger = 0;
if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup;
- zName = pTrig->name;
+ zName = pTrig->zName;
iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema);
pTrig->step_list = pStepList;
while( pStepList ){
pStepList->pTrig = pTrig;
pStepList = pStepList->pNext;
}
- nameToken.z = pTrig->name;
+ nameToken.z = pTrig->zName;
nameToken.n = sqlite3Strlen30(nameToken.z);
if( sqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken)
&& sqlite3FixTriggerStep(&sFix, pTrig->step_list) ){
@@ -451,7 +451,7 @@ TriggerStep *sqlite3TriggerDeleteStep(
void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){
if( pTrigger==0 ) return;
sqlite3DeleteTriggerStep(db, pTrigger->step_list);
- sqlite3DbFree(db, pTrigger->name);
+ sqlite3DbFree(db, pTrigger->zName);
sqlite3DbFree(db, pTrigger->table);
sqlite3ExprDelete(db, pTrigger->pWhen);
sqlite3IdListDelete(db, pTrigger->pColumns);
@@ -558,11 +558,11 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
sqlite3BeginWriteOperation(pParse, 0, iDb);
sqlite3OpenMasterTable(pParse, iDb);
base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger);
- sqlite3VdbeChangeP4(v, base+1, pTrigger->name, 0);
+ sqlite3VdbeChangeP4(v, base+1, pTrigger->zName, 0);
sqlite3VdbeChangeP4(v, base+4, "trigger", P4_STATIC);
sqlite3ChangeCookie(pParse, iDb);
sqlite3VdbeAddOp2(v, OP_Close, 0, 0);
- sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->name, 0);
+ sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0);
if( pParse->nMem<3 ){
pParse->nMem = 3;
}
@@ -674,70 +674,226 @@ static int codeTriggerProgram(
TriggerStep *pStepList, /* List of statements inside the trigger body */
int orconfin /* Conflict algorithm. (OE_Abort, etc) */
){
- TriggerStep * pTriggerStep = pStepList;
- int orconf;
+ TriggerStep * pStep = pStepList;
Vdbe *v = pParse->pVdbe;
sqlite3 *db = pParse->db;
- assert( pTriggerStep!=0 );
+ assert( pParse->pRoot );
+ assert( pStep!=0 );
assert( v!=0 );
- sqlite3VdbeAddOp2(v, OP_ContextPush, 0, 0);
- VdbeComment((v, "begin trigger %s", pStepList->pTrig->name));
- while( pTriggerStep ){
- sqlite3ExprCacheClear(pParse);
- orconf = (orconfin == OE_Default)?pTriggerStep->orconf:orconfin;
- pParse->trigStack->orconf = orconf;
- switch( pTriggerStep->op ){
+/* sqlite3VdbeAddOp2(v, OP_ContextPush, 0, 0); */
+ while( pStep ){
+ /* Figure out the ON CONFLICT policy that will be used for this step
+ ** of the trigger program. If the statement that caused this trigger
+ ** to fire had an explicit ON CONFLICT, then use it. Otherwise, use
+ ** the ON CONFLICT policy that was specified as part of the trigger
+ ** step statement. Example:
+ **
+ ** CREATE TRIGGER AFTER INSERT ON t1 BEGIN;
+ ** INSERT OR REPLACE INTO t2 VALUES(new.a, new.b);
+ ** END;
+ **
+ ** INSERT INTO t1 ... ; -- insert into t2 uses REPLACE policy
+ ** INSERT OR IGNORE INTO t1 ... ; -- insert into t2 uses IGNORE policy
+ */
+ pParse->orconf = (orconfin==OE_Default)?pStep->orconf:orconfin;
+
+ switch( pStep->op ){
case TK_UPDATE: {
- SrcList *pSrc;
- pSrc = targetSrcList(pParse, pTriggerStep);
- sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0);
- sqlite3Update(pParse, pSrc,
- sqlite3ExprListDup(db, pTriggerStep->pExprList, 0),
- sqlite3ExprDup(db, pTriggerStep->pWhere, 0), orconf);
- sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0);
+ sqlite3Update(pParse,
+ targetSrcList(pParse, pStep),
+ sqlite3ExprListDup(db, pStep->pExprList, 0),
+ sqlite3ExprDup(db, pStep->pWhere, 0),
+ pParse->orconf
+ );
break;
}
case TK_INSERT: {
- SrcList *pSrc;
- pSrc = targetSrcList(pParse, pTriggerStep);
- sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0);
- sqlite3Insert(pParse, pSrc,
- sqlite3ExprListDup(db, pTriggerStep->pExprList, 0),
- sqlite3SelectDup(db, pTriggerStep->pSelect, 0),
- sqlite3IdListDup(db, pTriggerStep->pIdList), orconf);
- sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0);
+ sqlite3Insert(pParse,
+ targetSrcList(pParse, pStep),
+ sqlite3ExprListDup(db, pStep->pExprList, 0),
+ sqlite3SelectDup(db, pStep->pSelect, 0),
+ sqlite3IdListDup(db, pStep->pIdList),
+ pParse->orconf
+ );
break;
}
case TK_DELETE: {
- SrcList *pSrc;
- sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0);
- pSrc = targetSrcList(pParse, pTriggerStep);
- sqlite3DeleteFrom(pParse, pSrc,
- sqlite3ExprDup(db, pTriggerStep->pWhere, 0));
- sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0);
+ sqlite3DeleteFrom(pParse,
+ targetSrcList(pParse, pStep),
+ sqlite3ExprDup(db, pStep->pWhere, 0)
+ );
break;
}
- default: assert( pTriggerStep->op==TK_SELECT ); {
- Select *ss = sqlite3SelectDup(db, pTriggerStep->pSelect, 0);
- if( ss ){
- SelectDest dest;
-
- sqlite3SelectDestInit(&dest, SRT_Discard, 0);
- sqlite3Select(pParse, ss, &dest);
- sqlite3SelectDelete(db, ss);
- }
+ default: assert( pStep->op==TK_SELECT ); {
+ SelectDest sDest;
+ Select *pSelect = sqlite3SelectDup(db, pStep->pSelect, 0);
+ sqlite3SelectDestInit(&sDest, SRT_Discard, 0);
+ sqlite3Select(pParse, pSelect, &sDest);
+ sqlite3SelectDelete(db, pSelect);
break;
}
}
- pTriggerStep = pTriggerStep->pNext;
+ pStep = pStep->pNext;
}
- sqlite3VdbeAddOp2(v, OP_ContextPop, 0, 0);
- VdbeComment((v, "end trigger %s", pStepList->pTrig->name));
+/* sqlite3VdbeAddOp2(v, OP_ContextPop, 0, 0); */
return 0;
}
+#ifdef SQLITE_DEBUG
+/*
+** This function is used to add VdbeComment() annotations to a VDBE
+** program. It is not used in production code, only for debugging.
+*/
+static const char *onErrorText(int onError){
+ switch( onError ){
+ case OE_Abort: return "abort";
+ case OE_Rollback: return "rollback";
+ case OE_Fail: return "fail";
+ case OE_Replace: return "replace";
+ case OE_Ignore: return "ignore";
+ case OE_Default: return "default";
+ }
+ return "n/a";
+}
+#endif
+
+/*
+** Parse context structure pFrom has just been used to create a sub-vdbe
+** (trigger program). If an error has occurred, transfer error information
+** from pFrom to pTo.
+*/
+static void transferParseError(Parse *pTo, Parse *pFrom){
+ assert( pFrom->zErrMsg==0 || pFrom->nErr );
+ assert( pTo->zErrMsg==0 || pTo->nErr );
+ if( pTo->nErr==0 ){
+ pTo->zErrMsg = pFrom->zErrMsg;
+ pTo->nErr = pFrom->nErr;
+ }else{
+ sqlite3DbFree(pFrom->db, pFrom->zErrMsg);
+ }
+}
+
+static CodedTrigger *codeRowTrigger(
+ Parse *pRoot, /* Root parse context */
+ Parse *pParse, /* Current parse context */
+ Trigger *pTrigger, /* Trigger to code */
+ int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
+ Table *pTab, /* The table to code triggers from */
+ int orconf
+){
+ sqlite3 *db = pParse->db;
+ CodedTrigger *pC;
+ Expr *pWhen = 0; /* Duplicate of trigger WHEN expression */
+ Vdbe *v; /* Temporary VM */
+ AuthContext sContext; /* Auth context for sub-vdbe */
+ NameContext sNC; /* Name context for sub-vdbe */
+ SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */
+ Parse *pSubParse; /* Parse context for sub-vdbe */
+ int iEndTrigger = 0; /* Label to jump to if WHEN is false */
+
+ pC = sqlite3DbMallocZero(db, sizeof(CodedTrigger));
+ if( !pC ) return 0;
+ pC->pNext = pRoot->pCodedTrigger;
+ pRoot->pCodedTrigger = pC;
+ pC->pProgram = pProgram = sqlite3DbMallocZero(db, sizeof(SubProgram));
+ if( !pProgram ) return 0;
+ pProgram->nRef = 1;
+ pSubParse = sqlite3StackAllocZero(db, sizeof(Parse));
+ if( !pSubParse ) return 0;
+
+ pC->pProgram = pProgram;
+ pC->pTrigger = pTrigger;
+ pC->orconf = orconf;
+
+ memset(&sNC, 0, sizeof(sNC));
+ sNC.pParse = pSubParse;
+ pSubParse->db = db;
+ pSubParse->pTriggerTab = pTab;
+ pSubParse->pRoot = pRoot;
+
+ /* Push an entry on to the auth context stack */
+ sqlite3AuthContextPush(pParse, &sContext, pTrigger->name);
+
+ v = sqlite3GetVdbe(pSubParse);
+ if( v ){
+ VdbeComment((v, "Trigger: %s (%s %s%s%s ON %s) (%s)", pTrigger->zName,
+ (pTrigger->tr_tm==TRIGGER_BEFORE ? "BEFORE" : "AFTER"),
+ (op==TK_UPDATE ? "UPDATE" : ""),
+ (op==TK_INSERT ? "INSERT" : ""),
+ (op==TK_DELETE ? "DELETE" : ""),
+ pTab->zName, onErrorText(orconf)
+ ));
+
+ if( pTrigger->pWhen ){
+ /* Code the WHEN clause. If it evaluates to false (or NULL) the
+ ** sub-vdbe is immediately halted. */
+ pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0);
+ if( SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen) ){
+ iEndTrigger = sqlite3VdbeMakeLabel(v);
+ sqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL);
+ }
+ sqlite3ExprDelete(db, pWhen);
+ }
+
+ /* Code the trigger program into the sub-vdbe. */
+ codeTriggerProgram(pSubParse, pTrigger->step_list, OE_Default);
+ if( iEndTrigger ){
+ sqlite3VdbeResolveLabel(v, iEndTrigger);
+ }
+ sqlite3VdbeAddOp0(v, OP_Halt);
+ transferParseError(pParse, pSubParse);
+ pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pParse->nArg);
+ pProgram->nMem = pSubParse->nMem;
+ pProgram->nCsr = pSubParse->nTab;
+ pProgram->token = (void *)pTrigger;
+ pC->oldmask = pSubParse->oldmask;
+ pC->newmask = pSubParse->newmask;
+ sqlite3VdbeDelete(v);
+
+ while( pSubParse->pAinc ){
+ AutoincInfo *p = pSubParse->pAinc;
+ pSubParse->pAinc = p->pNext;
+ sqlite3DbFree(db, p);
+ }
+ }
+ sqlite3StackFree(db, pSubParse);
+
+ /* Pop the entry off the authorization stack */
+ sqlite3AuthContextPop(&sContext);
+ return pC;
+}
+
+static CodedTrigger *getRowTrigger(
+ Parse *pParse,
+ Trigger *pTrigger, /* Trigger to code */
+ int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
+ Table *pTab, /* The table to code triggers from */
+ int orconf
+){
+ CodedTrigger *pC;
+ Parse *pRoot = pParse;
+
+ /* It may be that this trigger has already been coded (or is in the
+ ** process of being coded). If this is the case, then an entry with
+ ** a matching CodedTrigger.pTrigger field will be present somewhere
+ ** in the Parse.pCodedTrigger list. Search for such an entry. */
+ if( pParse->pRoot ){
+ pRoot = pParse->pRoot;
+ }
+ for(pC=pRoot->pCodedTrigger;
+ pC && (pC->pTrigger!=pTrigger || pC->orconf!=orconf);
+ pC=pC->pNext
+ );
+
+ if( !pC ){
+ pC = codeRowTrigger(pRoot, pParse, pTrigger, op, pTab, orconf);
+ }
+
+ return pC;
+}
+
/*
** This is called to code FOR EACH ROW triggers.
**
@@ -765,7 +921,7 @@ static int codeTriggerProgram(
** output mask is set to the special value 0xffffffff.
**
*/
-int sqlite3CodeRowTrigger(
+void sqlite3CodeRowTrigger(
Parse *pParse, /* Parse context */
Trigger *pTrigger, /* List of triggers on table pTab */
int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
@@ -775,100 +931,68 @@ int sqlite3CodeRowTrigger(
int newIdx, /* The indice of the "new" row to access */
int oldIdx, /* The indice of the "old" row to access */
int orconf, /* ON CONFLICT policy */
- int ignoreJump, /* Instruction to jump to for RAISE(IGNORE) */
- u32 *piOldColMask, /* OUT: Mask of columns used from the OLD.* table */
- u32 *piNewColMask /* OUT: Mask of columns used from the NEW.* table */
+ int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */
){
Trigger *p;
- sqlite3 *db = pParse->db;
- TriggerStack trigStackEntry;
-
- trigStackEntry.oldColMask = 0;
- trigStackEntry.newColMask = 0;
assert(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE);
assert(tr_tm == TRIGGER_BEFORE || tr_tm == TRIGGER_AFTER );
- assert(newIdx != -1 || oldIdx != -1);
-
for(p=pTrigger; p; p=p->pNext){
- int fire_this = 0;
/* Sanity checking: The schema for the trigger and for the table are
** always defined. The trigger must be in the same schema as the table
** or else it must be a TEMP trigger. */
assert( p->pSchema!=0 );
assert( p->pTabSchema!=0 );
- assert( p->pSchema==p->pTabSchema || p->pSchema==db->aDb[1].pSchema );
+ assert( p->pSchema==p->pTabSchema
+ || p->pSchema==pParse->db->aDb[1].pSchema );
/* Determine whether we should code this trigger */
- if(
- p->op==op &&
- p->tr_tm==tr_tm &&
- checkColumnOverlap(p->pColumns,pChanges)
+ if( p->op==op
+ && p->tr_tm==tr_tm
+ && checkColumnOverlap(p->pColumns,pChanges)
){
- TriggerStack *pS; /* Pointer to trigger-stack entry */
- for(pS=pParse->trigStack; pS && p!=pS->pTrigger; pS=pS->pNext){}
- if( !pS ){
- fire_this = 1;
+ Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */
+ CodedTrigger *pC;
+ pC = getRowTrigger(pParse, p, op, pTab, orconf);
+ assert( pC || pParse->nErr || pParse->db->mallocFailed );
+
+ /* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program
+ ** is a pointer to the sub-vdbe containing the trigger program. */
+ if( pC ){
+ sqlite3VdbeAddOp3(v, OP_Program, oldIdx, newIdx, ++pParse->nMem);
+ pC->pProgram->nRef++;
+ sqlite3VdbeChangeP4(v, -1, (const char *)pC->pProgram, P4_SUBPROGRAM);
+ VdbeComment((v, "Call trigger: %s (%s)", p->zName,onErrorText(orconf)));
}
-#if 0 /* Give no warning for recursive triggers. Just do not do them */
- else{
- sqlite3ErrorMsg(pParse, "recursive triggers not supported (%s)",
- p->name);
- return SQLITE_ERROR;
- }
-#endif
}
-
- if( fire_this ){
- int endTrigger;
- Expr * whenExpr;
- AuthContext sContext;
- NameContext sNC;
-
-#ifndef SQLITE_OMIT_TRACE
- sqlite3VdbeAddOp4(pParse->pVdbe, OP_Trace, 0, 0, 0,
- sqlite3MPrintf(db, "-- TRIGGER %s", p->name),
- P4_DYNAMIC);
-#endif
- memset(&sNC, 0, sizeof(sNC));
- sNC.pParse = pParse;
-
- /* Push an entry on to the trigger stack */
- trigStackEntry.pTrigger = p;
- trigStackEntry.newIdx = newIdx;
- trigStackEntry.oldIdx = oldIdx;
- trigStackEntry.pTab = pTab;
- trigStackEntry.pNext = pParse->trigStack;
- trigStackEntry.ignoreJump = ignoreJump;
- pParse->trigStack = &trigStackEntry;
- sqlite3AuthContextPush(pParse, &sContext, p->name);
-
- /* code the WHEN clause */
- endTrigger = sqlite3VdbeMakeLabel(pParse->pVdbe);
- whenExpr = sqlite3ExprDup(db, p->pWhen, 0);
- if( db->mallocFailed || sqlite3ResolveExprNames(&sNC, whenExpr) ){
- pParse->trigStack = trigStackEntry.pNext;
- sqlite3ExprDelete(db, whenExpr);
- return 1;
- }
- sqlite3ExprIfFalse(pParse, whenExpr, endTrigger, SQLITE_JUMPIFNULL);
- sqlite3ExprDelete(db, whenExpr);
-
- sqlite3ExprCachePush(pParse);
- codeTriggerProgram(pParse, p->step_list, orconf);
- sqlite3ExprCachePop(pParse, 1);
+ }
+}
- /* Pop the entry off the trigger stack */
- pParse->trigStack = trigStackEntry.pNext;
- sqlite3AuthContextPop(&sContext);
+void sqlite3TriggerUses(
+ Parse *pParse, /* Parse context */
+ Trigger *pTrigger, /* List of triggers on table pTab */
+ int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
+ ExprList *pChanges, /* Changes list for any UPDATE OF triggers */
+ Table *pTab, /* The table to code triggers from */
+ int orconf, /* Default ON CONFLICT policy for trigger steps */
+ u32 *piOldColMask, /* OUT: Mask of columns used from the OLD.* table */
+ u32 *piNewColMask /* OUT: Mask of columns used from the NEW.* table */
+){
+ Trigger *p;
+ assert(op==TK_UPDATE || op==TK_INSERT || op==TK_DELETE);
- sqlite3VdbeResolveLabel(pParse->pVdbe, endTrigger);
+ for(p=pTrigger; p; p=p->pNext){
+ if( p->op==op && checkColumnOverlap(p->pColumns,pChanges) ){
+ CodedTrigger *pC;
+ pC = getRowTrigger(pParse, p, op, pTab, orconf);
+ if( pC ){
+ *piOldColMask |= pC->oldmask;
+ *piNewColMask |= pC->newmask;
+ }
}
}
- if( piOldColMask ) *piOldColMask |= trigStackEntry.oldColMask;
- if( piNewColMask ) *piNewColMask |= trigStackEntry.newColMask;
- return 0;
}
+
#endif /* !defined(SQLITE_OMIT_TRIGGER) */