/* ** This is called to code FOR EACH ROW triggers. ** ** When the code that this function generates is executed, the following ** must be true: ** ** 1. No cursors may be open in the main database. (But newIdx and oldIdx ** can be indices of cursors in temporary tables. See below.) ** ** 2. If the triggers being coded are ON INSERT or ON UPDATE triggers, then ** a temporary vdbe cursor (index newIdx) must be open and pointing at ** a row containing values to be substituted for new.* expressions in the ** trigger program(s). ** ** 3. If the triggers being coded are ON DELETE or ON UPDATE triggers, then ** a temporary vdbe cursor (index oldIdx) must be open and pointing at ** a row containing values to be substituted for old.* expressions in the ** trigger program(s). ** ** If they are not NULL, the piOldColMask and piNewColMask output variables ** are set to values that describe the columns used by the trigger program ** in the OLD.* and NEW.* tables respectively. If column N of the ** pseudo-table is read at least once, the corresponding bit of the output ** mask is set. If a column with an index greater than 32 is read, the ** output mask is set to the special value 0xffffffff. ** */ static int sqlite3CodeRowTrigger( 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 */ int tr_tm, /* One of TRIGGER_BEFORE, TRIGGER_AFTER */ Table pTab, /* The table to code triggers from */ 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) */ ref u32 piOldColMask, /* OUT: Mask of columns used from the OLD.* table */ ref u32 piNewColMask /* OUT: Mask of columns used from the NEW.* table */ ) { Trigger p; sqlite3 db = pParse.db; TriggerStack trigStackEntry = new TriggerStack(); trigStackEntry.oldColMask = 0; trigStackEntry.newColMask = 0; Debug.Assert(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE); Debug.Assert(tr_tm == TRIGGER_BEFORE || tr_tm == TRIGGER_AFTER); Debug.Assert(newIdx != -1 || oldIdx != -1); for (p = pTrigger; p != null; p = p.pNext) { bool fire_this = false; /* 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. */ Debug.Assert(p.pSchema != null); Debug.Assert(p.pTabSchema != null); Debug.Assert(p.pSchema == p.pTabSchema || p.pSchema == db.aDb[1].pSchema); /* Determine whether we should code this trigger */ if ( p.op == op && p.tr_tm == tr_tm && checkColumnOverlap(p.pColumns, pChanges) != 0) { TriggerStack pS; /* Pointer to trigger-stack entry */ for (pS = pParse.trigStack; pS != null && p != pS.pTrigger; pS = pS.pNext) { } if (pS == null) { fire_this = true; } #if FALSE // * 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; #if !SQLITE_OMIT_TRACE sqlite3VdbeAddOp4(pParse.pVdbe, OP_Trace, 0, 0, 0, sqlite3MPrintf(db, "-- TRIGGER %s", p.name), P4_DYNAMIC); #endif sNC = new NameContext();// 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; #if !SQLITE_OMIT_AUTHORIZATION sqlite3AuthContextPush(pParse, sContext, p.name); #endif /* code the WHEN clause */ endTrigger = sqlite3VdbeMakeLabel(pParse.pVdbe); whenExpr = sqlite3ExprDup(db, p.pWhen, 0); if (/* db.mallocFailed != 0 || */ sqlite3ResolveExprNames(sNC, ref whenExpr) != 0) { pParse.trigStack = trigStackEntry.pNext; sqlite3ExprDelete(db, ref whenExpr); return(1); } sqlite3ExprIfFalse(pParse, whenExpr, endTrigger, SQLITE_JUMPIFNULL); sqlite3ExprDelete(db, ref whenExpr); codeTriggerProgram(pParse, p.step_list, orconf); /* Pop the entry off the trigger stack */ pParse.trigStack = trigStackEntry.pNext; #if !SQLITE_OMIT_AUTHORIZATION sqlite3AuthContextPop(sContext); #endif sqlite3VdbeResolveLabel(pParse.pVdbe, endTrigger); } } piOldColMask |= trigStackEntry.oldColMask; // if ( piOldColMask != 0 ) piOldColMask |= trigStackEntry.oldColMask; piNewColMask |= trigStackEntry.newColMask; // if ( piNewColMask != 0 ) piNewColMask |= trigStackEntry.newColMask; return(0); }
/* ** This is called to code FOR EACH ROW triggers. ** ** When the code that this function generates is executed, the following ** must be true: ** ** 1. No cursors may be open in the main database. (But newIdx and oldIdx ** can be indices of cursors in temporary tables. See below.) ** ** 2. If the triggers being coded are ON INSERT or ON UPDATE triggers, then ** a temporary vdbe cursor (index newIdx) must be open and pointing at ** a row containing values to be substituted for new.* expressions in the ** trigger program(s). ** ** 3. If the triggers being coded are ON DELETE or ON UPDATE triggers, then ** a temporary vdbe cursor (index oldIdx) must be open and pointing at ** a row containing values to be substituted for old.* expressions in the ** trigger program(s). ** ** If they are not NULL, the piOldColMask and piNewColMask output variables ** are set to values that describe the columns used by the trigger program ** in the OLD.* and NEW.* tables respectively. If column N of the ** pseudo-table is read at least once, the corresponding bit of the output ** mask is set. If a column with an index greater than 32 is read, the ** output mask is set to the special value 0xffffffff. ** */ static int sqlite3CodeRowTrigger( 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 */ int tr_tm, /* One of TRIGGER_BEFORE, TRIGGER_AFTER */ Table pTab, /* The table to code triggers from */ 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) */ ref u32 piOldColMask,/* OUT: Mask of columns used from the OLD.* table */ ref u32 piNewColMask /* OUT: Mask of columns used from the NEW.* table */ ) { Trigger p; sqlite3 db = pParse.db; TriggerStack trigStackEntry = new TriggerStack(); trigStackEntry.oldColMask = 0; trigStackEntry.newColMask = 0; Debug.Assert( op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE ); Debug.Assert( tr_tm == TRIGGER_BEFORE || tr_tm == TRIGGER_AFTER ); Debug.Assert( newIdx != -1 || oldIdx != -1 ); for ( p = pTrigger ; p != null ; p = p.pNext ) { bool fire_this = false; /* 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. */ Debug.Assert( p.pSchema != null ); Debug.Assert( p.pTabSchema != null ); Debug.Assert( p.pSchema == p.pTabSchema || p.pSchema == db.aDb[1].pSchema ); /* Determine whether we should code this trigger */ if ( p.op == op && p.tr_tm == tr_tm && checkColumnOverlap( p.pColumns, pChanges ) != 0 ) { TriggerStack pS; /* Pointer to trigger-stack entry */ for ( pS = pParse.trigStack ; pS != null && p != pS.pTrigger ; pS = pS.pNext ) { } if ( pS == null ) { fire_this = true; } #if FALSE // * 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; #if !SQLITE_OMIT_TRACE sqlite3VdbeAddOp4( pParse.pVdbe, OP_Trace, 0, 0, 0, sqlite3MPrintf( db, "-- TRIGGER %s", p.name ), P4_DYNAMIC ); #endif sNC = new NameContext();// 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; #if !SQLITE_OMIT_AUTHORIZATION sqlite3AuthContextPush( pParse, sContext, p.name ); #endif /* code the WHEN clause */ endTrigger = sqlite3VdbeMakeLabel( pParse.pVdbe ); whenExpr = sqlite3ExprDup( db, p.pWhen, 0 ); if ( /* db.mallocFailed != 0 || */ sqlite3ResolveExprNames( sNC, ref whenExpr ) != 0 ) { pParse.trigStack = trigStackEntry.pNext; sqlite3ExprDelete( db, ref whenExpr ); return 1; } sqlite3ExprIfFalse( pParse, whenExpr, endTrigger, SQLITE_JUMPIFNULL ); sqlite3ExprDelete( db, ref whenExpr ); codeTriggerProgram( pParse, p.step_list, orconf ); /* Pop the entry off the trigger stack */ pParse.trigStack = trigStackEntry.pNext; #if !SQLITE_OMIT_AUTHORIZATION sqlite3AuthContextPop( sContext ); #endif sqlite3VdbeResolveLabel( pParse.pVdbe, endTrigger ); } } piOldColMask |= trigStackEntry.oldColMask; // if ( piOldColMask != 0 ) piOldColMask |= trigStackEntry.oldColMask; piNewColMask |= trigStackEntry.newColMask; // if ( piNewColMask != 0 ) piNewColMask |= trigStackEntry.newColMask; return 0; }