static void UpdateVirtualTable(Parse parse, SrcList src, Table table, ExprList changes, Expr rowid, int[] xrefs, Expr where_, int onError) { int i; Context ctx = parse.Ctx; // Database connection VTable vtable = VTable.GetVTable(ctx, table); SelectDest dest = new SelectDest(); // Construct the SELECT statement that will find the new values for all updated rows. ExprList list = ExprList.Append(parse, 0, Expr.Expr(ctx, TK.ID, "_rowid_")); // The result set of the SELECT statement if (rowid != null) { list = ExprList.Append(parse, list, Expr.Dup(ctx, rowid, 0)); } Debug.Assert(table.PKey < 0); for (i = 0; i < table.Cols.length; i++) { Expr expr = (xrefs[i] >= 0 ? Expr.Dup(ctx, changes.Ids[xrefs[i]].Expr, 0) : Expr.Expr(ctx, TK.ID, table.Cols[i].Name)); // Temporary expression list = ExprList.Append(parse, list, expr); } Select select = Select.New(parse, list, src, where_, null, null, null, 0, null, null); // The SELECT statement // Create the ephemeral table into which the update results will be stored. Vdbe v = parse.V; // Virtual machine under construction Debug.Assert(v != null); int ephemTab = parse.Tabs++; // Table holding the result of the SELECT v.AddOp2(OP.OpenEphemeral, ephemTab, table.Cols.length + 1 + (rowid != null ? 1 : 0)); v.ChangeP5(BTREE_UNORDERED); // fill the ephemeral table Select.DestInit(dest, SRT.Table, ephemTab); Select.Select(parse, select, ref dest); // Generate code to scan the ephemeral table and call VUpdate. int regId = ++parse.Mems;// First register in set passed to OP_VUpdate parse.Mems += table.Cols.length + 1; int addr = v.AddOp2(OP.Rewind, ephemTab, 0); // Address of top of loop v.AddOp3(OP.Column, ephemTab, 0, regId); v.AddOp3(OP.Column, ephemTab, (rowid != null ? 1 : 0), regId + 1); for (i = 0; i < table.nCol; i++) { v.AddOp3(OP.Column, ephemTab, i + 1 + (rowid != null ? 1 : 0), regId + 2 + i); } sqlite3VtabMakeWritable(parse, table); v.AddOp4(OP_VUpdate, 0, table.Cols.length + 2, regId, vtable, P4_VTAB); v.ChangeP5((byte)(onError == OE_Default ? OE_Abort : onError)); parse.MayAbort(); v.AddOp2(OP.Next, ephemTab, addr + 1); v.JumpHere(addr); v.AddOp2(OP.Close, ephemTab, 0); // Cleanup Select.Delete(ctx, ref select); }
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); }
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); } }