mirror of
https://github.com/sqlite/sqlite.git
synced 2025-08-07 02:42:48 +03:00
Changes to support recursive triggers.
FossilOrigin-Name: 9b9c19211593d5ff7b39254a29c284560a8bcedb
This commit is contained in:
376
src/trigger.c
376
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);
|
||||
|
||||
sqlite3VdbeResolveLabel(pParse->pVdbe, endTrigger);
|
||||
}
|
||||
}
|
||||
if( piOldColMask ) *piOldColMask |= trigStackEntry.oldColMask;
|
||||
if( piNewColMask ) *piNewColMask |= trigStackEntry.newColMask;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* !defined(SQLITE_OMIT_TRIGGER) */
|
||||
|
Reference in New Issue
Block a user