Add PRAGMA foreign_key_check. Name the child and parent tables as part of
a "foreign key mismatch" error. FossilOrigin-Name: e408dc9080594dc464b8763dece6b365772c6105
This commit is contained in:
+9
-7
@@ -142,7 +142,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
|
||||
@@ -178,7 +178,7 @@
|
||||
** 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 */
|
||||
@@ -275,7 +275,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;
|
||||
@@ -736,7 +738,7 @@ 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 ){
|
||||
@@ -816,7 +818,7 @@ void sqlite3FkCheck(
|
||||
continue;
|
||||
}
|
||||
|
||||
if( locateFkeyIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ){
|
||||
if( sqlite3FkLocateIndex(pParse, pTab, pFKey, &pIdx, &aiCol) ){
|
||||
if( !isIgnoreErrors || db->mallocFailed ) return;
|
||||
continue;
|
||||
}
|
||||
@@ -871,7 +873,7 @@ 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]);
|
||||
}
|
||||
@@ -997,7 +999,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++){
|
||||
|
||||
+114
@@ -1114,6 +1114,120 @@ void sqlite3Pragma(
|
||||
}else
|
||||
#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */
|
||||
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
if( sqlite3StrICmp(zLeft, "foreign_key_check")==0 ){
|
||||
FKey *pFK; /* A foreign key constraint */
|
||||
Table *pTab; /* Child table contain "REFERENCES" keyword */
|
||||
Table *pParent; /* Parent table that child points to */
|
||||
Index *pIdx; /* Index in the parent table */
|
||||
int i; /* Loop counter: Foreign key number for pTab */
|
||||
int j; /* Loop counter: Field of the foreign key */
|
||||
HashElem *k; /* Loop counter: Next table in schema */
|
||||
int x; /* result variable */
|
||||
int regResult; /* 3 registers to hold a result row */
|
||||
int regKey; /* Register to hold key for checking the FK */
|
||||
int regRow; /* Registers to hold a row from pTab */
|
||||
int addrTop; /* Top of a loop checking foreign keys */
|
||||
int addrOk; /* Jump here if the key is OK */
|
||||
int *aiCols; /* child to parent column mapping */
|
||||
|
||||
if( sqlite3ReadSchema(pParse) ) goto pragma_out;
|
||||
regResult = pParse->nMem+1;
|
||||
pParse->nMem += 4;
|
||||
regKey = ++pParse->nMem;
|
||||
regRow = ++pParse->nMem;
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
sqlite3VdbeSetNumCols(v, 4);
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "table", SQLITE_STATIC);
|
||||
sqlite3VdbeSetColName(v, 1, COLNAME_NAME, "rowid", SQLITE_STATIC);
|
||||
sqlite3VdbeSetColName(v, 2, COLNAME_NAME, "parent", SQLITE_STATIC);
|
||||
sqlite3VdbeSetColName(v, 3, COLNAME_NAME, "fkid", SQLITE_STATIC);
|
||||
sqlite3CodeVerifySchema(pParse, iDb);
|
||||
k = sqliteHashFirst(&db->aDb[iDb].pSchema->tblHash);
|
||||
while( k ){
|
||||
if( zRight ){
|
||||
pTab = sqlite3LocateTable(pParse, 0, zRight, zDb);
|
||||
k = 0;
|
||||
}else{
|
||||
pTab = (Table*)sqliteHashData(k);
|
||||
k = sqliteHashNext(k);
|
||||
}
|
||||
if( pTab==0 || pTab->pFKey==0 ) continue;
|
||||
sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
|
||||
if( pTab->nCol+regRow>pParse->nMem ) pParse->nMem = pTab->nCol + regRow;
|
||||
sqlite3OpenTable(pParse, 0, iDb, pTab, OP_OpenRead);
|
||||
sqlite3VdbeAddOp4(v, OP_String8, 0, regResult, 0, pTab->zName,
|
||||
P4_TRANSIENT);
|
||||
for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){
|
||||
pParent = sqlite3LocateTable(pParse, 0, pFK->zTo, zDb);
|
||||
if( pParent==0 ) break;
|
||||
pIdx = 0;
|
||||
sqlite3TableLock(pParse, iDb, pParent->tnum, 0, pParent->zName);
|
||||
x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, 0);
|
||||
if( x==0 ){
|
||||
if( pIdx==0 ){
|
||||
sqlite3OpenTable(pParse, i, iDb, pParent, OP_OpenRead);
|
||||
}else{
|
||||
KeyInfo *pKey = sqlite3IndexKeyinfo(pParse, pIdx);
|
||||
sqlite3VdbeAddOp3(v, OP_OpenRead, i, pIdx->tnum, iDb);
|
||||
sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF);
|
||||
}
|
||||
}else{
|
||||
k = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( pFK ) break;
|
||||
if( pParse->nTab<i ) pParse->nTab = i;
|
||||
addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, 0);
|
||||
for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){
|
||||
pParent = sqlite3LocateTable(pParse, 0, pFK->zTo, zDb);
|
||||
assert( pParent!=0 );
|
||||
pIdx = 0;
|
||||
aiCols = 0;
|
||||
x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, &aiCols);
|
||||
assert( x==0 );
|
||||
addrOk = sqlite3VdbeMakeLabel(v);
|
||||
if( pIdx==0 ){
|
||||
int iKey = pFK->aCol[0].iFrom;
|
||||
assert( iKey>=0 && iKey<pTab->nCol );
|
||||
if( iKey!=pTab->iPKey ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, 0, iKey, regRow);
|
||||
sqlite3ColumnDefault(v, pTab, iKey, regRow);
|
||||
sqlite3VdbeAddOp2(v, OP_IsNull, regRow, addrOk);
|
||||
sqlite3VdbeAddOp2(v, OP_MustBeInt, regRow,
|
||||
sqlite3VdbeCurrentAddr(v)+3);
|
||||
}else{
|
||||
sqlite3VdbeAddOp2(v, OP_Rowid, 0, regRow);
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_NotExists, i, 0, regRow);
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, addrOk);
|
||||
sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2);
|
||||
}else{
|
||||
for(j=0; j<pFK->nCol; j++){
|
||||
sqlite3ExprCodeGetColumnOfTable(v, pTab, 0,
|
||||
aiCols ? aiCols[j] : pFK->aCol[0].iFrom, regRow+j);
|
||||
sqlite3VdbeAddOp2(v, OP_IsNull, regRow+j, addrOk);
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regRow, pFK->nCol, regKey);
|
||||
sqlite3VdbeChangeP4(v, -1,
|
||||
sqlite3IndexAffinityStr(v,pIdx), P4_TRANSIENT);
|
||||
sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0);
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Rowid, 0, regResult+1);
|
||||
sqlite3VdbeAddOp4(v, OP_String8, 0, regResult+2, 0,
|
||||
pFK->zTo, P4_TRANSIENT);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, i-1, regResult+3);
|
||||
sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, 4);
|
||||
sqlite3VdbeResolveLabel(v, addrOk);
|
||||
sqlite3DbFree(db, aiCols);
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Next, 0, addrTop+1);
|
||||
sqlite3VdbeJumpHere(v, addrTop);
|
||||
}
|
||||
}else
|
||||
#endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */
|
||||
|
||||
#ifndef NDEBUG
|
||||
if( sqlite3StrICmp(zLeft, "parser_trace")==0 ){
|
||||
if( zRight ){
|
||||
|
||||
@@ -3210,8 +3210,10 @@ const char *sqlite3JournalModename(int);
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_FOREIGN_KEY
|
||||
void sqlite3FkDelete(sqlite3 *, Table*);
|
||||
int sqlite3FkLocateIndex(Parse*,Table*,FKey*,Index**,int**);
|
||||
#else
|
||||
#define sqlite3FkDelete(a,b)
|
||||
#define sqlite3FkLocateIndex(a,b,c,d,e)
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user