static WRC LookupName(Parse parse, string dbName, string tableName, string colName, NameContext nc, Expr expr) { int cnt = 0; // Number of matching column names int cntTab = 0; // Number of matching table names int subquerys = 0; // How many levels of subquery Context ctx = parse.Ctx; // The database connection SrcList.SrcListItem item; // Use for looping over pSrcList items SrcList.SrcListItem match = null; // The matching pSrcList item NameContext topNC = nc; // First namecontext in the list Schema schema = null; // Schema of the expression bool isTrigger = false; int i, j; Debug.Assert(nc != null); // the name context cannot be NULL. Debug.Assert(colName != null); // The Z in X.Y.Z cannot be NULL Debug.Assert(!E.ExprHasAnyProperty(expr, EP.TokenOnly | EP.Reduced)); // Initialize the node to no-match expr.TableId = -1; expr.Table = null; E.ExprSetIrreducible(expr); // Translate the schema name in zDb into a pointer to the corresponding schema. If not found, pSchema will remain NULL and nothing will match // resulting in an appropriate error message toward the end of this routine if (dbName != null) { for (i = 0; i < ctx.DBs.length; i++) { Debug.Assert(ctx.DBs[i].Name != null); if (string.Compare(ctx.DBs[i].Name, dbName) == 0) { schema = ctx.DBs[i].Schema; break; } } } // Start at the inner-most context and move outward until a match is found while (nc != null && cnt == 0) { ExprList list; SrcList srcList = nc.SrcList; if (srcList != null) { for (i = 0; i < srcList.Srcs; i++) { item = srcList.Ids[i]; Table table = item.Table; Debug.Assert(table != null && table.Name != null); Debug.Assert(table.Cols.length > 0); if (item.Select != null && (item.Select.SelFlags & SF.NestedFrom) != 0) { bool hit = false; list = item.Select.EList; for (j = 0; j < list.Exprs; j++) { if (Walker.MatchSpanName(list.Ids[j].Span, colName, tableName, dbName)) { cnt++; cntTab = 2; match = item; expr.ColumnId = j; hit = true; } } if (hit || table == null) { continue; } } if (dbName != null && table.Schema != schema) { continue; } if (tableName != null) { string tableName2 = (item.Alias != null ? item.Alias : table.Name); Debug.Assert(tableName2 != null); if (!string.Equals(tableName2, tableName, StringComparison.OrdinalIgnoreCase)) { continue; } } if (cntTab++ == 0) { match = item; } Column col; for (j = 0; j < table.Cols.length; j++) { col = table.Cols[j]; if (string.Equals(col.Name, colName, StringComparison.InvariantCultureIgnoreCase)) { // If there has been exactly one prior match and this match is for the right-hand table of a NATURAL JOIN or is in a // USING clause, then skip this match. if (cnt == 1) { if ((item.Jointype & JT.NATURAL) != 0) { continue; } if (NameInUsingClause(item.Using, colName)) { continue; } } cnt++; match = item; // Substitute the rowid (column -1) for the INTEGER PRIMARY KEY expr.ColumnId = (j == table.PKey ? -1 : (short)j); break; } } } if (match != null) { expr.TableId = match.Cursor; expr.Table = match.Table; schema = expr.Table.Schema; } } #if !OMIT_TRIGGER // If we have not already resolved the name, then maybe it is a new.* or old.* trigger argument reference if (dbName == null && tableName != null && cnt == 0 && parse.TriggerTab != null) { TK op = parse.TriggerOp; Table table = null; Debug.Assert(op == TK.DELETE || op == TK.UPDATE || op == TK.INSERT); if (op != TK.DELETE && string.Equals("new", tableName, StringComparison.InvariantCultureIgnoreCase)) { expr.TableId = 1; table = parse.TriggerTab; } else if (op != TK.INSERT && string.Equals("old", tableName, StringComparison.InvariantCultureIgnoreCase)) { expr.TableId = 0; table = parse.TriggerTab; } if (table != null) { int colId; schema = table.Schema; cntTab++; for (colId = 0; colId < table.Cols.length; colId++) { Column col = table.Cols[colId]; if (string.Equals(col.Name, colName, StringComparison.InvariantCultureIgnoreCase)) { if (colId == table.PKey) { colId = -1; } break; } } if (colId >= table.Cols.length && Expr.IsRowid(colName)) { colId = -1; // IMP: R-44911-55124 } if (colId < table.Cols.length) { cnt++; if (colId < 0) { expr.Aff = AFF.INTEGER; } else if (expr.TableId == 0) { C.ASSERTCOVERAGE(colId == 31); C.ASSERTCOVERAGE(colId == 32); parse.Oldmask |= (colId >= 32 ? 0xffffffff : (((uint)1) << colId)); } else { C.ASSERTCOVERAGE(colId == 31); C.ASSERTCOVERAGE(colId == 32); parse.Newmask |= (colId >= 32 ? 0xffffffff : (((uint)1) << colId)); } expr.ColumnId = (short)colId; expr.Table = table; isTrigger = true; } } } #endif // Perhaps the name is a reference to the ROWID if (cnt == 0 && cntTab == 1 && Expr.IsRowid(colName)) { cnt = 1; expr.ColumnId = -1; // IMP: R-44911-55124 expr.Aff = AFF.INTEGER; } // If the input is of the form Z (not Y.Z or X.Y.Z) then the name Z might refer to an result-set alias. This happens, for example, when // we are resolving names in the WHERE clause of the following command: // // SELECT a+b AS x FROM table WHERE x<10; // // In cases like this, replace pExpr with a copy of the expression that forms the result set entry ("a+b" in the example) and return immediately. // Note that the expression in the result set should have already been resolved by the time the WHERE clause is resolved. if (cnt == 0 && (list = nc.EList) != null && tableName == null) { for (j = 0; j < list.Exprs; j++) { string asName = list.Ids[j].Name; if (asName != null && string.Equals(asName, colName, StringComparison.InvariantCultureIgnoreCase)) { Debug.Assert(expr.Left == null && expr.Right == null); Debug.Assert(expr.x.List == null); Debug.Assert(expr.x.Select == null); Expr orig = list.Ids[j].Expr; if ((nc.NCFlags & NC.AllowAgg) == 0 && E.ExprHasProperty(orig, EP.Agg)) { parse.ErrorMsg("misuse of aliased aggregate %s", asName); return(WRC.Abort); } ResolveAlias(parse, list, j, expr, "", subquerys); cnt = 1; match = null; Debug.Assert(tableName == null && dbName == null); goto lookupname_end; } } } // Advance to the next name context. The loop will exit when either we have a match (cnt>0) or when we run out of name contexts. if (cnt == 0) { nc = nc.Next; subquerys++; } } // If X and Y are NULL (in other words if only the column name Z is supplied) and the value of Z is enclosed in double-quotes, then // Z is a string literal if it doesn't match any column names. In that case, we need to return right away and not make any changes to // pExpr. // // Because no reference was made to outer contexts, the pNC->nRef fields are not changed in any context. if (cnt == 0 && tableName == null && E.ExprHasProperty(expr, EP.DblQuoted)) { expr.OP = TK.STRING; expr.Table = null; return(WRC.Prune); } // cnt==0 means there was not match. cnt>1 means there were two or more matches. Either way, we have an error. if (cnt != 1) { string err = (cnt == 0 ? "no such column" : "ambiguous column name"); if (dbName != null) { parse.ErrorMsg("%s: %s.%s.%s", err, dbName, tableName, colName); } else if (tableName != null) { parse.ErrorMsg("%s: %s.%s", err, tableName, colName); } else { parse.ErrorMsg("%s: %s", err, colName); } parse.CheckSchema = 1; topNC.Errs++; } // If a column from a table in pSrcList is referenced, then record this fact in the pSrcList.a[].colUsed bitmask. Column 0 causes // bit 0 to be set. Column 1 sets bit 1. And so forth. If the column number is greater than the number of bits in the bitmask // then set the high-order bit of the bitmask. if (expr.ColumnId >= 0 && match != null) { int n = expr.ColumnId; C.ASSERTCOVERAGE(n == BMS - 1); if (n >= BMS) { n = BMS - 1; } Debug.Assert(match.Cursor == expr.TableId); match.ColUsed |= ((Bitmask)1) << n; } // Clean up and return Expr.Delete(ctx, ref expr.Left); expr.Left = null; Expr.Delete(ctx, ref expr.Right); expr.Right = null; expr.OP = (isTrigger ? TK.TRIGGER : TK.COLUMN); lookupname_end: if (cnt == 1) { Debug.Assert(nc != null); Auth.Read(parse, expr, schema, nc.SrcList); // Increment the nRef value on all name contexts from TopNC up to the point where the name matched. for (; ;) { Debug.Assert(topNC != null); topNC.Refs++; if (topNC == nc) { break; } topNC = topNC.Next; } return(WRC.Prune); } return(WRC.Abort); }
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 CodeRowTrigger(Parse parse, TK op, ExprList changes, TRIGGER trtm, Table table, int reg, OE orconf, int ignoreJump) { Debug.Assert(op == TK.UPDATE || op == TK.INSERT || op == TK.DELETE); Debug.Assert(trtm == TRIGGER.BEFORE || trtm == TRIGGER.AFTER); Debug.Assert((op == TK.UPDATE) == (changes != null)); for (Trigger p = this; p != null; p = p.Next) { // 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.Schema != null); Debug.Assert(p.TabSchema != null); Debug.Assert(p.Schema == p.TabSchema || p.Schema == parse.Ctx.DBs[1].Schema); // Determine whether we should code this trigger if (p.OP == op && p.TRtm == trtm && CheckColumnOverlap(p.Columns, changes)) { p.CodeRowTriggerDirect(parse, table, reg, orconf, ignoreJump); } } }
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); } }
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); }