diff options
author | dan <dan@noemail.net> | 2009-08-28 18:53:45 +0000 |
---|---|---|
committer | dan <dan@noemail.net> | 2009-08-28 18:53:45 +0000 |
commit | 165921a7427ef9dd52a398c9ef7edcbd2feffae4 (patch) | |
tree | f64d9573437057095784703b9c4804c24399efc4 /src/trigger.c | |
parent | e408edac1636c656f7044616ef0e37890612d233 (diff) | |
download | sqlite-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.c | 370 |
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) */ |