| Index: third_party/sqlite/src/src/fkey.c
|
| diff --git a/third_party/sqlite/src/src/fkey.c b/third_party/sqlite/src/src/fkey.c
|
| index 34fdfda5d94636c2194cc3a1926128fea9dbea40..e816bd95daf30bcfa985eb89785c1e0c95d37a7a 100644
|
| --- a/third_party/sqlite/src/src/fkey.c
|
| +++ b/third_party/sqlite/src/src/fkey.c
|
| @@ -21,8 +21,9 @@
|
| ** --------------------------
|
| **
|
| ** Foreign keys in SQLite come in two flavours: deferred and immediate.
|
| -** If an immediate foreign key constraint is violated, SQLITE_CONSTRAINT
|
| -** is returned and the current statement transaction rolled back. If a
|
| +** If an immediate foreign key constraint is violated,
|
| +** SQLITE_CONSTRAINT_FOREIGNKEY is returned and the current
|
| +** statement transaction rolled back. If a
|
| ** deferred foreign key constraint is violated, no action is taken
|
| ** immediately. However if the application attempts to commit the
|
| ** transaction before fixing the constraint violation, the attempt fails.
|
| @@ -86,7 +87,8 @@
|
| ** Immediate constraints are usually handled similarly. The only difference
|
| ** is that the counter used is stored as part of each individual statement
|
| ** object (struct Vdbe). If, after the statement has run, its immediate
|
| -** constraint counter is greater than zero, it returns SQLITE_CONSTRAINT
|
| +** constraint counter is greater than zero,
|
| +** it returns SQLITE_CONSTRAINT_FOREIGNKEY
|
| ** and the statement transaction is rolled back. An exception is an INSERT
|
| ** statement that inserts a single row only (no triggers). In this case,
|
| ** instead of using a counter, an exception is thrown immediately if the
|
| @@ -142,7 +144,7 @@
|
| ** A foreign key constraint requires that the key columns in the parent
|
| ** table are collectively subject to a UNIQUE or PRIMARY KEY constraint.
|
| ** Given that pParent is the parent table for foreign key constraint pFKey,
|
| -** search the schema a unique index on the parent key columns.
|
| +** search the schema for a unique index on the parent key columns.
|
| **
|
| ** If successful, zero is returned. If the parent key is an INTEGER PRIMARY
|
| ** KEY column, then output variable *ppIdx is set to NULL. Otherwise, *ppIdx
|
| @@ -171,14 +173,14 @@
|
| **
|
| ** 4) No parent key columns were provided explicitly as part of the
|
| ** foreign key definition, and the PRIMARY KEY of the parent table
|
| -** consists of a a different number of columns to the child key in
|
| +** consists of a different number of columns to the child key in
|
| ** the child table.
|
| **
|
| ** then non-zero is returned, and a "foreign key mismatch" error loaded
|
| ** into pParse. If an OOM error occurs, non-zero is returned and the
|
| ** pParse->db->mallocFailed flag is set.
|
| */
|
| -static int locateFkeyIndex(
|
| +int sqlite3FkLocateIndex(
|
| Parse *pParse, /* Parse context to store any error in */
|
| Table *pParent, /* Parent table of FK constraint pFKey */
|
| FKey *pFKey, /* Foreign key to find index for */
|
| @@ -223,7 +225,7 @@ static int locateFkeyIndex(
|
| }
|
|
|
| for(pIdx=pParent->pIndex; pIdx; pIdx=pIdx->pNext){
|
| - if( pIdx->nColumn==nCol && pIdx->onError!=OE_None ){
|
| + if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) ){
|
| /* pIdx is a UNIQUE index (or a PRIMARY KEY) and has the right number
|
| ** of columns. If each indexed column corresponds to a foreign key
|
| ** column of pFKey, then this index is a winner. */
|
| @@ -231,8 +233,8 @@ static int locateFkeyIndex(
|
| if( zKey==0 ){
|
| /* If zKey is NULL, then this foreign key is implicitly mapped to
|
| ** the PRIMARY KEY of table pParent. The PRIMARY KEY index may be
|
| - ** identified by the test (Index.autoIndex==2). */
|
| - if( pIdx->autoIndex==2 ){
|
| + ** identified by the test. */
|
| + if( IsPrimaryKeyIndex(pIdx) ){
|
| if( aiCol ){
|
| int i;
|
| for(i=0; i<nCol; i++) aiCol[i] = pFKey->aCol[i].iFrom;
|
| @@ -246,7 +248,7 @@ static int locateFkeyIndex(
|
| ** the default collation sequences for each column. */
|
| int i, j;
|
| for(i=0; i<nCol; i++){
|
| - int iCol = pIdx->aiColumn[i]; /* Index of column in parent tbl */
|
| + i16 iCol = pIdx->aiColumn[i]; /* Index of column in parent tbl */
|
| char *zDfltColl; /* Def. collation for column */
|
| char *zIdxCol; /* Name of indexed column */
|
|
|
| @@ -275,7 +277,9 @@ static int locateFkeyIndex(
|
|
|
| if( !pIdx ){
|
| if( !pParse->disableTriggers ){
|
| - sqlite3ErrorMsg(pParse, "foreign key mismatch");
|
| + sqlite3ErrorMsg(pParse,
|
| + "foreign key mismatch - \"%w\" referencing \"%w\"",
|
| + pFKey->pFrom->zName, pFKey->zTo);
|
| }
|
| sqlite3DbFree(pParse->db, aiCol);
|
| return 1;
|
| @@ -336,10 +340,11 @@ static void fkLookupParent(
|
| ** search for a matching row in the parent table. */
|
| if( nIncr<0 ){
|
| sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, iOk);
|
| + VdbeCoverage(v);
|
| }
|
| for(i=0; i<pFKey->nCol; i++){
|
| int iReg = aiCol[i] + regData + 1;
|
| - sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iOk);
|
| + sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iOk); VdbeCoverage(v);
|
| }
|
|
|
| if( isIgnore==0 ){
|
| @@ -356,17 +361,19 @@ static void fkLookupParent(
|
| ** will have INTEGER affinity applied to it, which may not be correct. */
|
| sqlite3VdbeAddOp2(v, OP_SCopy, aiCol[0]+1+regData, regTemp);
|
| iMustBeInt = sqlite3VdbeAddOp2(v, OP_MustBeInt, regTemp, 0);
|
| + VdbeCoverage(v);
|
|
|
| /* 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( pTab==pFKey->pFrom && nIncr==1 ){
|
| - sqlite3VdbeAddOp3(v, OP_Eq, regData, iOk, regTemp);
|
| + sqlite3VdbeAddOp3(v, OP_Eq, regData, iOk, regTemp); VdbeCoverage(v);
|
| + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL);
|
| }
|
|
|
| sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead);
|
| - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regTemp);
|
| + sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regTemp); VdbeCoverage(v);
|
| sqlite3VdbeAddOp2(v, OP_Goto, 0, iOk);
|
| sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2);
|
| sqlite3VdbeJumpHere(v, iMustBeInt);
|
| @@ -375,10 +382,9 @@ static void fkLookupParent(
|
| int nCol = pFKey->nCol;
|
| int regTemp = sqlite3GetTempRange(pParse, nCol);
|
| int regRec = sqlite3GetTempReg(pParse);
|
| - KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx);
|
|
|
| sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb);
|
| - sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF);
|
| + sqlite3VdbeSetP4KeyInfo(pParse, pIdx);
|
| for(i=0; i<nCol; i++){
|
| sqlite3VdbeAddOp2(v, OP_Copy, aiCol[i]+1+regData, regTemp+i);
|
| }
|
| @@ -386,35 +392,49 @@ static void fkLookupParent(
|
| /* 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. */
|
| + ** 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( pTab==pFKey->pFrom && nIncr==1 ){
|
| int iJump = sqlite3VdbeCurrentAddr(v) + nCol + 1;
|
| for(i=0; i<nCol; i++){
|
| int iChild = aiCol[i]+1+regData;
|
| int iParent = pIdx->aiColumn[i]+1+regData;
|
| - sqlite3VdbeAddOp3(v, OP_Ne, iChild, iJump, iParent);
|
| + assert( aiCol[i]!=pTab->iPKey );
|
| + if( pIdx->aiColumn[i]==pTab->iPKey ){
|
| + /* The parent key is a composite key that includes the IPK column */
|
| + iParent = regData;
|
| + }
|
| + sqlite3VdbeAddOp3(v, OP_Ne, iChild, iJump, iParent); VdbeCoverage(v);
|
| + sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL);
|
| }
|
| sqlite3VdbeAddOp2(v, OP_Goto, 0, iOk);
|
| }
|
|
|
| - sqlite3VdbeAddOp3(v, OP_MakeRecord, regTemp, nCol, regRec);
|
| - sqlite3VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v,pIdx), P4_TRANSIENT);
|
| - sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0);
|
| + sqlite3VdbeAddOp4(v, OP_MakeRecord, regTemp, nCol, regRec,
|
| + sqlite3IndexAffinityStr(v,pIdx), nCol);
|
| + sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0); VdbeCoverage(v);
|
|
|
| sqlite3ReleaseTempReg(pParse, regRec);
|
| sqlite3ReleaseTempRange(pParse, regTemp, nCol);
|
| }
|
| }
|
|
|
| - if( !pFKey->isDeferred && !pParse->pToplevel && !pParse->isMultiWrite ){
|
| + if( !pFKey->isDeferred && !(pParse->db->flags & SQLITE_DeferFKs)
|
| + && !pParse->pToplevel
|
| + && !pParse->isMultiWrite
|
| + ){
|
| /* 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. */
|
| assert( nIncr==1 );
|
| - sqlite3HaltConstraint(
|
| - pParse, OE_Abort, "foreign key constraint failed", P4_STATIC
|
| - );
|
| + sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY,
|
| + OE_Abort, 0, P4_STATIC, P5_ConstraintFK);
|
| }else{
|
| if( nIncr>0 && pFKey->isDeferred==0 ){
|
| sqlite3ParseToplevel(pParse)->mayAbort = 1;
|
| @@ -426,6 +446,62 @@ static void fkLookupParent(
|
| sqlite3VdbeAddOp1(v, OP_Close, iCur);
|
| }
|
|
|
| +
|
| +/*
|
| +** Return an Expr object that refers to a memory register corresponding
|
| +** to column iCol of table pTab.
|
| +**
|
| +** regBase is the first of an array of register that contains the data
|
| +** for pTab. regBase itself holds the rowid. regBase+1 holds the first
|
| +** column. regBase+2 holds the second column, and so forth.
|
| +*/
|
| +static Expr *exprTableRegister(
|
| + Parse *pParse, /* Parsing and code generating context */
|
| + Table *pTab, /* The table whose content is at r[regBase]... */
|
| + int regBase, /* Contents of table pTab */
|
| + i16 iCol /* Which column of pTab is desired */
|
| +){
|
| + Expr *pExpr;
|
| + Column *pCol;
|
| + const char *zColl;
|
| + sqlite3 *db = pParse->db;
|
| +
|
| + pExpr = sqlite3Expr(db, TK_REGISTER, 0);
|
| + if( pExpr ){
|
| + if( iCol>=0 && iCol!=pTab->iPKey ){
|
| + pCol = &pTab->aCol[iCol];
|
| + pExpr->iTable = regBase + iCol + 1;
|
| + pExpr->affinity = pCol->affinity;
|
| + zColl = pCol->zColl;
|
| + if( zColl==0 ) zColl = db->pDfltColl->zName;
|
| + pExpr = sqlite3ExprAddCollateString(pParse, pExpr, zColl);
|
| + }else{
|
| + pExpr->iTable = regBase;
|
| + pExpr->affinity = SQLITE_AFF_INTEGER;
|
| + }
|
| + }
|
| + return pExpr;
|
| +}
|
| +
|
| +/*
|
| +** Return an Expr object that refers to column iCol of table pTab which
|
| +** has cursor iCur.
|
| +*/
|
| +static Expr *exprTableColumn(
|
| + sqlite3 *db, /* The database connection */
|
| + Table *pTab, /* The table whose column is desired */
|
| + int iCursor, /* The open cursor on the table */
|
| + i16 iCol /* The column that is wanted */
|
| +){
|
| + Expr *pExpr = sqlite3Expr(db, TK_COLUMN, 0);
|
| + if( pExpr ){
|
| + pExpr->pTab = pTab;
|
| + pExpr->iTable = iCursor;
|
| + pExpr->iColumn = iCol;
|
| + }
|
| + return pExpr;
|
| +}
|
| +
|
| /*
|
| ** This function is called to generate code executed when a row is deleted
|
| ** from the parent table of foreign key constraint pFKey and, if pFKey is
|
| @@ -441,13 +517,13 @@ static void fkLookupParent(
|
| ** --------------------------------------------------------------------------
|
| ** DELETE immediate Increment the "immediate constraint counter".
|
| ** Or, if the ON (UPDATE|DELETE) action is RESTRICT,
|
| -** throw a "foreign key constraint failed" exception.
|
| +** throw a "FOREIGN KEY constraint failed" exception.
|
| **
|
| ** INSERT immediate Decrement the "immediate constraint counter".
|
| **
|
| ** DELETE deferred Increment the "deferred constraint counter".
|
| ** Or, if the ON (UPDATE|DELETE) action is RESTRICT,
|
| -** throw a "foreign key constraint failed" exception.
|
| +** throw a "FOREIGN KEY constraint failed" exception.
|
| **
|
| ** INSERT deferred Decrement the "deferred constraint counter".
|
| **
|
| @@ -456,12 +532,12 @@ static void fkLookupParent(
|
| */
|
| static void fkScanChildren(
|
| Parse *pParse, /* Parse context */
|
| - SrcList *pSrc, /* SrcList containing the table to scan */
|
| - Table *pTab,
|
| - Index *pIdx, /* Foreign key index */
|
| - FKey *pFKey, /* Foreign key relationship */
|
| + SrcList *pSrc, /* The child table to be scanned */
|
| + Table *pTab, /* The parent table */
|
| + Index *pIdx, /* Index on parent covering the foreign key */
|
| + FKey *pFKey, /* The foreign key linking pSrc to pTab */
|
| int *aiCol, /* Map from pIdx cols to child table cols */
|
| - int regData, /* Referenced table data starts here */
|
| + int regData, /* Parent row data starts here */
|
| int nIncr /* Amount to increment deferred counter by */
|
| ){
|
| sqlite3 *db = pParse->db; /* Database handle */
|
| @@ -472,10 +548,14 @@ static void fkScanChildren(
|
| int iFkIfZero = 0; /* Address of OP_FkIfZero */
|
| Vdbe *v = sqlite3GetVdbe(pParse);
|
|
|
| - assert( !pIdx || pIdx->pTable==pTab );
|
| + assert( pIdx==0 || pIdx->pTable==pTab );
|
| + assert( pIdx==0 || pIdx->nKeyCol==pFKey->nCol );
|
| + assert( pIdx!=0 || pFKey->nCol==1 );
|
| + assert( pIdx!=0 || HasRowid(pTab) );
|
|
|
| if( nIncr<0 ){
|
| iFkIfZero = sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, 0);
|
| + VdbeCoverage(v);
|
| }
|
|
|
| /* Create an Expr object representing an SQL expression like:
|
| @@ -490,26 +570,11 @@ static void fkScanChildren(
|
| Expr *pLeft; /* Value from parent table row */
|
| Expr *pRight; /* Column ref to child table */
|
| Expr *pEq; /* Expression (pLeft = pRight) */
|
| - int iCol; /* Index of column in child table */
|
| + i16 iCol; /* Index of column in child table */
|
| const char *zCol; /* Name of column in child table */
|
|
|
| - pLeft = sqlite3Expr(db, TK_REGISTER, 0);
|
| - if( pLeft ){
|
| - /* Set the collation sequence and affinity of the LHS of each TK_EQ
|
| - ** expression to the parent key column defaults. */
|
| - if( pIdx ){
|
| - Column *pCol;
|
| - iCol = pIdx->aiColumn[i];
|
| - pCol = &pTab->aCol[iCol];
|
| - if( pTab->iPKey==iCol ) iCol = -1;
|
| - pLeft->iTable = regData+iCol+1;
|
| - pLeft->affinity = pCol->affinity;
|
| - pLeft->pColl = sqlite3LocateCollSeq(pParse, pCol->zColl);
|
| - }else{
|
| - pLeft->iTable = regData;
|
| - pLeft->affinity = SQLITE_AFF_INTEGER;
|
| - }
|
| - }
|
| + iCol = pIdx ? pIdx->aiColumn[i] : -1;
|
| + pLeft = exprTableRegister(pParse, pTab, regData, iCol);
|
| iCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom;
|
| assert( iCol>=0 );
|
| zCol = pFKey->pFrom->aCol[iCol].zName;
|
| @@ -518,24 +583,39 @@ static void fkScanChildren(
|
| pWhere = sqlite3ExprAnd(db, pWhere, pEq);
|
| }
|
|
|
| - /* 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 the child table is the same as the parent table, then add terms
|
| + ** to the WHERE clause that prevent this entry from being scanned.
|
| + ** The added WHERE clause terms are like this:
|
| + **
|
| + ** $current_rowid!=rowid
|
| + ** NOT( $current_a==a AND $current_b==b AND ... )
|
| + **
|
| + ** The first form is used for rowid tables. The second form is used
|
| + ** for WITHOUT ROWID tables. In the second form, the primary key is
|
| + ** (a,b,...)
|
| + */
|
| if( pTab==pFKey->pFrom && nIncr>0 ){
|
| - Expr *pEq; /* Expression (pLeft = pRight) */
|
| + Expr *pNe; /* Expression (pLeft != pRight) */
|
| Expr *pLeft; /* Value from parent table row */
|
| Expr *pRight; /* Column ref to child table */
|
| - pLeft = sqlite3Expr(db, TK_REGISTER, 0);
|
| - pRight = sqlite3Expr(db, TK_COLUMN, 0);
|
| - if( pLeft && pRight ){
|
| - pLeft->iTable = regData;
|
| - pLeft->affinity = SQLITE_AFF_INTEGER;
|
| - pRight->iTable = pSrc->a[0].iCursor;
|
| - pRight->iColumn = -1;
|
| + if( HasRowid(pTab) ){
|
| + pLeft = exprTableRegister(pParse, pTab, regData, -1);
|
| + pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, -1);
|
| + pNe = sqlite3PExpr(pParse, TK_NE, pLeft, pRight, 0);
|
| + }else{
|
| + Expr *pEq, *pAll = 0;
|
| + Index *pPk = sqlite3PrimaryKeyIndex(pTab);
|
| + assert( pIdx!=0 );
|
| + for(i=0; i<pPk->nKeyCol; i++){
|
| + i16 iCol = pIdx->aiColumn[i];
|
| + pLeft = exprTableRegister(pParse, pTab, regData, iCol);
|
| + pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, iCol);
|
| + pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0);
|
| + pAll = sqlite3ExprAnd(db, pAll, pEq);
|
| + }
|
| + pNe = sqlite3PExpr(pParse, TK_NOT, pAll, 0, 0);
|
| }
|
| - pEq = sqlite3PExpr(pParse, TK_NE, pLeft, pRight, 0);
|
| - pWhere = sqlite3ExprAnd(db, pWhere, pEq);
|
| + pWhere = sqlite3ExprAnd(db, pWhere, pNe);
|
| }
|
|
|
| /* Resolve the references in the WHERE clause. */
|
| @@ -548,7 +628,7 @@ static void fkScanChildren(
|
| ** clause. If the constraint is not deferred, throw an exception for
|
| ** each row found. Otherwise, for deferred constraints, increment the
|
| ** deferred constraint counter by nIncr for each row selected. */
|
| - pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0);
|
| + pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0);
|
| if( nIncr>0 && pFKey->isDeferred==0 ){
|
| sqlite3ParseToplevel(pParse)->mayAbort = 1;
|
| }
|
| @@ -565,8 +645,8 @@ static void fkScanChildren(
|
| }
|
|
|
| /*
|
| -** This function returns a pointer to the head of a linked list of FK
|
| -** constraints for which table pTab is the parent table. For example,
|
| +** This function returns a linked list of FKey objects (connected by
|
| +** FKey.pNextTo) holding all children of table pTab. For example,
|
| ** given the following schema:
|
| **
|
| ** CREATE TABLE t1(a PRIMARY KEY);
|
| @@ -579,8 +659,7 @@ static void fkScanChildren(
|
| ** table).
|
| */
|
| FKey *sqlite3FkReferences(Table *pTab){
|
| - int nName = sqlite3Strlen30(pTab->zName);
|
| - return (FKey *)sqlite3HashFind(&pTab->pSchema->fkeyHash, pTab->zName, nName);
|
| + return (FKey *)sqlite3HashFind(&pTab->pSchema->fkeyHash, pTab->zName);
|
| }
|
|
|
| /*
|
| @@ -634,11 +713,11 @@ void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){
|
| ** when this statement is run. */
|
| FKey *p;
|
| for(p=pTab->pFKey; p; p=p->pNextFrom){
|
| - if( p->isDeferred ) break;
|
| + if( p->isDeferred || (db->flags & SQLITE_DeferFKs) ) break;
|
| }
|
| if( !p ) return;
|
| iSkip = sqlite3VdbeMakeLabel(v);
|
| - sqlite3VdbeAddOp2(v, OP_FkIfZero, 1, iSkip);
|
| + sqlite3VdbeAddOp2(v, OP_FkIfZero, 1, iSkip); VdbeCoverage(v);
|
| }
|
|
|
| pParse->disableTriggers = 1;
|
| @@ -648,11 +727,18 @@ void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){
|
| /* 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. */
|
| - sqlite3VdbeAddOp2(v, OP_FkIfZero, 0, sqlite3VdbeCurrentAddr(v)+2);
|
| - sqlite3HaltConstraint(
|
| - pParse, OE_Abort, "foreign key constraint failed", P4_STATIC
|
| - );
|
| + ** transactions are not able to rollback schema changes.
|
| + **
|
| + ** If the SQLITE_DeferFKs flag is set, then this is not required, as
|
| + ** the statement transaction will not be rolled back even if FK
|
| + ** constraints are violated.
|
| + */
|
| + if( (db->flags & SQLITE_DeferFKs)==0 ){
|
| + sqlite3VdbeAddOp2(v, OP_FkIfZero, 0, sqlite3VdbeCurrentAddr(v)+2);
|
| + VdbeCoverage(v);
|
| + sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_FOREIGNKEY,
|
| + OE_Abort, 0, P4_STATIC, P5_ConstraintFK);
|
| + }
|
|
|
| if( iSkip ){
|
| sqlite3VdbeResolveLabel(v, iSkip);
|
| @@ -660,6 +746,70 @@ void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){
|
| }
|
| }
|
|
|
| +
|
| +/*
|
| +** The second argument points to an FKey object representing a foreign key
|
| +** for which pTab is the child table. An UPDATE statement against pTab
|
| +** is currently being processed. For each column of the table that is
|
| +** actually updated, the corresponding element in the aChange[] array
|
| +** is zero or greater (if a column is unmodified the corresponding element
|
| +** is set to -1). If the rowid column is modified by the UPDATE statement
|
| +** the bChngRowid argument is non-zero.
|
| +**
|
| +** This function returns true if any of the columns that are part of the
|
| +** child key for FK constraint *p are modified.
|
| +*/
|
| +static int fkChildIsModified(
|
| + Table *pTab, /* Table being updated */
|
| + FKey *p, /* Foreign key for which pTab is the child */
|
| + int *aChange, /* Array indicating modified columns */
|
| + int bChngRowid /* True if rowid is modified by this update */
|
| +){
|
| + int i;
|
| + for(i=0; i<p->nCol; i++){
|
| + int iChildKey = p->aCol[i].iFrom;
|
| + if( aChange[iChildKey]>=0 ) return 1;
|
| + if( iChildKey==pTab->iPKey && bChngRowid ) return 1;
|
| + }
|
| + return 0;
|
| +}
|
| +
|
| +/*
|
| +** The second argument points to an FKey object representing a foreign key
|
| +** for which pTab is the parent table. An UPDATE statement against pTab
|
| +** is currently being processed. For each column of the table that is
|
| +** actually updated, the corresponding element in the aChange[] array
|
| +** is zero or greater (if a column is unmodified the corresponding element
|
| +** is set to -1). If the rowid column is modified by the UPDATE statement
|
| +** the bChngRowid argument is non-zero.
|
| +**
|
| +** This function returns true if any of the columns that are part of the
|
| +** parent key for FK constraint *p are modified.
|
| +*/
|
| +static int fkParentIsModified(
|
| + Table *pTab,
|
| + FKey *p,
|
| + int *aChange,
|
| + int bChngRowid
|
| +){
|
| + int i;
|
| + for(i=0; i<p->nCol; i++){
|
| + char *zKey = p->aCol[i].zCol;
|
| + int iKey;
|
| + for(iKey=0; iKey<pTab->nCol; iKey++){
|
| + if( aChange[iKey]>=0 || (iKey==pTab->iPKey && bChngRowid) ){
|
| + Column *pCol = &pTab->aCol[iKey];
|
| + if( zKey ){
|
| + if( 0==sqlite3StrICmp(pCol->zName, zKey) ) return 1;
|
| + }else if( pCol->colFlags & COLFLAG_PRIMKEY ){
|
| + return 1;
|
| + }
|
| + }
|
| + }
|
| + }
|
| + return 0;
|
| +}
|
| +
|
| /*
|
| ** This function is called when inserting, deleting or updating a row of
|
| ** table pTab to generate VDBE code to perform foreign key constraint
|
| @@ -684,7 +834,9 @@ void sqlite3FkCheck(
|
| Parse *pParse, /* Parse context */
|
| Table *pTab, /* Row is being deleted from this table */
|
| int regOld, /* Previous row data is stored here */
|
| - int regNew /* New row data is stored here */
|
| + int regNew, /* New row data is stored here */
|
| + int *aChange, /* Array indicating UPDATEd columns (or 0) */
|
| + int bChngRowid /* True if rowid is UPDATEd */
|
| ){
|
| sqlite3 *db = pParse->db; /* Database handle */
|
| FKey *pFKey; /* Used to iterate through FKs */
|
| @@ -712,6 +864,13 @@ void sqlite3FkCheck(
|
| int i;
|
| int isIgnore = 0;
|
|
|
| + if( aChange
|
| + && sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0
|
| + && fkChildIsModified(pTab, pFKey, aChange, bChngRowid)==0
|
| + ){
|
| + continue;
|
| + }
|
| +
|
| /* 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
|
| @@ -721,8 +880,25 @@ void sqlite3FkCheck(
|
| }else{
|
| pTo = sqlite3LocateTable(pParse, 0, pFKey->zTo, zDb);
|
| }
|
| - if( !pTo || locateFkeyIndex(pParse, pTo, pFKey, &pIdx, &aiFree) ){
|
| + if( !pTo || sqlite3FkLocateIndex(pParse, pTo, pFKey, &pIdx, &aiFree) ){
|
| + assert( isIgnoreErrors==0 || (regOld!=0 && regNew==0) );
|
| if( !isIgnoreErrors || db->mallocFailed ) return;
|
| + if( pTo==0 ){
|
| + /* If isIgnoreErrors is true, then a table is being dropped. In this
|
| + ** case 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 relevant
|
| + ** FK counter for each row of the current table with non-NULL keys.
|
| + */
|
| + Vdbe *v = sqlite3GetVdbe(pParse);
|
| + int iJump = sqlite3VdbeCurrentAddr(v) + pFKey->nCol + 1;
|
| + for(i=0; i<pFKey->nCol; i++){
|
| + int iReg = pFKey->aCol[i].iFrom + regOld + 1;
|
| + sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iJump); VdbeCoverage(v);
|
| + }
|
| + sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, -1);
|
| + }
|
| continue;
|
| }
|
| assert( pFKey->nCol==1 || (aiFree && pIdx) );
|
| @@ -771,28 +947,34 @@ void sqlite3FkCheck(
|
| sqlite3DbFree(db, aiFree);
|
| }
|
|
|
| - /* Loop through all the foreign key constraints that refer to this table */
|
| + /* Loop through all the foreign key constraints that refer to this table.
|
| + ** (the "child" constraints) */
|
| for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){
|
| Index *pIdx = 0; /* Foreign key index for pFKey */
|
| SrcList *pSrc;
|
| int *aiCol = 0;
|
|
|
| - if( !pFKey->isDeferred && !pParse->pToplevel && !pParse->isMultiWrite ){
|
| + if( aChange && fkParentIsModified(pTab, pFKey, aChange, bChngRowid)==0 ){
|
| + continue;
|
| + }
|
| +
|
| + if( !pFKey->isDeferred && !(db->flags & SQLITE_DeferFKs)
|
| + && !pParse->pToplevel && !pParse->isMultiWrite
|
| + ){
|
| 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;
|
| }
|
|
|
| - if( locateFkeyIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ){
|
| + if( sqlite3FkLocateIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ){
|
| if( !isIgnoreErrors || db->mallocFailed ) return;
|
| continue;
|
| }
|
| assert( aiCol || pFKey->nCol==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. */
|
| + /* Create a SrcList structure containing the child table. We need the
|
| + ** child table as a SrcList for sqlite3WhereBegin() */
|
| pSrc = sqlite3SrcListAppend(db, 0, 0, 0);
|
| if( pSrc ){
|
| struct SrcList_item *pItem = pSrc->a;
|
| @@ -839,15 +1021,16 @@ u32 sqlite3FkOldmask(
|
| }
|
| for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){
|
| Index *pIdx = 0;
|
| - locateFkeyIndex(pParse, pTab, p, &pIdx, 0);
|
| + sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0);
|
| if( pIdx ){
|
| - for(i=0; i<pIdx->nColumn; i++) mask |= COLUMN_MASK(pIdx->aiColumn[i]);
|
| + for(i=0; i<pIdx->nKeyCol; i++) mask |= COLUMN_MASK(pIdx->aiColumn[i]);
|
| }
|
| }
|
| }
|
| return mask;
|
| }
|
|
|
| +
|
| /*
|
| ** This function is called before generating code to update or delete a
|
| ** row contained in table pTab. If the operation is a DELETE, then
|
| @@ -877,31 +1060,16 @@ int sqlite3FkRequired(
|
| }else{
|
| /* This is an UPDATE. Foreign key processing is only required if the
|
| ** operation modifies one or more child or parent key columns. */
|
| - int i;
|
| FKey *p;
|
|
|
| /* Check if any child key columns are being modified. */
|
| for(p=pTab->pFKey; p; p=p->pNextFrom){
|
| - for(i=0; i<p->nCol; i++){
|
| - int iChildKey = p->aCol[i].iFrom;
|
| - if( aChange[iChildKey]>=0 ) return 1;
|
| - if( iChildKey==pTab->iPKey && chngRowid ) return 1;
|
| - }
|
| + if( fkChildIsModified(pTab, p, aChange, chngRowid) ) return 1;
|
| }
|
|
|
| /* Check if any parent key columns are being modified. */
|
| for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){
|
| - for(i=0; i<p->nCol; i++){
|
| - char *zKey = p->aCol[i].zCol;
|
| - int iKey;
|
| - for(iKey=0; iKey<pTab->nCol; iKey++){
|
| - Column *pCol = &pTab->aCol[iKey];
|
| - if( (zKey ? !sqlite3StrICmp(pCol->zName, zKey) : pCol->isPrimKey) ){
|
| - if( aChange[iKey]>=0 ) return 1;
|
| - if( iKey==pTab->iPKey && chngRowid ) return 1;
|
| - }
|
| - }
|
| - }
|
| + if( fkParentIsModified(pTab, p, aChange, chngRowid) ) return 1;
|
| }
|
| }
|
| }
|
| @@ -964,7 +1132,7 @@ static Trigger *fkActionTrigger(
|
| int i; /* Iterator variable */
|
| Expr *pWhen = 0; /* WHEN clause for the trigger */
|
|
|
| - if( locateFkeyIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ) return 0;
|
| + if( sqlite3FkLocateIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ) return 0;
|
| assert( aiCol || pFKey->nCol==1 );
|
|
|
| for(i=0; i<pFKey->nCol; i++){
|
| @@ -1047,7 +1215,7 @@ static Trigger *fkActionTrigger(
|
|
|
| tFrom.z = zFrom;
|
| tFrom.n = nFrom;
|
| - pRaise = sqlite3Expr(db, TK_RAISE, "foreign key constraint failed");
|
| + pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed");
|
| if( pRaise ){
|
| pRaise->affinity = OE_Abort;
|
| }
|
| @@ -1095,6 +1263,7 @@ static Trigger *fkActionTrigger(
|
| fkTriggerDelete(db, pTrigger);
|
| return 0;
|
| }
|
| + assert( pStep!=0 );
|
|
|
| switch( action ){
|
| case OE_Restrict:
|
| @@ -1126,7 +1295,9 @@ void sqlite3FkActions(
|
| Parse *pParse, /* Parse context */
|
| Table *pTab, /* Table being updated or deleted from */
|
| ExprList *pChanges, /* Change-list for UPDATE, NULL for DELETE */
|
| - int regOld /* Address of array containing old row */
|
| + int regOld, /* Address of array containing old row */
|
| + int *aChange, /* Array indicating UPDATEd columns (or 0) */
|
| + int bChngRowid /* True if rowid is UPDATEd */
|
| ){
|
| /* If foreign-key support is enabled, iterate through all FKs that
|
| ** refer to table pTab. If there is an action associated with the FK
|
| @@ -1135,9 +1306,11 @@ void sqlite3FkActions(
|
| if( pParse->db->flags&SQLITE_ForeignKeys ){
|
| FKey *pFKey; /* Iterator variable */
|
| for(pFKey = sqlite3FkReferences(pTab); pFKey; pFKey=pFKey->pNextTo){
|
| - Trigger *pAction = fkActionTrigger(pParse, pTab, pFKey, pChanges);
|
| - if( pAction ){
|
| - sqlite3CodeRowTriggerDirect(pParse, pAction, pTab, regOld, OE_Abort, 0);
|
| + if( aChange==0 || fkParentIsModified(pTab, pFKey, aChange, bChngRowid) ){
|
| + Trigger *pAct = fkActionTrigger(pParse, pTab, pFKey, pChanges);
|
| + if( pAct ){
|
| + sqlite3CodeRowTriggerDirect(pParse, pAct, pTab, regOld, OE_Abort, 0);
|
| + }
|
| }
|
| }
|
| }
|
| @@ -1164,7 +1337,7 @@ void sqlite3FkDelete(sqlite3 *db, Table *pTab){
|
| }else{
|
| void *p = (void *)pFKey->pNextTo;
|
| const char *z = (p ? pFKey->pNextTo->zTo : pFKey->zTo);
|
| - sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, sqlite3Strlen30(z), p);
|
| + sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, p);
|
| }
|
| if( pFKey->pNextTo ){
|
| pFKey->pNextTo->pPrevTo = pFKey->pPrevTo;
|
|
|