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