public uint TriggerColmask(Parse parse, ExprList changes, bool isNew, TRIGGER trtm, Table table, OE orconf) { TK op = (changes != null ? TK.UPDATE : TK.DELETE); int isNewId = (isNew ? 1 : 0); uint mask = 0; for (Trigger p = this; p != null; p = p.Next) { if (p.OP == op && (trtm & p.TRtm) != 0 && CheckColumnOverlap(p.Columns, changes)) { TriggerPrg prg = GetRowTrigger(parse, p, table, orconf); if (prg != null) { mask |= prg.Colmasks[isNewId]; } } } return(mask); }
public void CodeRowTriggerDirect(Parse parse, Table table, int reg, OE orconf, int ignoreJump) { Vdbe v = parse.GetVdbe(); // Main VM TriggerPrg prg = GetRowTrigger(parse, this, table, orconf); Debug.Assert(prg != null || parse.Errs != 0 || parse.Ctx.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 (prg != null) { bool recursive = (Name != null && (parse.Ctx.Flags & Context.FLAG.RecTriggers) == 0); v.AddOp3(Core.OP.Program, reg, ignoreJump, ++parse.Mems); v.ChangeP4(-1, prg.Program, Vdbe.P4T.SUBPROGRAM); #if DEBUG v.Comment("Call: %s.%s", (!string.IsNullOrEmpty(p.Name) ? p.Name : "fkey"), OnErrorText(orconf)); #endif // Set the P5 operand of the OP_Program instruction to non-zero if recursive invocation of this trigger program is disallowed. Recursive // invocation is disallowed if (a) the sub-program is really a trigger, not a foreign key action, and (b) the flag to enable recursive triggers is clear. v.ChangeP5((int)(recursive ? 1 : 0)); } }
/* ** Create and populate a new TriggerPrg object with a sub-program ** implementing trigger pTrigger with ON CONFLICT policy orconf. */ static TriggerPrg codeRowTrigger( Parse pParse, /* Current parse context */ Trigger pTrigger, /* Trigger to code */ Table pTab, /* The table pTrigger is attached to */ int orconf /* ON CONFLICT policy to code trigger program with */ ) { Parse pTop = sqlite3ParseToplevel( pParse ); sqlite3 db = pParse.db; /* Database handle */ TriggerPrg pPrg; /* Value to return */ Expr pWhen = null; /* Duplicate of trigger WHEN expression */ Vdbe v; /* Temporary VM */ NameContext sNC; /* Name context for sub-vdbe */ SubProgram pProgram = null; /* Sub-vdbe for trigger program */ Parse pSubParse; /* Parse context for sub-vdbe */ int iEndTrigger = 0; /* Label to jump to if WHEN is false */ Debug.Assert( pTrigger.zName == null || pTab == tableOfTrigger( pTrigger ) ); Debug.Assert( pTop.pVdbe != null ); /* Allocate the TriggerPrg and SubProgram objects. To ensure that they ** are freed if an error occurs, link them into the Parse.pTriggerPrg ** list of the top-level Parse object sooner rather than later. */ pPrg = new TriggerPrg();// sqlite3DbMallocZero( db, sizeof( TriggerPrg ) ); //if ( null == pPrg ) return 0; pPrg.pNext = pTop.pTriggerPrg; pTop.pTriggerPrg = pPrg; pPrg.pProgram = pProgram = new SubProgram();// sqlite3DbMallocZero( db, sizeof( SubProgram ) ); //if( null==pProgram ) return 0; sqlite3VdbeLinkSubProgram( pTop.pVdbe, pProgram ); pPrg.pTrigger = pTrigger; pPrg.orconf = orconf; pPrg.aColmask[0] = 0xffffffff; pPrg.aColmask[1] = 0xffffffff; /* Allocate and populate a new Parse context to use for coding the ** trigger sub-program. */ pSubParse = new Parse();// sqlite3StackAllocZero( db, sizeof( Parse ) ); //if ( null == pSubParse ) return null; sNC = new NameContext();// memset( &sNC, 0, sizeof( sNC ) ); sNC.pParse = pSubParse; pSubParse.db = db; pSubParse.pTriggerTab = pTab; pSubParse.pToplevel = pTop; pSubParse.zAuthContext = pTrigger.zName; pSubParse.eTriggerOp = pTrigger.op; pSubParse.nQueryLoop = pParse.nQueryLoop; v = sqlite3GetVdbe( pSubParse ); if ( v != null ) { #if SQLITE_DEBUG VdbeComment( v, "Start: %s.%s (%s %s%s%s ON %s)", pTrigger.zName != null ? pTrigger.zName : "", onErrorText( orconf ), ( pTrigger.tr_tm == TRIGGER_BEFORE ? "BEFORE" : "AFTER" ), ( pTrigger.op == TK_UPDATE ? "UPDATE" : "" ), ( pTrigger.op == TK_INSERT ? "INSERT" : "" ), ( pTrigger.op == TK_DELETE ? "DELETE" : "" ), pTab.zName ); #endif #if !SQLITE_OMIT_TRACE sqlite3VdbeChangeP4( v, -1, sqlite3MPrintf( db, "-- TRIGGER %s", pTrigger.zName ), P4_DYNAMIC ); #endif /* If one was specified, code the WHEN clause. If it evaluates to false ** (or NULL) the sub-vdbe is immediately halted by jumping to the ** OP_Halt inserted at the end of the program. */ if ( pTrigger.pWhen != null ) { pWhen = sqlite3ExprDup( db, pTrigger.pWhen, 0 ); if ( SQLITE_OK == sqlite3ResolveExprNames( sNC, ref pWhen ) //&& db.mallocFailed==0 ) { iEndTrigger = sqlite3VdbeMakeLabel( v ); sqlite3ExprIfFalse( pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL ); } sqlite3ExprDelete( db, ref pWhen ); } /* Code the trigger program into the sub-vdbe. */ codeTriggerProgram( pSubParse, pTrigger.step_list, orconf ); /* Insert an OP_Halt at the end of the sub-program. */ if ( iEndTrigger != 0 ) { sqlite3VdbeResolveLabel( v, iEndTrigger ); } sqlite3VdbeAddOp0( v, OP_Halt ); #if SQLITE_DEBUG VdbeComment( v, "End: %s.%s", pTrigger.zName, onErrorText( orconf ) ); #endif transferParseError( pParse, pSubParse ); //if( db.mallocFailed==0 ){ pProgram.aOp = sqlite3VdbeTakeOpArray( v, ref pProgram.nOp, ref pTop.nMaxArg ); //} pProgram.nMem = pSubParse.nMem; pProgram.nCsr = pSubParse.nTab; pProgram.token = pTrigger.GetHashCode(); pPrg.aColmask[0] = pSubParse.oldmask; pPrg.aColmask[1] = pSubParse.newmask; sqlite3VdbeDelete( ref v ); } Debug.Assert( null == pSubParse.pAinc && null == pSubParse.pZombieTab ); Debug.Assert( null == pSubParse.pTriggerPrg && 0 == pSubParse.nMaxArg ); //sqlite3StackFree(db, pSubParse); return pPrg; }
public static RC Prepare_(Context ctx, string sql, int bytes, bool isPrepareV2, Vdbe reprepare, ref Vdbe stmtOut, ref string tailOut) { stmtOut = null; tailOut = null; string errMsg = null; // Error message RC rc = RC.OK; int i; // Allocate the parsing context Parse parse = new Parse(); // Parsing context if (parse == null) { rc = RC.NOMEM; goto end_prepare; } parse.Reprepare = reprepare; parse.LastToken.data = null; //: C#? Debug.Assert(tailOut == null); Debug.Assert(!ctx.MallocFailed); Debug.Assert(MutexEx.Held(ctx.Mutex)); // Check to verify that it is possible to get a read lock on all database schemas. The inability to get a read lock indicates that // some other database connection is holding a write-lock, which in turn means that the other connection has made uncommitted changes // to the schema. // // Were we to proceed and prepare the statement against the uncommitted schema changes and if those schema changes are subsequently rolled // back and different changes are made in their place, then when this prepared statement goes to run the schema cookie would fail to detect // the schema change. Disaster would follow. // // This thread is currently holding mutexes on all Btrees (because of the sqlite3BtreeEnterAll() in sqlite3LockAndPrepare()) so it // is not possible for another thread to start a new schema change while this routine is running. Hence, we do not need to hold // locks on the schema, we just need to make sure nobody else is holding them. // // Note that setting READ_UNCOMMITTED overrides most lock detection, but it does *not* override schema lock detection, so this all still // works even if READ_UNCOMMITTED is set. for (i = 0; i < ctx.DBs.length; i++) { Btree bt = ctx.DBs[i].Bt; if (bt != null) { Debug.Assert(bt.HoldsMutex()); rc = bt.SchemaLocked(); if (rc != 0) { string dbName = ctx.DBs[i].Name; sqlite3Error(ctx, rc, "database schema is locked: %s", dbName); C.ASSERTCOVERAGE((ctx.Flags & Context.FLAG.ReadUncommitted) != 0); goto end_prepare; } } } VTable.UnlockList(ctx); parse.Ctx = ctx; parse.QueryLoops = (double)1; if (bytes >= 0 && (bytes == 0 || sql[bytes - 1] != 0)) { int maxLen = ctx.aLimit[SQLITE_LIMIT_SQL_LENGTH]; C.ASSERTCOVERAGE(bytes == maxLen); C.ASSERTCOVERAGE(bytes == maxLen + 1); if (bytes > maxLen) { sqlite3Error(ctx, RC.TOOBIG, "statement too long"); rc = SysEx.ApiExit(ctx, RC.TOOBIG); goto end_prepare; } string sqlCopy = sql.Substring(0, bytes); if (sqlCopy != null) { parse.RunParser(sqlCopy, ref errMsg); C._tagfree(ctx, ref sqlCopy); parse.Tail = null; //: &sql[parse->Tail - sqlCopy]; } else { parse.Tail = null; //: &sql[bytes]; } } else { parse.RunParser(sql, ref errMsg); } Debug.Assert((int)parse.QueryLoops == 1); if (ctx.MallocFailed) { parse.RC = RC.NOMEM; } if (parse.RC == RC.DONE) { parse.RC = RC.OK; } if (parse.CheckSchema != 0) { SchemaIsValid(parse); } if (ctx.MallocFailed) { parse.RC = RC.NOMEM; } tailOut = (parse.Tail == null ? null : parse.Tail.ToString()); rc = parse.RC; Vdbe v = parse.V; #if !OMIT_EXPLAIN if (rc == RC.OK && parse.V != null && parse.Explain != 0) { int first, max; if (parse.Explain == 2) { v.SetNumCols(4); first = 8; max = 12; } else { v.SetNumCols(8); first = 0; max = 8; } for (i = first; i < max; i++) { v.SetColName(i - first, COLNAME_NAME, _colName[i], C.DESTRUCTOR_STATIC); } } #endif Debug.Assert(!ctx.Init.Busy || !isPrepareV2); if (!ctx.Init.Busy) { Vdbe.SetSql(v, sql, (int)(sql.Length - (parse.Tail == null ? 0 : parse.Tail.Length)), isPrepareV2); } if (v != null && (rc != RC.OK || ctx.MallocFailed)) { v.Finalize(); Debug.Assert(stmtOut == null); } else { stmtOut = v; } if (errMsg != null) { sqlite3Error(ctx, rc, "%s", errMsg); C._tagfree(ctx, ref errMsg); } else { sqlite3Error(ctx, rc, null); } // Delete any TriggerPrg structures allocated while parsing this statement. while (parse.TriggerPrg != null) { TriggerPrg t = parse.TriggerPrg; parse.TriggerPrg = t.Next; C._tagfree(ctx, ref t); } end_prepare: //sqlite3StackFree( db, pParse ); rc = SysEx.ApiExit(ctx, rc); Debug.Assert((RC)((int)rc & ctx.ErrMask) == rc); return(rc); }
/* ** Compile the UTF-8 encoded SQL statement zSql into a statement handle. */ static int sqlite3Prepare( sqlite3 db, /* Database handle. */ string zSql, /* UTF-8 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ int saveSqlFlag, /* True to copy SQL text into the sqlite3_stmt */ Vdbe pReprepare, /* VM being reprepared */ ref sqlite3_stmt ppStmt, /* OUT: A pointer to the prepared statement */ ref string pzTail /* OUT: End of parsed string */ ) { Parse pParse; /* Parsing context */ string zErrMsg = ""; /* Error message */ int rc = SQLITE_OK; /* Result code */ int i; /* Loop counter */ ppStmt = null; pzTail = null; /* Allocate the parsing context */ pParse = new Parse();//sqlite3StackAllocZero(db, sizeof(*pParse)); //if ( pParse == null ) //{ // rc = SQLITE_NOMEM; // goto end_prepare; //} pParse.pReprepare = pReprepare; pParse.sLastToken.z = ""; // assert( ppStmt && *ppStmt==0 ); //Debug.Assert( 0 == db.mallocFailed ); Debug.Assert(sqlite3_mutex_held(db.mutex)); /* Check to verify that it is possible to get a read lock on all ** database schemas. The inability to get a read lock indicates that ** some other database connection is holding a write-lock, which in ** turn means that the other connection has made uncommitted changes ** to the schema. ** ** Were we to proceed and prepare the statement against the uncommitted ** schema changes and if those schema changes are subsequently rolled ** back and different changes are made in their place, then when this ** prepared statement goes to run the schema cookie would fail to detect ** the schema change. Disaster would follow. ** ** This thread is currently holding mutexes on all Btrees (because ** of the sqlite3BtreeEnterAll() in sqlite3LockAndPrepare()) so it ** is not possible for another thread to start a new schema change ** while this routine is running. Hence, we do not need to hold ** locks on the schema, we just need to make sure nobody else is ** holding them. ** ** Note that setting READ_UNCOMMITTED overrides most lock detection, ** but it does *not* override schema lock detection, so this all still ** works even if READ_UNCOMMITTED is set. */ for (i = 0; i < db.nDb; i++) { Btree pBt = db.aDb[i].pBt; if (pBt != null) { Debug.Assert(sqlite3BtreeHoldsMutex(pBt)); rc = sqlite3BtreeSchemaLocked(pBt); if (rc != 0) { string zDb = db.aDb[i].zName; sqlite3Error(db, rc, "database schema is locked: %s", zDb); testcase(db.flags & SQLITE_ReadUncommitted); goto end_prepare; } } } sqlite3VtabUnlockList(db); pParse.db = db; pParse.nQueryLoop = (double)1; if (nBytes >= 0 && (nBytes == 0 || zSql[nBytes - 1] != 0)) { string zSqlCopy; int mxLen = db.aLimit[SQLITE_LIMIT_SQL_LENGTH]; testcase(nBytes == mxLen); testcase(nBytes == mxLen + 1); if (nBytes > mxLen) { sqlite3Error(db, SQLITE_TOOBIG, "statement too long"); rc = sqlite3ApiExit(db, SQLITE_TOOBIG); goto end_prepare; } zSqlCopy = zSql.Substring(0, nBytes);// sqlite3DbStrNDup(db, zSql, nBytes); if (zSqlCopy != null) { sqlite3RunParser(pParse, zSqlCopy, ref zErrMsg); sqlite3DbFree(db, ref zSqlCopy); //pParse->zTail = &zSql[pParse->zTail-zSqlCopy]; } else { //pParse->zTail = &zSql[nBytes]; } } else { sqlite3RunParser(pParse, zSql, ref zErrMsg); } Debug.Assert(1 == (int)pParse.nQueryLoop); //if ( db.mallocFailed != 0 ) //{ // pParse.rc = SQLITE_NOMEM; //} if (pParse.rc == SQLITE_DONE) { pParse.rc = SQLITE_OK; } if (pParse.checkSchema != 0) { schemaIsValid(pParse); } //if ( db.mallocFailed != 0 ) //{ // pParse.rc = SQLITE_NOMEM; //} //if (pzTail != null) { pzTail = pParse.zTail == null ? "" : pParse.zTail.ToString(); } rc = pParse.rc; #if !SQLITE_OMIT_EXPLAIN if (rc == SQLITE_OK && pParse.pVdbe != null && pParse.explain != 0) { string[] azColName = new string[] { "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", "selectid", "order", "from", "detail" }; int iFirst, mx; if (pParse.explain == 2) { sqlite3VdbeSetNumCols(pParse.pVdbe, 4); iFirst = 8; mx = 12; } else { sqlite3VdbeSetNumCols(pParse.pVdbe, 8); iFirst = 0; mx = 8; } for (i = iFirst; i < mx; i++) { sqlite3VdbeSetColName(pParse.pVdbe, i - iFirst, COLNAME_NAME, azColName[i], SQLITE_STATIC); } } #endif Debug.Assert(db.init.busy == 0 || saveSqlFlag == 0); if (db.init.busy == 0) { Vdbe pVdbe = pParse.pVdbe; sqlite3VdbeSetSql(pVdbe, zSql, (int)(zSql.Length - (pParse.zTail == null ? 0 : pParse.zTail.Length)), saveSqlFlag); } if (pParse.pVdbe != null && (rc != SQLITE_OK /*|| db.mallocFailed != 0 */)) { sqlite3VdbeFinalize(ref pParse.pVdbe); //Debug.Assert( ppStmt == null ); } else { ppStmt = pParse.pVdbe; } if (zErrMsg != "") { sqlite3Error(db, rc, "%s", zErrMsg); sqlite3DbFree(db, ref zErrMsg); } else { sqlite3Error(db, rc, 0); } /* Delete any TriggerPrg structures allocated while parsing this statement. */ while (pParse.pTriggerPrg != null) { TriggerPrg pT = pParse.pTriggerPrg; pParse.pTriggerPrg = pT.pNext; sqlite3DbFree(db, ref pT); } end_prepare: //sqlite3StackFree( db, pParse ); rc = sqlite3ApiExit(db, rc); Debug.Assert((rc & db.errMask) == rc); return(rc); }
static TriggerPrg CodeRowTrigger(Parse parse, Trigger trigger, Table table, OE orconf) { Parse top = E.Parse_Toplevel(parse); Context ctx = parse.Ctx; // Database handle Debug.Assert(trigger.Name == null || table == TableOfTrigger(trigger)); Debug.Assert(top.V != null); // Allocate the TriggerPrg and SubProgram objects. To ensure that they are freed if an error occurs, link them into the Parse.pTriggerPrg // list of the top-level Parse object sooner rather than later. TriggerPrg prg = new TriggerPrg(); // Value to return //: _tagalloc(ctx, sizeof(TriggerPrg), true); if (prg == null) { return(null); } prg.Next = top.TriggerPrg; top.TriggerPrg = prg; Vdbe.SubProgram program; // Sub-vdbe for trigger program prg.Program = program = new Vdbe.SubProgram(); // sqlite3DbMallocZero( db, sizeof( SubProgram ) ); if (program == null) { return(null); } top.V.LinkSubProgram(program); prg.Trigger = trigger; prg.Orconf = orconf; prg.Colmasks[0] = 0xffffffff; prg.Colmasks[1] = 0xffffffff; // Allocate and populate a new Parse context to use for coding the trigger sub-program. Parse subParse = new Parse(); // Parse context for sub-vdbe //: _stackalloc(ctx, sizeof(Parse), true); if (subParse == null) { return(null); } NameContext sNC = new NameContext(); // Name context for sub-vdbe sNC.Parse = subParse; subParse.Ctx = ctx; subParse.TriggerTab = table; subParse.Toplevel = top; subParse.AuthContext = trigger.Name; subParse.TriggerOp = trigger.OP; subParse.QueryLoops = parse.QueryLoops; int endTrigger = 0; // Label to jump to if WHEN is false Vdbe v = subParse.GetVdbe(); // Temporary VM if (v != null) { #if DEBUG v.Comment("Start: %s.%s (%s %s%s%s ON %s)", trigger.Name, OnErrorText(orconf), (trigger.TRtm == TRIGGER.BEFORE ? "BEFORE" : "AFTER"), (trigger.OP == TK.UPDATE ? "UPDATE" : string.Empty), (trigger.OP == TK.INSERT ? "INSERT" : string.Empty), (trigger.OP == TK.DELETE ? "DELETE" : string.Empty), table.Name); #endif #if !OMIT_TRACE v.ChangeP4(-1, C._mtagprintf(ctx, "-- TRIGGER %s", trigger.Name), Vdbe.P4T.DYNAMIC); #endif // If one was specified, code the WHEN clause. If it evaluates to false (or NULL) the sub-vdbe is immediately halted by jumping to the // OP_Halt inserted at the end of the program. if (trigger.When != null) { Expr when = Expr.Dup(ctx, trigger.When, 0); // Duplicate of trigger WHEN expression if (ResolveExprNames(sNC, ref when) == RC.OK && !ctx.MallocFailed) { endTrigger = v.MakeLabel(); subParse.IfFalse(when, endTrigger, RC_JUMPIFNULL); } Expr.Delete(ctx, ref when); } // Code the trigger program into the sub-vdbe. CodeTriggerProgram(subParse, trigger.StepList, orconf); // Insert an OP_Halt at the end of the sub-program. if (endTrigger != 0) { v.ResolveLabel(endTrigger); } v.AddOp0(Core.OP.Halt); #if DEBUG v.Comment("End: %s.%s", trigger.Name, OnErrorText(orconf)); #endif TransferParseError(parse, subParse); if (!ctx.MallocFailed) { program.Ops.data = v.TakeOpArray(ref program.Ops.length, ref top.MaxArgs); } program.Mems = subParse.Mems; program.Csrs = subParse.Tabs; program.Token = trigger.GetHashCode(); prg.Colmasks[0] = subParse.Oldmask; prg.Colmasks[1] = subParse.Newmask; Vdbe.Delete(v); } Debug.Assert(subParse.Ainc == null && subParse.ZombieTab == null); Debug.Assert(subParse.TriggerPrg == null && subParse.MaxArgs == 0); C._stackfree(ctx, ref subParse); return(prg); }
static TriggerPrg CodeRowTrigger(Parse parse, Trigger trigger, Table table, OE orconf) { Parse top = E.Parse_Toplevel(parse); Context ctx = parse.Ctx; // Database handle Debug.Assert(trigger.Name == null || table == TableOfTrigger(trigger)); Debug.Assert(top.V != null); // Allocate the TriggerPrg and SubProgram objects. To ensure that they are freed if an error occurs, link them into the Parse.pTriggerPrg // list of the top-level Parse object sooner rather than later. TriggerPrg prg = new TriggerPrg(); // Value to return //: _tagalloc(ctx, sizeof(TriggerPrg), true); if (prg == null) return null; prg.Next = top.TriggerPrg; top.TriggerPrg = prg; Vdbe.SubProgram program; // Sub-vdbe for trigger program prg.Program = program = new Vdbe.SubProgram();// sqlite3DbMallocZero( db, sizeof( SubProgram ) ); if (program == null) return null; top.V.LinkSubProgram(program); prg.Trigger = trigger; prg.Orconf = orconf; prg.Colmasks[0] = 0xffffffff; prg.Colmasks[1] = 0xffffffff; // Allocate and populate a new Parse context to use for coding the trigger sub-program. Parse subParse = new Parse(); // Parse context for sub-vdbe //: _stackalloc(ctx, sizeof(Parse), true); if (subParse == null) return null; NameContext sNC = new NameContext(); // Name context for sub-vdbe sNC.Parse = subParse; subParse.Ctx = ctx; subParse.TriggerTab = table; subParse.Toplevel = top; subParse.AuthContext = trigger.Name; subParse.TriggerOp = trigger.OP; subParse.QueryLoops = parse.QueryLoops; int endTrigger = 0; // Label to jump to if WHEN is false Vdbe v = subParse.GetVdbe(); // Temporary VM if (v != null) { #if DEBUG v.Comment("Start: %s.%s (%s %s%s%s ON %s)", trigger.Name, OnErrorText(orconf), (trigger.TRtm == TRIGGER.BEFORE ? "BEFORE" : "AFTER"), (trigger.OP == TK.UPDATE ? "UPDATE" : string.Empty), (trigger.OP == TK.INSERT ? "INSERT" : string.Empty), (trigger.OP == TK.DELETE ? "DELETE" : string.Empty), table.Name); #endif #if !OMIT_TRACE v.ChangeP4(-1, C._mtagprintf(ctx, "-- TRIGGER %s", trigger.Name), Vdbe.P4T.DYNAMIC); #endif // If one was specified, code the WHEN clause. If it evaluates to false (or NULL) the sub-vdbe is immediately halted by jumping to the // OP_Halt inserted at the end of the program. if (trigger.When != null) { Expr when = Expr.Dup(ctx, trigger.When, 0); // Duplicate of trigger WHEN expression if (ResolveExprNames(sNC, ref when) == RC.OK && !ctx.MallocFailed) { endTrigger = v.MakeLabel(); subParse.IfFalse(when, endTrigger, RC_JUMPIFNULL); } Expr.Delete(ctx, ref when); } // Code the trigger program into the sub-vdbe. CodeTriggerProgram(subParse, trigger.StepList, orconf); // Insert an OP_Halt at the end of the sub-program. if (endTrigger != 0) v.ResolveLabel(endTrigger); v.AddOp0(Core.OP.Halt); #if DEBUG v.Comment("End: %s.%s", trigger.Name, OnErrorText(orconf)); #endif TransferParseError(parse, subParse); if (!ctx.MallocFailed) program.Ops.data = v.TakeOpArray(ref program.Ops.length, ref top.MaxArgs); program.Mems = subParse.Mems; program.Csrs = subParse.Tabs; program.Token = trigger.GetHashCode(); prg.Colmasks[0] = subParse.Oldmask; prg.Colmasks[1] = subParse.Newmask; Vdbe.Delete(v); } Debug.Assert(subParse.Ainc == null && subParse.ZombieTab == null); Debug.Assert(subParse.TriggerPrg == null && subParse.MaxArgs == 0); C._stackfree(ctx, ref subParse); return prg; }