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; } } }
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)); } }
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); }
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); }
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; }
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); }
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 void FKScanChildren(Parse parse, SrcList src, Table table, Index index, FKey fkey, int[] cols, int regDataId, int incr) { Context ctx = parse.Ctx; // Database handle Vdbe v = parse.GetVdbe(); Expr where_ = null; // WHERE clause to scan with Debug.Assert(index == null || index.Table == table); int fkIfZero = 0; // Address of OP_FkIfZero if (incr < 0) { fkIfZero = v.AddOp2(OP.FkIfZero, fkey.IsDeferred, 0); } // Create an Expr object representing an SQL expression like: // // <parent-key1> = <child-key1> AND <parent-key2> = <child-key2> ... // // The collation sequence used for the comparison should be that of the parent key columns. The affinity of the parent key column should // be applied to each child key value before the comparison takes place. for (int i = 0; i < fkey.Cols.length; i++) { int col; // Index of column in child table Expr left = Expr.Expr(ctx, TK.REGISTER, null); // Value from parent table row if (left != null) { // Set the collation sequence and affinity of the LHS of each TK_EQ expression to the parent key column defaults. if (index != null) { col = index.Columns[i]; Column colObj = table.Cols[col]; if (table.PKey == col) { col = -1; } left.TableId = regDataId + col + 1; left.Aff = colObj.Affinity; string collName = colObj.Coll; if (collName == null) { collName = ctx.DefaultColl.Name; } left = Expr.AddCollateString(parse, left, collName); } else { left.TableId = regDataId; left.Aff = AFF.INTEGER; } } col = (cols != null ? cols[i] : fkey.Cols[0].From); Debug.Assert(col >= 0); string colName = fkey.From.Cols[col].Name; // Name of column in child table Expr right = Expr.Expr(ctx, TK.ID, colName); // Column ref to child table Expr eq = Expr.PExpr(parse, TK.EQ, left, right, 0); // Expression (pLeft = pRight) where_ = Expr.And(ctx, where_, eq); } // If the child table is the same as the parent table, and this scan is taking place as part of a DELETE operation (operation D.2), omit the // row being deleted from the scan by adding ($rowid != rowid) to the WHERE clause, where $rowid is the rowid of the row being deleted. if (table == fkey.From && incr > 0) { Expr left = Expr.Expr(ctx, TK.REGISTER, null); // Value from parent table row Expr right = Expr.Expr(ctx, TK.COLUMN, null); // Column ref to child table if (left != null && right != null) { left.TableId = regDataId; left.Aff = AFF.INTEGER; right.TableId = src.Ids[0].Cursor; right.ColumnId = -1; } Expr eq = Expr.PExpr(parse, TK.NE, left, right, 0); // Expression (pLeft = pRight) where_ = Expr.And(ctx, where_, eq); } // Resolve the references in the WHERE clause. NameContext nameContext; // Context used to resolve WHERE clause nameContext = new NameContext(); // memset( &sNameContext, 0, sizeof( NameContext ) ); nameContext.SrcList = src; nameContext.Parse = parse; ResolveExprNames(nameContext, ref where_); // Create VDBE to loop through the entries in src that match the WHERE clause. If the constraint is not deferred, throw an exception for // each row found. Otherwise, for deferred constraints, increment the deferred constraint counter by incr for each row selected. ExprList dummy = null; WhereInfo whereInfo = Where.Begin(parse, src, where_, ref dummy, 0); // Context used by sqlite3WhereXXX() if (incr > 0 && !fkey.IsDeferred) { E.Parse_Toplevel(parse).MayAbort = true; } v.AddOp2(OP.FkCounter, fkey.IsDeferred, incr); if (whereInfo != null) { Where.End(whereInfo); } // Clean up the WHERE clause constructed above. Expr.Delete(ctx, ref where_); if (fkIfZero != 0) { v.JumpHere(fkIfZero); } }
static void FKLookupParent(Parse parse, int db, Table table, Index index, FKey fkey, int[] cols, int regDataId, int incr, bool isIgnore) { Vdbe v = parse.GetVdbe(); // Vdbe to add code to int curId = parse.Tabs - 1; // Cursor number to use int okId = v.MakeLabel(); // jump here if parent key found // If nIncr is less than zero, then check at runtime if there are any outstanding constraints to resolve. If there are not, there is no need // to check if deleting this row resolves any outstanding violations. // // Check if any of the key columns in the child table row are NULL. If any are, then the constraint is considered satisfied. No need to // search for a matching row in the parent table. int i; if (incr < 0) { v.AddOp2(OP.FkIfZero, fkey.IsDeferred, okId); } for (i = 0; i < fkey.Cols.length; i++) { int regId = cols[i] + regDataId + 1; v.AddOp2(OP.IsNull, regId, okId); } if (!isIgnore) { if (index == null) { // If pIdx is NULL, then the parent key is the INTEGER PRIMARY KEY column of the parent table (table pTab). int mustBeIntId; // Address of MustBeInt instruction int regTempId = parse.GetTempReg(); // Invoke MustBeInt to coerce the child key value to an integer (i.e. apply the affinity of the parent key). If this fails, then there // is no matching parent key. Before using MustBeInt, make a copy of the value. Otherwise, the value inserted into the child key column // will have INTEGER affinity applied to it, which may not be correct. v.AddOp2(OP.SCopy, cols[0] + 1 + regDataId, regTempId); mustBeIntId = v.AddOp2(OP.MustBeInt, regTempId, 0); // If the parent table is the same as the child table, and we are about to increment the constraint-counter (i.e. this is an INSERT operation), // then check if the row being inserted matches itself. If so, do not increment the constraint-counter. if (table == fkey.From && incr == 1) { v.AddOp3(OP.Eq, regDataId, okId, regTempId); } parse.OpenTable(parse, curId, db, table, OP.OpenRead); v.AddOp3(OP.NotExists, curId, 0, regTempId); v.AddOp2(OP.Goto, 0, okId); v.JumpHere(v.CurrentAddr() - 2); v.JumpHere(mustBeIntId); parse.ReleaseTempReg(regTempId); } else { int colsLength = fkey.Cols.length; int regTempId = parse.GetTempRange(colsLength); int regRecId = parse.GetTempReg(); KeyInfo key = IndexKeyinfo(parse, index); v.AddOp3(OP.OpenRead, curId, index.Id, db); v.ChangeP4(v, -1, key, Vdbe.P4T.KEYINFO_HANDOFF); for (i = 0; i < colsLength; i++) { v.AddOp2(OP.Copy, cols[i] + 1 + regDataId, regTempId + i); } // If the parent table is the same as the child table, and we are about to increment the constraint-counter (i.e. this is an INSERT operation), // then check if the row being inserted matches itself. If so, do not increment the constraint-counter. // If any of the parent-key values are NULL, then the row cannot match itself. So set JUMPIFNULL to make sure we do the OP_Found if any // of the parent-key values are NULL (at this point it is known that none of the child key values are). if (table == fkey.From && incr == 1) { int jumpId = v.CurrentAddr() + colsLength + 1; for (i = 0; i < colsLength; i++) { int childId = cols[i] + 1 + regDataId; int parentId = index.Columns[i] + 1 + regDataId; Debug.Assert(cols[i] != table.PKey); if (index.Columns[i] == table.PKey) { parentId = regDataId; // The parent key is a composite key that includes the IPK column } v.AddOp3(OP.Ne, childId, jumpId, parentId); v.ChangeP5(SQLITE_JUMPIFNULL); } v.AddOp2(OP.Goto, 0, okId); } v.AddOp3(OP.MakeRecord, regTempId, colsLength, regRecId); v.ChangeP4(-1, IndexAffinityStr(v, index), Vdbe.P4T.TRANSIENT); v.AddOp4Int(OP.Found, curId, okId, regRecId, 0); parse.ReleaseTempReg(regRecId); parse.ReleaseTempRange(regTempId, colsLength); } } if (!fkey.IsDeferred && parse.Toplevel == null && parse.IsMultiWrite == 0) { // Special case: If this is an INSERT statement that will insert exactly one row into the table, raise a constraint immediately instead of // incrementing a counter. This is necessary as the VM code is being generated for will not open a statement transaction. Debug.Assert(incr == 1); HaltConstraint(parse, OE.Abort, "foreign key constraint failed", Vdbe.P4T.STATIC); } else { if (incr > 0 && !fkey.IsDeferred) { E.Parse_Toplevel(parse).MayAbort = true; } v.AddOp2(OP.FkCounter, fkey.IsDeferred, incr); } v.ResolveLabel(v, okId); v.AddOp1(OP.Close, curId); }
public static void FinishParse(Parse parse, Token end) { Table table = parse.NewTable; // The table being constructed Context ctx = parse.Ctx; // The database connection if (table == null) { return; } AddArgumentToVtab(parse); parse.Arg.data = null; if (table.ModuleArgs.length < 1) { return; } // If the CREATE VIRTUAL TABLE statement is being entered for the first time (in other words if the virtual table is actually being // created now instead of just being read out of sqlite_master) then do additional initialization work and store the statement text // in the sqlite_master table. if (!ctx.Init.Busy) { // Compute the complete text of the CREATE VIRTUAL TABLE statement if (end != null) { parse.NameToken.length = (uint)parse.NameToken.data.Length; //: (int)(end->data - parse->NameToken) + end->length; } string stmt = C._mtagprintf(ctx, "CREATE VIRTUAL TABLE %T", parse.NameToken); //.Z.Substring(0, parse.NameToken.length)); // A slot for the record has already been allocated in the SQLITE_MASTER table. We just need to update that slot with all // the information we've collected. // // The VM register number pParse->regRowid holds the rowid of an entry in the sqlite_master table tht was created for this vtab // by sqlite3StartTable(). int db = Prepare.SchemaToIndex(ctx, table.Schema); parse.NestedParse("UPDATE %Q.%s SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q WHERE rowid=#%d", ctx.DBs[db].Name, E.SCHEMA_TABLE(db), table.Name, table.Name, stmt, parse.RegRowid ); C._tagfree(ctx, ref stmt); Vdbe v = parse.GetVdbe(); parse.ChangeCookie(db); v.AddOp2(OP.Expire, 0, 0); string where_ = C._mtagprintf(ctx, "name='%q' AND type='table'", table.Name); v.AddParseSchemaOp(db, where_); v.AddOp4(OP.VCreate, db, 0, 0, table.Name, (Vdbe.P4T)table.Name.Length + 1); } // If we are rereading the sqlite_master table create the in-memory record of the table. The xConnect() method is not called until // the first time the virtual table is used in an SQL statement. This allows a schema that contains virtual tables to be loaded before // the required virtual table implementations are registered. else { Schema schema = table.Schema; string name = table.Name; int nameLength = name.Length; Debug.Assert(Btree.SchemaMutexHeld(ctx, 0, schema)); Table oldTable = schema.TableHash.Insert(name, nameLength, table); if (oldTable != null) { ctx.MallocFailed = true; Debug.Assert(table == oldTable); // Malloc must have failed inside HashInsert() return; } parse.NewTable = null; } }
static void FKScanChildren(Parse parse, SrcList src, Table table, Index index, FKey fkey, int[] cols, int regDataId, int incr) { Context ctx = parse.Ctx; // Database handle Vdbe v = parse.GetVdbe(); Expr where_ = null; // WHERE clause to scan with Debug.Assert(index == null || index.Table == table); int fkIfZero = 0; // Address of OP_FkIfZero if (incr < 0) fkIfZero = v.AddOp2(OP.FkIfZero, fkey.IsDeferred, 0); // Create an Expr object representing an SQL expression like: // // <parent-key1> = <child-key1> AND <parent-key2> = <child-key2> ... // // The collation sequence used for the comparison should be that of the parent key columns. The affinity of the parent key column should // be applied to each child key value before the comparison takes place. for (int i = 0; i < fkey.Cols.length; i++) { int col; // Index of column in child table Expr left = Expr.Expr(ctx, TK.REGISTER, null); // Value from parent table row if (left != null) { // Set the collation sequence and affinity of the LHS of each TK_EQ expression to the parent key column defaults. if (index != null) { col = index.Columns[i]; Column colObj = table.Cols[col]; if (table.PKey == col) col = -1; left.TableId = regDataId + col + 1; left.Aff = colObj.Affinity; string collName = colObj.Coll; if (collName == null) collName = ctx.DefaultColl.Name; left = Expr.AddCollateString(parse, left, collName); } else { left.TableId = regDataId; left.Aff = AFF.INTEGER; } } col = (cols != null ? cols[i] : fkey.Cols[0].From); Debug.Assert(col >= 0); string colName = fkey.From.Cols[col].Name; // Name of column in child table Expr right = Expr.Expr(ctx, TK.ID, colName); // Column ref to child table Expr eq = Expr.PExpr(parse, TK.EQ, left, right, 0); // Expression (pLeft = pRight) where_ = Expr.And(ctx, where_, eq); } // If the child table is the same as the parent table, and this scan is taking place as part of a DELETE operation (operation D.2), omit the // row being deleted from the scan by adding ($rowid != rowid) to the WHERE clause, where $rowid is the rowid of the row being deleted. if (table == fkey.From && incr > 0) { Expr left = Expr.Expr(ctx, TK.REGISTER, null); // Value from parent table row Expr right = Expr.Expr(ctx, TK.COLUMN, null); // Column ref to child table if (left != null && right != null) { left.TableId = regDataId; left.Aff = AFF.INTEGER; right.TableId = src.Ids[0].Cursor; right.ColumnIdx = -1; } Expr eq = Expr.PExpr(parse, TK.NE, left, right, 0); // Expression (pLeft = pRight) where_ = Expr.And(ctx, where_, eq); } // Resolve the references in the WHERE clause. NameContext nameContext; // Context used to resolve WHERE clause nameContext = new NameContext(); // memset( &sNameContext, 0, sizeof( NameContext ) ); nameContext.SrcList = src; nameContext.Parse = parse; ResolveExprNames(nameContext, ref where_); // Create VDBE to loop through the entries in src that match the WHERE clause. If the constraint is not deferred, throw an exception for // each row found. Otherwise, for deferred constraints, increment the deferred constraint counter by incr for each row selected. ExprList dummy = null; WhereInfo whereInfo = Where.Begin(parse, src, where_, ref dummy, 0); // Context used by sqlite3WhereXXX() if (incr > 0 && !fkey.IsDeferred) E.Parse_Toplevel(parse).MayAbort = true; v.AddOp2(OP.FkCounter, fkey.IsDeferred, incr); if (whereInfo != null) Where.End(whereInfo); // Clean up the WHERE clause constructed above. Expr.Delete(ctx, ref where_); if (fkIfZero != 0) v.JumpHere(fkIfZero); }
static void FKLookupParent(Parse parse, int db, Table table, Index index, FKey fkey, int[] cols, int regDataId, int incr, bool isIgnore) { Vdbe v = parse.GetVdbe(); // Vdbe to add code to int curId = parse.Tabs - 1; // Cursor number to use int okId = v.MakeLabel(); // jump here if parent key found // If nIncr is less than zero, then check at runtime if there are any outstanding constraints to resolve. If there are not, there is no need // to check if deleting this row resolves any outstanding violations. // // Check if any of the key columns in the child table row are NULL. If any are, then the constraint is considered satisfied. No need to // search for a matching row in the parent table. int i; if (incr < 0) v.AddOp2(OP.FkIfZero, fkey.IsDeferred, okId); for (i = 0; i < fkey.Cols.length; i++) { int regId = cols[i] + regDataId + 1; v.AddOp2(OP.IsNull, regId, okId); } if (!isIgnore) { if (index == null) { // If pIdx is NULL, then the parent key is the INTEGER PRIMARY KEY column of the parent table (table pTab). int mustBeIntId; // Address of MustBeInt instruction int regTempId = parse.GetTempReg(); // Invoke MustBeInt to coerce the child key value to an integer (i.e. apply the affinity of the parent key). If this fails, then there // is no matching parent key. Before using MustBeInt, make a copy of the value. Otherwise, the value inserted into the child key column // will have INTEGER affinity applied to it, which may not be correct. v.AddOp2(OP.SCopy, cols[0] + 1 + regDataId, regTempId); mustBeIntId = v.AddOp2(OP.MustBeInt, regTempId, 0); // If the parent table is the same as the child table, and we are about to increment the constraint-counter (i.e. this is an INSERT operation), // then check if the row being inserted matches itself. If so, do not increment the constraint-counter. if (table == fkey.From && incr == 1) v.AddOp3(OP.Eq, regDataId, okId, regTempId); parse.OpenTable(parse, curId, db, table, OP.OpenRead); v.AddOp3(OP.NotExists, curId, 0, regTempId); v.AddOp2(OP.Goto, 0, okId); v.JumpHere(v.CurrentAddr() - 2); v.JumpHere(mustBeIntId); parse.ReleaseTempReg(regTempId); } else { int colsLength = fkey.Cols.length; int regTempId = parse.GetTempRange(colsLength); int regRecId = parse.GetTempReg(); KeyInfo key = IndexKeyinfo(parse, index); v.AddOp3(OP.OpenRead, curId, index.Id, db); v.ChangeP4(v, -1, key, Vdbe.P4T.KEYINFO_HANDOFF); for (i = 0; i < colsLength; i++) v.AddOp2(OP.Copy, cols[i] + 1 + regDataId, regTempId + i); // If the parent table is the same as the child table, and we are about to increment the constraint-counter (i.e. this is an INSERT operation), // then check if the row being inserted matches itself. If so, do not increment the constraint-counter. // If any of the parent-key values are NULL, then the row cannot match itself. So set JUMPIFNULL to make sure we do the OP_Found if any // of the parent-key values are NULL (at this point it is known that none of the child key values are). if (table == fkey.From && incr == 1) { int jumpId = v.CurrentAddr() + colsLength + 1; for (i = 0; i < colsLength; i++) { int childId = cols[i] + 1 + regDataId; int parentId = index.Columns[i] + 1 + regDataId; Debug.Assert(cols[i] != table.PKey); if (index.Columns[i] == table.PKey) parentId = regDataId; // The parent key is a composite key that includes the IPK column v.AddOp3(OP.Ne, childId, jumpId, parentId); v.ChangeP5(SQLITE_JUMPIFNULL); } v.AddOp2(OP.Goto, 0, okId); } v.AddOp3(OP.MakeRecord, regTempId, colsLength, regRecId); v.ChangeP4(-1, IndexAffinityStr(v, index), Vdbe.P4T.TRANSIENT); v.AddOp4Int(OP.Found, curId, okId, regRecId, 0); parse.ReleaseTempReg(regRecId); parse.ReleaseTempRange(regTempId, colsLength); } } if (!fkey.IsDeferred && parse.Toplevel == null && parse.IsMultiWrite == 0) { // Special case: If this is an INSERT statement that will insert exactly one row into the table, raise a constraint immediately instead of // incrementing a counter. This is necessary as the VM code is being generated for will not open a statement transaction. Debug.Assert(incr == 1); HaltConstraint(parse, OE.Abort, "foreign key constraint failed", Vdbe.P4T.STATIC); } else { if (incr > 0 && !fkey.IsDeferred) E.Parse_Toplevel(parse).MayAbort = true; v.AddOp2(OP.FkCounter, fkey.IsDeferred, incr); } v.ResolveLabel(v, okId); v.AddOp1(OP.Close, curId); }