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); }
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); }