public void FKDropTable(SrcList name, Table table) { Context ctx = Ctx; if ((ctx.Flags & Context.FLAG.ForeignKeys) != 0 && !IsVirtual(table) && table.Select == null) { int skipId = 0; Vdbe v = GetVdbe(); Debug.Assert(v != null); // VDBE has already been allocated if (FKReferences(table) == null) { // Search for a deferred foreign key constraint for which this table is the child table. If one cannot be found, return without // generating any VDBE code. If one can be found, then jump over the entire DELETE if there are no outstanding deferred constraints // when this statement is run. FKey p; for (p = table.FKeys; p != null; p = p.NextFrom) { if (p.IsDeferred) { break; } } if (p == null) { return; } skipId = v.MakeLabel(); v.AddOp2(OP.FkIfZero, 1, skipId); } DisableTriggers = true; DeleteFrom(this, SrcListDup(ctx, name, 0), null); DisableTriggers = false; // If the DELETE has generated immediate foreign key constraint violations, halt the VDBE and return an error at this point, before // any modifications to the schema are made. This is because statement transactions are not able to rollback schema changes. v.AddOp2(OP.FkIfZero, 0, v.CurrentAddr() + 2); HaltConstraint(this, OE.Abort, "foreign key constraint failed", Vdbe.P4T.STATIC); if (skipId != 0) { v.ResolveLabel(skipId); } } }
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 GenerateSortTail(Parse parse, Select p, Vdbe v, int columns, SelectDest dest) { int addrBreak = v.MakeLabel(); // Jump here to exit loop int addrContinue = v.MakeLabel(); // Jump here for next cycle ExprList orderBy = p.OrderBy; SRT dest2 = dest.Dest; int parmId = dest.SDParmId; int tabId = orderBy.ECursor; int regRow = Expr.GetTempReg(parse); int pseudoTab = 0; int regRowid; if (dest2 == SRT.Output || dest2 == SRT.Coroutine) { pseudoTab = parse.Tabs++; v.AddOp3(OP.OpenPseudo, pseudoTab, regRow, columns); regRowid = 0; } else regRowid = Expr.GetTempReg(parse); int addr; if ((p.SelFlags & SF.UseSorter) != 0) { int regSortOut = ++parse.Mems; int ptab2 = parse.Tabs++; v.AddOp3(OP.OpenPseudo, ptab2, regSortOut, orderBy.Exprs + 2); addr = 1 + v.AddOp2(OP.SorterSort, tabId, addrBreak); CodeOffset(v, p, addrContinue); v.AddOp2(OP.SorterData, tabId, regSortOut); v.AddOp3(OP.Column, ptab2, orderBy.Exprs + 1, regRow); v.ChangeP5(Vdbe.OPFLAG.CLEARCACHE); } else { addr = 1 + v.AddOp2(OP.Sort, tabId, addrBreak); CodeOffset(v, p, addrContinue); v.AddOp3(OP.Column, tabId, orderBy.Exprs + 1, regRow); } switch (dest2) { case SRT.Table: case SRT.EphemTab: { C.ASSERTCOVERAGE(dest2 == SRT.Table); C.ASSERTCOVERAGE(dest2 == SRT.EphemTab); v.AddOp2(OP.NewRowid, parmId, regRowid); v.AddOp3(OP.Insert, parmId, regRow, regRowid); v.ChangeP5(Vdbe.OPFLAG.APPEND); break; } #if !OMIT_SUBQUERY case SRT.Set: { Debug.Assert(columns == 1); v.AddOp4(OP.MakeRecord, regRow, 1, regRowid, dest->AffSdst, 1); Expr.CacheAffinityChange(parse, regRow, 1); v.AddOp2(OP.IdxInsert, parmId, regRowid); break; } case SRT.Mem: { Debug.Assert(columns == 1); Expr.CodeMove(parse, regRow, parmId, 1); // The LIMIT clause will terminate the loop for us break; } #endif default: { Debug.Assert(dest2 == SRT.Output || dest2 == SRT.Coroutine); C.ASSERTCOVERAGE(dest2 == SRT.Output); C.ASSERTCOVERAGE(dest2 == SRT.Coroutine); for (int i = 0; i < columns; i++) { Debug.Assert(regRow != dest.SdstId + i); v.AddOp3(OP.Column, pseudoTab, i, dest.SdstId + i); if (i == 0) v.ChangeP5(Vdbe.OPFLAG.CLEARCACHE); } if (dest2 == SRT.Output) { v.AddOp2(OP.ResultRow, dest.SdstId, columns); Expr.CacheAffinityChange(parse, dest.SdstId, columns); } else v.AddOp1(OP.Yield, dest.SDParmId); break; } } Expr.ReleaseTempReg(parse, regRow); Expr.ReleaseTempReg(parse, regRowid); // The bottom of the loop v.ResolveLabel(addrContinue); v.AddOp2(OP.Next, tabId, addr); v.ResolveLabel(addrBreak); if (dest2 == SRT.Output || dest2 == SRT.Coroutine) v.AddOp2(OP.Close, pseudoTab, 0); }
static void CodeOffset(Vdbe v, Select p, int continueId) { if (p.OffsetId != 0 && continueId != 0) { v.AddOp2(OP.AddImm, p.OffsetId, -1); int addr = v.AddOp1(OP.IfNeg, p.OffsetId); v.AddOp2(OP.Goto, 0, continueId); v.Comment("skip OFFSET records"); v.JumpHere(addr); } }
public void FKCheck(Table table, int regOld, int regNew) { Context ctx = Ctx; // Database handle bool isIgnoreErrors = DisableTriggers; // Exactly one of regOld and regNew should be non-zero. Debug.Assert((regOld == 0) != (regNew == 0)); // If foreign-keys are disabled, this function is a no-op. if ((ctx.Flags & Context.FLAG.ForeignKeys) == 0) { return; } int db = SchemaToIndex(ctx, table.Schema); // Index of database containing pTab string dbName = ctx.DBs[db].Name; // Name of database containing pTab // Loop through all the foreign key constraints for which pTab is the child table (the table that the foreign key definition is part of). FKey fkey; // Used to iterate through FKs for (fkey = table.FKeys; fkey != null; fkey = fkey.NextFrom) { bool isIgnore = false; // Find the parent table of this foreign key. Also find a unique index on the parent key columns in the parent table. If either of these // schema items cannot be located, set an error in pParse and return early. Table to = (DisableTriggers ? FindTable(ctx, fkey.To, dbName) : LocateTable(false, fkey.To, dbName)); // Parent table of foreign key pFKey Index index = null; // Index on key columns in pTo int[] frees = null; if (to == null || LocateFkeyIndex(to, fkey, out index, out frees) != 0) { Debug.Assert(!isIgnoreErrors || (regOld != 0 && regNew == 0)); if (!isIgnoreErrors || ctx.MallocFailed) { return; } if (to == null) { // If isIgnoreErrors is true, then a table is being dropped. In this se SQLite runs a "DELETE FROM xxx" on the table being dropped // before actually dropping it in order to check FK constraints. If the parent table of an FK constraint on the current table is // missing, behave as if it is empty. i.e. decrement the FK counter for each row of the current table with non-NULL keys. Vdbe v = GetVdbe(); int jumpId = v.CurrentAddr() + fkey.Cols.length + 1; for (int i = 0; i < fkey.Cols.length; i++) { int regId = fkey.Cols[i].From + regOld + 1; v.AddOp2(OP.IsNull, regId, jumpId); } v.AddOp2(OP.FkCounter, fkey.IsDeferred, -1); } continue; } Debug.Assert(fkey.Cols.length == 1 || (frees != null && index != null)); int[] cols; if (frees != null) { cols = frees; } else { int col = fkey.Cols[0].From; cols = new int[1]; cols[0] = col; } for (int i = 0; i < fkey.Cols.length; i++) { if (cols[i] == table.PKey) { cols[i] = -1; } #if !OMIT_AUTHORIZATION // Request permission to read the parent key columns. If the authorization callback returns SQLITE_IGNORE, behave as if any // values read from the parent table are NULL. if (ctx.Auth != null) { string colName = to.Cols[index != null ? index.Columns[i] : to.PKey].Name; ARC rcauth = Auth.ReadColumn(this, to.Name, colName, db); isIgnore = (rcauth == ARC.IGNORE); } #endif } // Take a shared-cache advisory read-lock on the parent table. Allocate a cursor to use to search the unique index on the parent key columns // in the parent table. TableLock(db, to.Id, false, to.Name); Tabs++; if (regOld != 0) // A row is being removed from the child table. Search for the parent. If the parent does not exist, removing the child row resolves an outstanding foreign key constraint violation. { FKLookupParent(this, db, to, index, fkey, cols, regOld, -1, isIgnore); } if (regNew != 0) // A row is being added to the child table. If a parent row cannot be found, adding the child row has violated the FK constraint. { FKLookupParent(this, db, to, index, fkey, cols, regNew, +1, isIgnore); } C._tagfree(ctx, ref frees); } // Loop through all the foreign key constraints that refer to this table for (fkey = FKReferences(table); fkey != null; fkey = fkey.NextTo) { if (!fkey.IsDeferred && Toplevel == null && !IsMultiWrite) { Debug.Assert(regOld == 0 && regNew != 0); // Inserting a single row into a parent table cannot cause an immediate foreign key violation. So do nothing in this case. continue; } Index index = null; // Foreign key index for pFKey int[] cols = null; if (LocateFkeyIndex(table, fkey, out index, out cols) != 0) { if (isIgnoreErrors || ctx.MallocFailed) { return; } continue; } Debug.Assert(cols != null || fkey.Cols.length == 1); // Create a SrcList structure containing a single table (the table the foreign key that refers to this table is attached to). This // is required for the sqlite3WhereXXX() interface. SrcList src = SrcListAppend(ctx, null, null, null); if (src != null) { SrcList.SrcListItem item = src.Ids[0]; item.Table = fkey.From; item.Name = fkey.From.Name; item.Table.Refs++; item.Cursor = Tabs++; if (regNew != 0) { FKScanChildren(this, src, table, index, fkey, cols, regNew, -1); } if (regOld != 0) { // If there is a RESTRICT action configured for the current operation on the parent table of this FK, then throw an exception // immediately if the FK constraint is violated, even if this is a deferred trigger. That's what RESTRICT means. To defer checking // the constraint, the FK should specify NO ACTION (represented using OE_None). NO ACTION is the default. FKScanChildren(this, src, table, index, fkey, cols, regOld, 1); } item.Name = null; SrcListDelete(ctx, ref src); } C._tagfree(ctx, ref cols); } }
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; } }