If an update does not modify any child or parent key columns, omit foreign key processing for the statement.

FossilOrigin-Name: edff3500058eb8ad2381f855ef7a09ecb680f7b8
diff --git a/src/delete.c b/src/delete.c
index d93ee36..9904d98 100644
--- a/src/delete.c
+++ b/src/delete.c
@@ -342,7 +342,7 @@
   ** this optimization caused the row change count (the value returned by 
   ** API function sqlite3_count_changes) to be set incorrectly.  */
   if( rcauth==SQLITE_OK && pWhere==0 && !pTrigger && !IsVirtual(pTab) 
-   && 0==sqlite3FkRequired(pParse, pTab, 0)
+   && 0==sqlite3FkRequired(pParse, pTab, 0, 0)
   ){
     assert( !isView );
     sqlite3VdbeAddOp4(v, OP_Clear, pTab->tnum, iDb, memCnt,
@@ -492,14 +492,14 @@
  
   /* If there are any triggers to fire, allocate a range of registers to
   ** use for the old.* references in the triggers.  */
-  if( sqlite3FkRequired(pParse, pTab, 0) || pTrigger ){
+  if( sqlite3FkRequired(pParse, pTab, 0, 0) || pTrigger ){
     u32 mask;                     /* Mask of OLD.* columns in use */
     int iCol;                     /* Iterator used while populating OLD.* */
 
     /* TODO: Could use temporary registers here. Also could attempt to
     ** avoid copying the contents of the rowid register.  */
     mask = sqlite3TriggerOldmask(pParse, pTrigger, 0, pTab, onconf);
-    mask |= sqlite3FkOldmask(pParse, pTab, 0);
+    mask |= sqlite3FkOldmask(pParse, pTab);
     iOld = pParse->nMem+1;
     pParse->nMem += (1 + pTab->nCol);
 
@@ -528,7 +528,7 @@
     /* Do FK processing. This call checks that any FK constraints that
     ** refer to this table (i.e. constraints attached to other tables) 
     ** are not violated by deleting this row.  */
-    sqlite3FkCheck(pParse, pTab, 0, iOld, 0);
+    sqlite3FkCheck(pParse, pTab, iOld, 0);
   }
 
   /* Delete the index and table entries. Skip this step if pTab is really
diff --git a/src/fkey.c b/src/fkey.c
index 7ec256d..4d6a32a 100644
--- a/src/fkey.c
+++ b/src/fkey.c
@@ -672,14 +672,11 @@
 ** For an UPDATE operation, this function is called twice. Once before
 ** the original record is deleted from the table using the calling convention
 ** described for DELETE. Then again after the original record is deleted
-** but before the new record is inserted using the INSERT convention. In
-** both cases parameter pChanges is passed the list of columns being 
-** updated by the statement.
+** but before the new record is inserted using the INSERT convention. 
 */
 void sqlite3FkCheck(
   Parse *pParse,                  /* Parse context */
   Table *pTab,                    /* Row is being deleted from this table */ 
-  ExprList *pChanges,             /* Changed columns if this is an UPDATE */
   int regOld,                     /* Previous row data is stored here */
   int regNew                      /* New row data is stored here */
 ){
@@ -725,11 +722,6 @@
     }
     assert( pFKey->nCol==1 || (aiFree && pIdx) );
 
-    /* If the key does not overlap with the pChanges list, skip this FK. */
-    if( pChanges ){
-      /* TODO */
-    }
-
     if( aiFree ){
       aiCol = aiFree;
     }else{
@@ -782,14 +774,6 @@
     }
     assert( aiCol || pFKey->nCol==1 );
 
-    /* Check if this update statement has modified any of the child key 
-    ** columns for this foreign key constraint. If it has not, there is 
-    ** no need to search the child table for rows in violation. This is
-    ** just an optimization. Things would work fine without this check.  */
-    if( pChanges ){
-      /* TODO */
-    }
-
     /* 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.  */
@@ -822,14 +806,11 @@
 
 /*
 ** This function is called before generating code to update or delete a 
-** row contained in table pTab. If the operation is an update, then 
-** pChanges is a pointer to the list of columns to modify. If this is a 
-** delete, then pChanges is NULL.
+** row contained in table pTab.
 */
 u32 sqlite3FkOldmask(
   Parse *pParse,                  /* Parse context */
-  Table *pTab,                    /* Table being modified */
-  ExprList *pChanges              /* Non-NULL for UPDATE operations */
+  Table *pTab                     /* Table being modified */
 ){
   u32 mask = 0;
   if( pParse->db->flags&SQLITE_ForeignKeys ){
@@ -851,9 +832,13 @@
 
 /*
 ** This function is called before generating code to update or delete a 
-** row contained in table pTab. If the operation is an update, then 
-** pChanges is a pointer to the list of columns to modify. If this is a 
-** delete, then pChanges is NULL.
+** row contained in table pTab. If the operation is a DELETE, then
+** parameter aChange is passed a NULL value. For an UPDATE, aChange points
+** to an array of size N, where N is the number of columns in table pTab.
+** If the i'th column is not modified by the UPDATE, then the corresponding 
+** entry in the aChange[] array is set to -1. If the column is modified,
+** the value is 0 or greater. Parameter chngRowid is set to true if the
+** UPDATE statement modifies the rowid fields of the table.
 **
 ** If any foreign key processing will be required, this function returns
 ** true. If there is no foreign key related processing, this function 
@@ -862,10 +847,45 @@
 int sqlite3FkRequired(
   Parse *pParse,                  /* Parse context */
   Table *pTab,                    /* Table being modified */
-  ExprList *pChanges              /* Non-NULL for UPDATE operations */
+  int *aChange,                   /* Non-NULL for UPDATE operations */
+  int chngRowid                   /* True for UPDATE that affects rowid */
 ){
   if( pParse->db->flags&SQLITE_ForeignKeys ){
-    if( sqlite3FkReferences(pTab) || pTab->pFKey ) return 1;
+    if( !aChange ){
+      /* A DELETE operation. Foreign key processing is required if the 
+      ** table in question is either the child or parent table for any 
+      ** foreign key constraint.  */
+      return (sqlite3FkReferences(pTab) || pTab->pFKey);
+    }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;
+        }
+      }
+
+      /* 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;
+            }
+          }
+        }
+      }
+    }
   }
   return 0;
 }
diff --git a/src/insert.c b/src/insert.c
index d714074..94b741a 100644
--- a/src/insert.c
+++ b/src/insert.c
@@ -979,7 +979,7 @@
       sqlite3GenerateConstraintChecks(pParse, pTab, baseCur, regIns, aRegIdx,
           keyColumn>=0, 0, onError, endOfLoop, &isReplace
       );
-      sqlite3FkCheck(pParse, pTab, 0, 0, regIns);
+      sqlite3FkCheck(pParse, pTab, 0, regIns);
       sqlite3CompleteInsertion(
           pParse, pTab, baseCur, regIns, aRegIdx, 0, appendFlag, isReplace==0
       );
@@ -1271,7 +1271,7 @@
           pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0);
         }
         sqlite3MultiWrite(pParse);
-        if( pTrigger || sqlite3FkRequired(pParse, pTab, 0) ){
+        if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){
           sqlite3GenerateRowDelete(
               pParse, pTab, baseCur, regRowid, 0, pTrigger, OE_Replace
           );
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 2ae1ec8..f97a81c 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -2949,18 +2949,18 @@
 ** provided (enforcement of FK constraints requires the triggers sub-system).
 */
 #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
-  void sqlite3FkCheck(Parse*, Table*, ExprList*, int, int);
+  void sqlite3FkCheck(Parse*, Table*, int, int);
   void sqlite3FkDropTable(Parse*, SrcList *, Table*);
   void sqlite3FkActions(Parse*, Table*, ExprList*, int);
-  int sqlite3FkRequired(Parse*, Table*, ExprList*);
-  u32 sqlite3FkOldmask(Parse*, Table*, ExprList*);
+  int sqlite3FkRequired(Parse*, Table*, int*, int);
+  u32 sqlite3FkOldmask(Parse*, Table*);
   FKey *sqlite3FkReferences(Table *);
 #else
   #define sqlite3FkActions(a,b,c,d)
-  #define sqlite3FkCheck(a,b,c,d,e)
+  #define sqlite3FkCheck(a,b,c,d)
   #define sqlite3FkDropTable(a,b,c)
-  #define sqlite3FkOldmask(a,b,c)  0
-  #define sqlite3FkRequired(a,b,c) 0
+  #define sqlite3FkOldmask(a,b)      0
+  #define sqlite3FkRequired(a,b,c,d) 0
 #endif
 #ifndef SQLITE_OMIT_FOREIGN_KEY
   void sqlite3FkDelete(Table*);
diff --git a/src/update.c b/src/update.c
index f443dbc..3703c1b 100644
--- a/src/update.c
+++ b/src/update.c
@@ -159,8 +159,6 @@
 # define isView 0
 #endif
 
-  hasFK = sqlite3FkRequired(pParse, pTab, pChanges);
-
   if( sqlite3ViewGetColumnNames(pParse, pTab) ){
     goto update_cleanup;
   }
@@ -230,6 +228,8 @@
 #endif
   }
 
+  hasFK = sqlite3FkRequired(pParse, pTab, aXRef, chngRowid);
+
   /* Allocate memory for the array aRegIdx[].  There is one entry in the
   ** array for each index associated with table being updated.  Fill in
   ** the value with a register number for indices that are to be used
@@ -389,7 +389,7 @@
   /* If there are triggers on this table, populate an array of registers 
   ** with the required old.* column data.  */
   if( hasFK || pTrigger ){
-    u32 oldmask = sqlite3FkOldmask(pParse, pTab, pChanges);
+    u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0);
     oldmask |= sqlite3TriggerOldmask(pParse, pTrigger, pChanges, pTab, onError);
     for(i=0; i<pTab->nCol; i++){
       if( aXRef[i]<0 || oldmask==0xffffffff || (oldmask & (1<<i)) ){
@@ -445,7 +445,9 @@
         aRegIdx, (chngRowid?regOldRowid:0), 1, onError, addr, 0);
 
     /* Do FK constraint checks. */
-    sqlite3FkCheck(pParse, pTab, pChanges, regOldRowid, 0);
+    if( hasFK ){
+      sqlite3FkCheck(pParse, pTab, regOldRowid, 0);
+    }
 
     /* Delete the index entries associated with the current record.  */
     j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regOldRowid);
@@ -457,7 +459,9 @@
     }
     sqlite3VdbeJumpHere(v, j1);
 
-    sqlite3FkCheck(pParse, pTab, pChanges, 0, regNewRowid);
+    if( hasFK ){
+      sqlite3FkCheck(pParse, pTab, 0, regNewRowid);
+    }
   
     /* Insert the new index entries and the new record. */
     sqlite3CompleteInsertion(pParse, pTab, iCur, regNewRowid, aRegIdx, 1, 0, 0);
@@ -465,7 +469,9 @@
     /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to
     ** handle rows (possibly in other tables) that refer via a foreign key
     ** to the row just updated. */ 
-    sqlite3FkActions(pParse, pTab, pChanges, regOldRowid);
+    if( hasFK ){
+      sqlite3FkActions(pParse, pTab, pChanges, regOldRowid);
+    }
   }
 
   /* Increment the row counter