static TriggerPrg GetRowTrigger(Parse parse, Trigger trigger, Table table, OE orconf) { Parse root = E.Parse_Toplevel(parse); Debug.Assert(trigger.Name == null || table == TableOfTrigger(trigger)); // 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 TriggerPrg.pTrigger field will be present somewhere in the Parse.pTriggerPrg list. Search for such an entry. TriggerPrg prg; for (prg = root.TriggerPrg; prg != null && (prg.Trigger != trigger || prg.Orconf != orconf); prg = prg.Next) ; // If an existing TriggerPrg could not be located, create a new one. if (prg == null) prg = CodeRowTrigger(parse, trigger, table, orconf); return prg; }
public static void BeginTrigger(Parse parse, Token name1, Token name2, TK trTm, TK op, IdList columns, SrcList tableName, Expr when, bool isTemp, int noErr) { Context ctx = parse.Ctx; // The database connection Debug.Assert(name1 != null); // pName1.z might be NULL, but not pName1 itself Debug.Assert(name2 != null); Debug.Assert(op == TK.INSERT || op == TK.UPDATE || op == TK.DELETE); Debug.Assert(op > 0 && op < (TK)0xff); Trigger trigger = null; // The new trigger int db; // The database to store the trigger in Token name = null; // The unqualified db name if (isTemp) { // If TEMP was specified, then the trigger name may not be qualified. if (name2.length > 0) { parse.ErrorMsg("temporary trigger may not have qualified name"); goto trigger_cleanup; } db = 1; name = name1; } else { // Figure out the db that the the trigger will be created in db = parse.TwoPartName(name1, name2, ref name); if (db < 0) goto trigger_cleanup; } if (tableName == null || ctx.MallocFailed) goto trigger_cleanup; // A long-standing parser bug is that this syntax was allowed: // CREATE TRIGGER attached.demo AFTER INSERT ON attached.tab .... // ^^^^^^^^ // To maintain backwards compatibility, ignore the database name on pTableName if we are reparsing our of SQLITE_MASTER. if (ctx.Init.Busy && db != 1) { C._tagfree(ctx, ref tableName.Ids[0].Database); tableName.Ids[0].Database = null; } // If the trigger name was unqualified, and the table is a temp table, then set iDb to 1 to create the trigger in the temporary database. // If sqlite3SrcListLookup() returns 0, indicating the table does not exist, the error is caught by the block below. //? if (tableName == null) goto trigger_cleanup; Table table = Delete.SrcListLookup(parse, tableName); // Table that the trigger fires off of if (ctx.Init.Busy == null && name2.length == 0 && table != null && table.Schema == ctx.DBs[1].Schema) db = 1; // Ensure the table name matches database name and that the table exists if (ctx.MallocFailed) goto trigger_cleanup; Debug.Assert(tableName.Srcs == 1); DbFixer sFix = new DbFixer(); // State vector for the DB fixer if (sFix.FixInit(parse, db, "trigger", name) && sFix.FixSrcList(tableName)) goto trigger_cleanup; table = Delete.SrcListLookup(parse, tableName); if (table == null) { // The table does not exist. if (ctx.Init.DB == 1) { // Ticket #3810. // Normally, whenever a table is dropped, all associated triggers are dropped too. But if a TEMP trigger is created on a non-TEMP table // and the table is dropped by a different database connection, the trigger is not visible to the database connection that does the // drop so the trigger cannot be dropped. This results in an "orphaned trigger" - a trigger whose associated table is missing. ctx.Init.OrphanTrigger = true; } goto trigger_cleanup; } if (E.IsVirtual(table)) { parse.ErrorMsg("cannot create triggers on virtual tables"); goto trigger_cleanup; } // Check that the trigger name is not reserved and that no trigger of the specified name exists string nameAsString = Parse.NameFromToken(ctx, name); if (nameAsString == null || parse.CheckObjectName(nameAsString) != RC.OK) goto trigger_cleanup; Debug.Assert(Btree.SchemaMutexHeld(ctx, db, null)); if (ctx.DBs[db].Schema.TriggerHash.Find(nameAsString, nameAsString.Length, (Trigger)null) != null) { if (noErr == 0) parse.ErrorMsg("trigger %T already exists", name); else { Debug.Assert(!ctx.Init.Busy); parse.CodeVerifySchema(db); } goto trigger_cleanup; } // Do not create a trigger on a system table if (table.Name.StartsWith("sqlite_", StringComparison.InvariantCultureIgnoreCase)) { parse.ErrorMsg("cannot create trigger on system table"); parse.Errs++; goto trigger_cleanup; } // INSTEAD of triggers are only for views and views only support INSTEAD of triggers. if (table.Select != null && trTm != TK.INSTEAD) { parse.ErrorMsg("cannot create %s trigger on view: %S", (trTm == TK.BEFORE ? "BEFORE" : "AFTER"), tableName, 0); goto trigger_cleanup; } if (table.Select == null && trTm == TK.INSTEAD) { parse.ErrorMsg("cannot create INSTEAD OF trigger on table: %S", tableName, 0); goto trigger_cleanup; } #if !OMIT_AUTHORIZATION { int tabDb = Prepare.SchemaToIndex(ctx, table.Schema); // Index of the database holding pTab AUTH code = AUTH.CREATE_TRIGGER; string dbName = ctx.DBs[tabDb].Name; string dbTrigName = (isTemp ? ctx.DBs[1].Name : dbName); if (tabDb == 1 || isTemp) code = AUTH.CREATE_TEMP_TRIGGER; if (Auth.Check(parse, code, nameAsString, table.Name, dbTrigName) != 0 || Auth.Check(parse, AUTH.INSERT, E.SCHEMA_TABLE(tabDb), 0, dbName)) goto trigger_cleanup; } #endif // INSTEAD OF triggers can only appear on views and BEFORE triggers cannot appear on views. So we might as well translate every // INSTEAD OF trigger into a BEFORE trigger. It simplifies code elsewhere. if (trTm == TK.INSTEAD) trTm = TK.BEFORE; // Build the Trigger object trigger = new Trigger(); //: (Trigger *)_tagalloc(db, sizeof(Trigger), true); if (trigger == null) goto trigger_cleanup; trigger.Name = name; trigger.Table = tableName.Ids[0].Name; //: _tagstrdup(ctx, tableName->Ids[0].Name); trigger.Schema = ctx.DBs[db].Schema; trigger.TabSchema = table.Schema; trigger.OP = op; trigger.TRtm = (trTm == TK.BEFORE ? TRIGGER.BEFORE : TRIGGER.AFTER); trigger.When = Expr.Dup(db, when, E.EXPRDUP_REDUCE); trigger.Columns = Expr.IdListDup(ctx, columns); Debug.Assert(parse.NewTrigger == null); parse.NewTrigger = trigger; trigger_cleanup: C._tagfree(ctx, ref name); Expr.SrcListDelete(ctx, ref tableName); Expr.IdListDelete(ctx, ref columns); Expr.Delete(ctx, ref when); if (parse.NewTrigger == null) DeleteTrigger(ctx, ref trigger); else Debug.Assert(parse.NewTrigger == trigger); }
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 Table TableOfTrigger(Trigger trigger) { int tableLength = trigger.Table.Length; return trigger.TabSchema.TableHash.Find(trigger.Table, tableLength, (Table)null); }
public static void DropTriggerPtr(Parse parse, Trigger trigger) { Context ctx = parse.Ctx; int db = Prepare.SchemaToIndex(ctx, trigger.Schema); Debug.Assert(db >= 0 && db < ctx.DBs.length); Table table = TableOfTrigger(trigger); Debug.Assert(table != null); Debug.Assert(table.Schema == trigger.Schema || db == 1); #if !OMIT_AUTHORIZATION { AUTH code = AUTH.DROP_TRIGGER; string dbName = ctx.DBs[db].Name; string tableName = E.SCHEMA_TABLE(db); if (db == 1) code = AUTH.DROP_TEMP_TRIGGER; if (Auth.Check(parse, code, trigger.Name, table.Name, dbName) || Auth.Check(parse, AUTH.DELETE, tableName, null, dbName)) return; } #endif // Generate code to destroy the database record of the trigger. Debug.Assert(table != null); Vdbe v = parse.GetVdbe(); if (v != null) { parse.BeginWriteOperation(0, db); parse.OpenMasterTable(db); int base_ = v.AddOpList(_dropTrigger.Length, _dropTrigger); v.ChangeP4(base_ + 1, trigger.Name, Vdbe.P4T.TRANSIENT); v.ChangeP4(base_ + 4, "trigger", Vdbe.P4T.STATIC); parse.ChangeCookie(db); v.AddOp2(Core.OP.Close, 0, 0); v.AddOp4(Core.OP.DropTrigger, db, 0, 0, trigger.Name, 0); if (parse.Mems < 3) parse.Mems = 3; } }
static Trigger FKActionTrigger(Parse parse, Table table, FKey fkey, ExprList changes) { Context ctx = parse.Ctx; // Database handle int actionId = (changes != null ? 1 : 0); // 1 for UPDATE, 0 for DELETE OE action = fkey.Actions[actionId]; // One of OE_None, OE_Cascade etc. Trigger trigger = fkey.Triggers[actionId]; // Trigger definition to return if (action != OE.None && trigger == null) { Index index = null; // Parent key index for this FK int[] cols = null; // child table cols . parent key cols if (LocateFkeyIndex(parse, table, fkey, out index, out cols) != 0) return null; Debug.Assert(cols != null || fkey.Cols.length == 1); Expr where_ = null; // WHERE clause of trigger step Expr when = null; // WHEN clause for the trigger ExprList list = null; // Changes list if ON UPDATE CASCADE for (int i = 0; i < fkey.Cols.length; i++) { Token oldToken = new Token("old", 3); // Literal "old" token Token newToken = new Token("new", 3); // Literal "new" token int fromColId = (cols != null ? cols[i] : fkey.Cols[0].From); // Idx of column in child table Debug.Assert(fromColId >= 0); Token fromCol = new Token(); // Name of column in child table Token toCol = new Token(); // Name of column in parent table toCol.data = (index != null ? table.Cols[index.Columns[i]].Name : "oid"); fromCol.data = fkey.From.Cols[fromColId].Name; toCol.length = (uint)toCol.data.Length; fromCol.length = (uint)fromCol.data.Length; // Create the expression "OLD.zToCol = zFromCol". It is important that the "OLD.zToCol" term is on the LHS of the = operator, so // that the affinity and collation sequence associated with the parent table are used for the comparison. Expr eq = Expr.PExpr(parse, TK.EQ, Expr.PExpr(parse, TK.DOT, Expr.PExpr(parse, TK.ID, null, null, oldToken), Expr.PExpr(parse, TK.ID, null, null, toCol) , 0), Expr.PExpr(parse, TK.ID, null, null, fromCol) , 0); // tFromCol = OLD.tToCol where_ = Expr.And(ctx, where_, eq); // For ON UPDATE, construct the next term of the WHEN clause. The final WHEN clause will be like this: // // WHEN NOT(old.col1 IS new.col1 AND ... AND old.colN IS new.colN) if (changes != null) { eq = Expr.PExpr(parse, TK.IS, Expr.PExpr(parse, TK.DOT, Expr.PExpr(parse, TK.ID, null, null, oldToken), Expr.PExpr(parse, TK.ID, null, null, toCol), 0), Expr.PExpr(parse, TK.DOT, Expr.PExpr(parse, TK.ID, null, null, newToken), Expr.PExpr(parse, TK.ID, null, null, toCol), 0), 0); when = Expr.And(ctx, when, eq); } if (action != OE.Restrict && (action != OE.Cascade || changes != null)) { Expr newExpr; if (action == OE.Cascade) newExpr = Expr.PExpr(parse, TK.DOT, Expr.PExpr(parse, TK.ID, null, null, newToken), Expr.PExpr(parse, TK.ID, null, null, toCol) , 0); else if (action == OE.SetDflt) { Expr dfltExpr = fkey.From.Cols[fromColId].Dflt; if (dfltExpr != null) newExpr = Expr.Dup(ctx, dfltExpr, 0); else newExpr = Expr.PExpr(parse, TK.NULL, 0, 0, 0); } else newExpr = Expr.PExpr(parse, TK.NULL, 0, 0, 0); list = Expr.ListAppend(parse, list, newExpr); Expr.ListSetName(parse, list, fromCol, 0); } } C._tagfree(ctx, ref cols); string fromName = fkey.From.Name; // Name of child table int fromNameLength = fromName.Length; // Length in bytes of zFrom Select select = null; // If RESTRICT, "SELECT RAISE(...)" if (action == OE.Restrict) { Token from = new Token(); from.data = fromName; from.length = fromNameLength; Expr raise = Expr.Expr(ctx, TK.RAISE, "foreign key constraint failed"); if (raise != null) raise.Affinity = OE.Abort; select = Select.New(parse, Expr.ListAppend(parse, 0, raise), SrcListAppend(ctx, 0, from, null), where_, null, null, null, 0, null, null); where_ = null; } // Disable lookaside memory allocation bool enableLookaside = ctx.Lookaside.Enabled; // Copy of ctx->lookaside.bEnabled ctx.Lookaside.Enabled = false; trigger = new Trigger(); //: trigger = (Trigger *)_tagalloc(ctx, //: sizeof(Trigger) + // Trigger //: sizeof(TriggerStep) + // Single step in trigger program //: fromNameLength + 1 // Space for pStep->target.z //: , true); TriggerStep step = null; // First (only) step of trigger program if (trigger != null) { step = trigger.StepList = new TriggerStep(); //: (TriggerStep)trigger[1]; step.Target.data = fromName; //: (char *)&step[1]; step.Target.length = fromNameLength; //: _memcpy((const char *)step->Target.data, fromName, fromNameLength); step.Where = Expr.Dup(ctx, where_, EXPRDUP_REDUCE); step.ExprList = Expr.ListDup(ctx, list, EXPRDUP_REDUCE); step.Select = Select.Dup(ctx, select, EXPRDUP_REDUCE); if (when != null) { when = Expr.PExpr(parse, TK.NOT, when, 0, 0); trigger.When = Expr.Dup(ctx, when, EXPRDUP_REDUCE); } } // Re-enable the lookaside buffer, if it was disabled earlier. ctx.Lookaside.Enabled = enableLookaside; Expr.Delete(ctx, ref where_); Expr.Delete(ctx, ref when); Expr.ListDelete(ctx, ref list); Select.Delete(ctx, ref select); if (ctx.MallocFailed) { FKTriggerDelete(ctx, trigger); return null; } switch (action) { case OE.Restrict: step.OP = TK.SELECT; break; case OE.Cascade: if (changes == null) { step.OP = TK.DELETE; break; } goto default; default: step.OP = TK.UPDATE; break; } step.Trigger = trigger; trigger.Schema = table.Schema; trigger.TabSchema = table.Schema; fkey.Triggers[actionId] = trigger; trigger.OP = (TK)(changes != null ? TK.UPDATE : TK.DELETE); } return trigger; }
public static void DeleteTrigger(Context ctx, ref Trigger trigger) { if (trigger == null) return; DeleteTriggerStep(ctx, ref trigger.StepList); C._tagfree(ctx, ref trigger.Name); C._tagfree(ctx, ref trigger.Table); Expr.Delete(ctx, ref trigger.When); Expr.IdListDelete(ctx, ref trigger.Columns); C._tagfree(ctx, ref trigger); trigger = null; }
static void FKTriggerDelete(Context ctx, Trigger p) { if (p != null) { TriggerStep step = p.StepList; Expr.Delete(ctx, ref step.Where); Expr.ListDelete(ctx, ref step.ExprList); Select.Delete(ctx, ref step.Select); Expr.Delete(ctx, ref p.When); C._tagfree(ctx, ref p); } }
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 //: _scratchalloc(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._scratchfree(ctx, ref subParse); return(prg); }
public static void BeginTrigger(Parse parse, Token name1, Token name2, TK trTm, TK op, IdList columns, SrcList tableName, Expr when, bool isTemp, int noErr) { Context ctx = parse.Ctx; // The database connection Debug.Assert(name1 != null); // pName1.z might be NULL, but not pName1 itself Debug.Assert(name2 != null); Debug.Assert(op == TK.INSERT || op == TK.UPDATE || op == TK.DELETE); Debug.Assert(op > 0 && op < (TK)0xff); Trigger trigger = null; // The new trigger int db; // The database to store the trigger in Token name = null; // The unqualified db name if (isTemp) { // If TEMP was specified, then the trigger name may not be qualified. if (name2.length > 0) { parse.ErrorMsg("temporary trigger may not have qualified name"); goto trigger_cleanup; } db = 1; name = name1; } else { // Figure out the db that the the trigger will be created in db = parse.TwoPartName(name1, name2, ref name); if (db < 0) { goto trigger_cleanup; } } if (tableName == null || ctx.MallocFailed) { goto trigger_cleanup; } // A long-standing parser bug is that this syntax was allowed: // CREATE TRIGGER attached.demo AFTER INSERT ON attached.tab .... // ^^^^^^^^ // To maintain backwards compatibility, ignore the database name on pTableName if we are reparsing our of SQLITE_MASTER. if (ctx.Init.Busy && db != 1) { C._tagfree(ctx, ref tableName.Ids[0].Database); tableName.Ids[0].Database = null; } // If the trigger name was unqualified, and the table is a temp table, then set iDb to 1 to create the trigger in the temporary database. // If sqlite3SrcListLookup() returns 0, indicating the table does not exist, the error is caught by the block below. //? if (tableName == null) goto trigger_cleanup; Table table = Delete.SrcListLookup(parse, tableName); // Table that the trigger fires off of if (ctx.Init.Busy == null && name2.length == 0 && table != null && table.Schema == ctx.DBs[1].Schema) { db = 1; } // Ensure the table name matches database name and that the table exists if (ctx.MallocFailed) { goto trigger_cleanup; } Debug.Assert(tableName.Srcs == 1); DbFixer sFix = new DbFixer(); // State vector for the DB fixer if (sFix.FixInit(parse, db, "trigger", name) && sFix.FixSrcList(tableName)) { goto trigger_cleanup; } table = Delete.SrcListLookup(parse, tableName); if (table == null) { // The table does not exist. if (ctx.Init.DB == 1) { // Ticket #3810. // Normally, whenever a table is dropped, all associated triggers are dropped too. But if a TEMP trigger is created on a non-TEMP table // and the table is dropped by a different database connection, the trigger is not visible to the database connection that does the // drop so the trigger cannot be dropped. This results in an "orphaned trigger" - a trigger whose associated table is missing. ctx.Init.OrphanTrigger = true; } goto trigger_cleanup; } if (E.IsVirtual(table)) { parse.ErrorMsg("cannot create triggers on virtual tables"); goto trigger_cleanup; } // Check that the trigger name is not reserved and that no trigger of the specified name exists string nameAsString = Parse.NameFromToken(ctx, name); if (nameAsString == null || parse.CheckObjectName(nameAsString) != RC.OK) { goto trigger_cleanup; } Debug.Assert(Btree.SchemaMutexHeld(ctx, db, null)); if (ctx.DBs[db].Schema.TriggerHash.Find(nameAsString, nameAsString.Length, (Trigger)null) != null) { if (noErr == 0) { parse.ErrorMsg("trigger %T already exists", name); } else { Debug.Assert(!ctx.Init.Busy); parse.CodeVerifySchema(db); } goto trigger_cleanup; } // Do not create a trigger on a system table if (table.Name.StartsWith("sqlite_", StringComparison.InvariantCultureIgnoreCase)) { parse.ErrorMsg("cannot create trigger on system table"); parse.Errs++; goto trigger_cleanup; } // INSTEAD of triggers are only for views and views only support INSTEAD of triggers. if (table.Select != null && trTm != TK.INSTEAD) { parse.ErrorMsg("cannot create %s trigger on view: %S", (trTm == TK.BEFORE ? "BEFORE" : "AFTER"), tableName, 0); goto trigger_cleanup; } if (table.Select == null && trTm == TK.INSTEAD) { parse.ErrorMsg("cannot create INSTEAD OF trigger on table: %S", tableName, 0); goto trigger_cleanup; } #if !OMIT_AUTHORIZATION { int tabDb = Prepare.SchemaToIndex(ctx, table.Schema); // Index of the database holding pTab AUTH code = AUTH.CREATE_TRIGGER; string dbName = ctx.DBs[tabDb].Name; string dbTrigName = (isTemp ? ctx.DBs[1].Name : dbName); if (tabDb == 1 || isTemp) { code = AUTH.CREATE_TEMP_TRIGGER; } if (Auth.Check(parse, code, nameAsString, table.Name, dbTrigName) != 0 || Auth.Check(parse, AUTH.INSERT, E.SCHEMA_TABLE(tabDb), 0, dbName)) { goto trigger_cleanup; } } #endif // INSTEAD OF triggers can only appear on views and BEFORE triggers cannot appear on views. So we might as well translate every // INSTEAD OF trigger into a BEFORE trigger. It simplifies code elsewhere. if (trTm == TK.INSTEAD) { trTm = TK.BEFORE; } // Build the Trigger object trigger = new Trigger(); //: (Trigger *)_tagalloc(db, sizeof(Trigger), true); if (trigger == null) { goto trigger_cleanup; } trigger.Name = name; trigger.Table = tableName.Ids[0].Name; //: _tagstrdup(ctx, tableName->Ids[0].Name); trigger.Schema = ctx.DBs[db].Schema; trigger.TabSchema = table.Schema; trigger.OP = op; trigger.TRtm = (trTm == TK.BEFORE ? TRIGGER.BEFORE : TRIGGER.AFTER); trigger.When = Expr.Dup(db, when, E.EXPRDUP_REDUCE); trigger.Columns = Expr.IdListDup(ctx, columns); Debug.Assert(parse.NewTrigger == null); parse.NewTrigger = trigger; trigger_cleanup: C._tagfree(ctx, ref name); Expr.SrcListDelete(ctx, ref tableName); Expr.IdListDelete(ctx, ref columns); Expr.Delete(ctx, ref when); if (parse.NewTrigger == null) { DeleteTrigger(ctx, ref trigger); } else { Debug.Assert(parse.NewTrigger == trigger); } }
static Table TableOfTrigger(Trigger trigger) { int tableLength = trigger.Table.Length; return(trigger.TabSchema.TableHash.Find(trigger.Table, tableLength, (Table)null)); }
public static void FinishTrigger(Parse parse, TriggerStep stepList, Token all) { Trigger trig = parse.NewTrigger; // Trigger being finished Context ctx = parse.Ctx; // The database Token nameToken = new Token(); // Trigger name for error reporting parse.NewTrigger = null; if (C._NEVER(parse.Errs != 0) || trig == null) { goto triggerfinish_cleanup; } string name = trig.Name; // Name of trigger int db = Prepare.SchemaToIndex(parse.Ctx, trig.Schema); // Database containing the trigger trig.StepList = stepList; while (stepList != null) { stepList.Trig = trig; stepList = stepList.Next; } nameToken.data = trig.Name; nameToken.length = (uint)nameToken.data.Length; DbFixer sFix = new DbFixer(); // Fixer object if (sFix.FixInit(parse, db, "trigger", nameToken) && sFix.FixTriggerStep(trig.StepList)) { goto triggerfinish_cleanup; } // if we are not initializing, build the sqlite_master entry if (ctx.Init.Busy) { // Make an entry in the sqlite_master table Vdbe v = parse.GetVdbe(); if (v == null) { goto triggerfinish_cleanup; } parse.BeginWriteOperation(0, db); string z = all.data.Substring(0, (int)all.length); //: _tagstrndup(ctx, (char *)all->data, all->length); parse.NestedParse("INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", ctx.DBs[db].Name, E.SCHEMA_TABLE(db), name, trig.Table, z); C._tagfree(ctx, ref z); parse.ChangeCookie(db); v.AddParseSchemaOp(db, C._mtagprintf(ctx, "type='trigger' AND name='%q'", name)); } if (!ctx.Init.Busy) { Trigger link = trig; Debug.Assert(Btree.SchemaMutexHeld(ctx, db, null)); trig = ctx.DBs[db].Schema.TriggerHash.Insert(name, name.Length, trig); if (trig != null) { ctx.MallocFailed = true; } else if (link.Schema == link.TabSchema) { int tableLength = link.Table.Length; Table table = (Table)link.TabSchema.TableHash.Find(link.Table, tableLength, (Table)null); Debug.Assert(table != null); link.Next = table.Triggers; table.Triggers = link; } } triggerfinish_cleanup: DeleteTrigger(ctx, ref trig); Debug.Assert(parse.NewTrigger == null); DeleteTriggerStep(ctx, ref stepList); }