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 e816bd95daf30bcfa985eb89785c1e0c95d37a7a..2abd06c693b7614977c4bc8c221b0a889ed80b69 100644 |
--- a/third_party/sqlite/src/src/fkey.c |
+++ b/third_party/sqlite/src/src/fkey.c |
@@ -249,16 +249,16 @@ int sqlite3FkLocateIndex( |
int i, j; |
for(i=0; i<nCol; i++){ |
i16 iCol = pIdx->aiColumn[i]; /* Index of column in parent tbl */ |
- char *zDfltColl; /* Def. collation for column */ |
+ const char *zDfltColl; /* Def. collation for column */ |
char *zIdxCol; /* Name of indexed column */ |
+ if( iCol<0 ) break; /* No foreign keys against expression indexes */ |
+ |
/* If the index uses a collation sequence that is different from |
** the default collation sequence for the column, this index is |
** unusable. Bail out early in this case. */ |
zDfltColl = pParent->aCol[iCol].zColl; |
- if( !zDfltColl ){ |
- zDfltColl = "BINARY"; |
- } |
+ if( !zDfltColl ) zDfltColl = sqlite3StrBINARY; |
if( sqlite3StrICmp(pIdx->azColl[i], zDfltColl) ) break; |
zIdxCol = pParent->aCol[iCol].zName; |
@@ -374,7 +374,7 @@ static void fkLookupParent( |
sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead); |
sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regTemp); VdbeCoverage(v); |
- sqlite3VdbeAddOp2(v, OP_Goto, 0, iOk); |
+ sqlite3VdbeGoto(v, iOk); |
sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2); |
sqlite3VdbeJumpHere(v, iMustBeInt); |
sqlite3ReleaseTempReg(pParse, regTemp); |
@@ -404,6 +404,7 @@ static void fkLookupParent( |
for(i=0; i<nCol; i++){ |
int iChild = aiCol[i]+1+regData; |
int iParent = pIdx->aiColumn[i]+1+regData; |
+ assert( pIdx->aiColumn[i]>=0 ); |
assert( aiCol[i]!=pTab->iPKey ); |
if( pIdx->aiColumn[i]==pTab->iPKey ){ |
/* The parent key is a composite key that includes the IPK column */ |
@@ -412,11 +413,11 @@ static void fkLookupParent( |
sqlite3VdbeAddOp3(v, OP_Ne, iChild, iJump, iParent); VdbeCoverage(v); |
sqlite3VdbeChangeP5(v, SQLITE_JUMPIFNULL); |
} |
- sqlite3VdbeAddOp2(v, OP_Goto, 0, iOk); |
+ sqlite3VdbeGoto(v, iOk); |
} |
sqlite3VdbeAddOp4(v, OP_MakeRecord, regTemp, nCol, regRec, |
- sqlite3IndexAffinityStr(v,pIdx), nCol); |
+ sqlite3IndexAffinityStr(pParse->db,pIdx), nCol); |
sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0); VdbeCoverage(v); |
sqlite3ReleaseTempReg(pParse, regRec); |
@@ -437,7 +438,7 @@ static void fkLookupParent( |
OE_Abort, 0, P4_STATIC, P5_ConstraintFK); |
}else{ |
if( nIncr>0 && pFKey->isDeferred==0 ){ |
- sqlite3ParseToplevel(pParse)->mayAbort = 1; |
+ sqlite3MayAbort(pParse); |
} |
sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); |
} |
@@ -509,6 +510,10 @@ static Expr *exprTableColumn( |
** code for an SQL UPDATE operation, this function may be called twice - |
** once to "delete" the old row and once to "insert" the new row. |
** |
+** Parameter nIncr is passed -1 when inserting a row (as this may decrease |
+** the number of FK violations in the db) or +1 when deleting one (as this |
+** may increase the number of FK constraint problems). |
+** |
** The code generated by this function scans through the rows in the child |
** table that correspond to the parent table row being deleted or inserted. |
** For each child row found, one of the following actions is taken: |
@@ -608,6 +613,7 @@ static void fkScanChildren( |
assert( pIdx!=0 ); |
for(i=0; i<pPk->nKeyCol; i++){ |
i16 iCol = pIdx->aiColumn[i]; |
+ assert( iCol>=0 ); |
pLeft = exprTableRegister(pParse, pTab, regData, iCol); |
pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, iCol); |
pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0); |
@@ -625,13 +631,9 @@ static void fkScanChildren( |
sqlite3ResolveExprNames(&sNameContext, pWhere); |
/* Create VDBE to loop through the entries in pSrc 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 nIncr for each row selected. */ |
+ ** clause. For each row found, increment either the deferred or immediate |
+ ** foreign key constraint counter. */ |
pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0); |
- if( nIncr>0 && pFKey->isDeferred==0 ){ |
- sqlite3ParseToplevel(pParse)->mayAbort = 1; |
- } |
sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); |
if( pWInfo ){ |
sqlite3WhereEnd(pWInfo); |
@@ -811,6 +813,24 @@ static int fkParentIsModified( |
} |
/* |
+** Return true if the parser passed as the first argument is being |
+** used to code a trigger that is really a "SET NULL" action belonging |
+** to trigger pFKey. |
+*/ |
+static int isSetNullAction(Parse *pParse, FKey *pFKey){ |
+ Parse *pTop = sqlite3ParseToplevel(pParse); |
+ if( pTop->pTriggerPrg ){ |
+ Trigger *p = pTop->pTriggerPrg->pTrigger; |
+ if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull) |
+ || (p==pFKey->apTrigger[1] && pFKey->aAction[1]==OE_SetNull) |
+ ){ |
+ 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 |
** processing for the operation. |
@@ -862,7 +882,7 @@ void sqlite3FkCheck( |
int *aiCol; |
int iCol; |
int i; |
- int isIgnore = 0; |
+ int bIgnore = 0; |
if( aChange |
&& sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0 |
@@ -913,6 +933,7 @@ void sqlite3FkCheck( |
if( aiCol[i]==pTab->iPKey ){ |
aiCol[i] = -1; |
} |
+ assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); |
#ifndef SQLITE_OMIT_AUTHORIZATION |
/* Request permission to read the parent key columns. If the |
** authorization callback returns SQLITE_IGNORE, behave as if any |
@@ -921,7 +942,7 @@ void sqlite3FkCheck( |
int rcauth; |
char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName; |
rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb); |
- isIgnore = (rcauth==SQLITE_IGNORE); |
+ bIgnore = (rcauth==SQLITE_IGNORE); |
} |
#endif |
} |
@@ -936,12 +957,18 @@ void sqlite3FkCheck( |
/* 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(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1,isIgnore); |
+ fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1, bIgnore); |
} |
- if( regNew!=0 ){ |
+ if( regNew!=0 && !isSetNullAction(pParse, pFKey) ){ |
/* 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(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1,isIgnore); |
+ ** be found, adding the child row has violated the FK constraint. |
+ ** |
+ ** If this operation is being performed as part of a trigger program |
+ ** that is actually a "SET NULL" action belonging to this very |
+ ** foreign key, then omit this scan altogether. As all child key |
+ ** values are guaranteed to be NULL, it is not possible for adding |
+ ** this row to cause an FK violation. */ |
+ fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1, bIgnore); |
} |
sqlite3DbFree(db, aiFree); |
@@ -962,8 +989,8 @@ void sqlite3FkCheck( |
&& !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. */ |
+ /* Inserting a single row into a parent table cannot cause (or fix) |
+ ** an immediate foreign key violation. So do nothing in this case. */ |
continue; |
} |
@@ -987,13 +1014,28 @@ void sqlite3FkCheck( |
fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, 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. */ |
+ int eAction = pFKey->aAction[aChange!=0]; |
fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1); |
+ /* If this is a deferred FK constraint, or a CASCADE or SET NULL |
+ ** action applies, then any foreign key violations caused by |
+ ** removing the parent key will be rectified by the action trigger. |
+ ** So do not set the "may-abort" flag in this case. |
+ ** |
+ ** Note 1: If the FK is declared "ON UPDATE CASCADE", then the |
+ ** may-abort flag will eventually be set on this statement anyway |
+ ** (when this function is called as part of processing the UPDATE |
+ ** within the action trigger). |
+ ** |
+ ** Note 2: At first glance it may seem like SQLite could simply omit |
+ ** all OP_FkCounter related scans when either CASCADE or SET NULL |
+ ** applies. The trouble starts if the CASCADE or SET NULL action |
+ ** trigger causes other triggers or action rules attached to the |
+ ** child table to fire. In these cases the fk constraint counters |
+ ** might be set incorrectly if any OP_FkCounter related scans are |
+ ** omitted. */ |
+ if( !pFKey->isDeferred && eAction!=OE_Cascade && eAction!=OE_SetNull ){ |
+ sqlite3MayAbort(pParse); |
+ } |
} |
pItem->zName = 0; |
sqlite3SrcListDelete(db, pSrc); |
@@ -1023,7 +1065,10 @@ u32 sqlite3FkOldmask( |
Index *pIdx = 0; |
sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0); |
if( pIdx ){ |
- for(i=0; i<pIdx->nKeyCol; i++) mask |= COLUMN_MASK(pIdx->aiColumn[i]); |
+ for(i=0; i<pIdx->nKeyCol; i++){ |
+ assert( pIdx->aiColumn[i]>=0 ); |
+ mask |= COLUMN_MASK(pIdx->aiColumn[i]); |
+ } |
} |
} |
} |
@@ -1145,7 +1190,9 @@ static Trigger *fkActionTrigger( |
iFromCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom; |
assert( iFromCol>=0 ); |
- tToCol.z = pIdx ? pTab->aCol[pIdx->aiColumn[i]].zName : "oid"; |
+ assert( pIdx!=0 || (pTab->iPKey>=0 && pTab->iPKey<pTab->nCol) ); |
+ assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); |
+ tToCol.z = pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zName; |
tFromCol.z = pFKey->pFrom->aCol[iFromCol].zName; |
tToCol.n = sqlite3Strlen30(tToCol.z); |
@@ -1157,10 +1204,10 @@ static Trigger *fkActionTrigger( |
** parent table are used for the comparison. */ |
pEq = sqlite3PExpr(pParse, TK_EQ, |
sqlite3PExpr(pParse, TK_DOT, |
- sqlite3PExpr(pParse, TK_ID, 0, 0, &tOld), |
- sqlite3PExpr(pParse, TK_ID, 0, 0, &tToCol) |
+ sqlite3ExprAlloc(db, TK_ID, &tOld, 0), |
+ sqlite3ExprAlloc(db, TK_ID, &tToCol, 0) |
, 0), |
- sqlite3PExpr(pParse, TK_ID, 0, 0, &tFromCol) |
+ sqlite3ExprAlloc(db, TK_ID, &tFromCol, 0) |
, 0); |
pWhere = sqlite3ExprAnd(db, pWhere, pEq); |
@@ -1172,12 +1219,12 @@ static Trigger *fkActionTrigger( |
if( pChanges ){ |
pEq = sqlite3PExpr(pParse, TK_IS, |
sqlite3PExpr(pParse, TK_DOT, |
- sqlite3PExpr(pParse, TK_ID, 0, 0, &tOld), |
- sqlite3PExpr(pParse, TK_ID, 0, 0, &tToCol), |
+ sqlite3ExprAlloc(db, TK_ID, &tOld, 0), |
+ sqlite3ExprAlloc(db, TK_ID, &tToCol, 0), |
0), |
sqlite3PExpr(pParse, TK_DOT, |
- sqlite3PExpr(pParse, TK_ID, 0, 0, &tNew), |
- sqlite3PExpr(pParse, TK_ID, 0, 0, &tToCol), |
+ sqlite3ExprAlloc(db, TK_ID, &tNew, 0), |
+ sqlite3ExprAlloc(db, TK_ID, &tToCol, 0), |
0), |
0); |
pWhen = sqlite3ExprAnd(db, pWhen, pEq); |
@@ -1187,8 +1234,8 @@ static Trigger *fkActionTrigger( |
Expr *pNew; |
if( action==OE_Cascade ){ |
pNew = sqlite3PExpr(pParse, TK_DOT, |
- sqlite3PExpr(pParse, TK_ID, 0, 0, &tNew), |
- sqlite3PExpr(pParse, TK_ID, 0, 0, &tToCol) |
+ sqlite3ExprAlloc(db, TK_ID, &tNew, 0), |
+ sqlite3ExprAlloc(db, TK_ID, &tToCol, 0) |
, 0); |
}else if( action==OE_SetDflt ){ |
Expr *pDflt = pFKey->pFrom->aCol[iFromCol].pDflt; |
@@ -1235,13 +1282,12 @@ static Trigger *fkActionTrigger( |
pTrigger = (Trigger *)sqlite3DbMallocZero(db, |
sizeof(Trigger) + /* struct Trigger */ |
sizeof(TriggerStep) + /* Single step in trigger program */ |
- nFrom + 1 /* Space for pStep->target.z */ |
+ nFrom + 1 /* Space for pStep->zTarget */ |
); |
if( pTrigger ){ |
pStep = pTrigger->step_list = (TriggerStep *)&pTrigger[1]; |
- pStep->target.z = (char *)&pStep[1]; |
- pStep->target.n = nFrom; |
- memcpy((char *)pStep->target.z, zFrom, nFrom); |
+ pStep->zTarget = (char *)&pStep[1]; |
+ memcpy((char *)pStep->zTarget, zFrom, nFrom); |
pStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); |
pStep->pExprList = sqlite3ExprListDup(db, pList, EXPRDUP_REDUCE); |