Fix the DROP TABLE command so that it cannot be used to bypass foreign key constraints (if foreign key support is enabled).

FossilOrigin-Name: 8353808c9e70412fdee31bfda7810e948f1c7485
diff --git a/src/build.c b/src/build.c
index 544014d..215b3c0 100644
--- a/src/build.c
+++ b/src/build.c
@@ -2061,6 +2061,7 @@
       sqlite3VdbeAddOp0(v, OP_VBegin);
     }
 #endif
+    sqlite3FkDropTable(pParse, pName, pTab);
 
     /* Drop all triggers associated with the table being dropped. Code
     ** is generated to remove entries from sqlite_master and/or
diff --git a/src/fkey.c b/src/fkey.c
index 804d1ed..706e054 100644
--- a/src/fkey.c
+++ b/src/fkey.c
@@ -588,6 +588,64 @@
 }
 
 /*
+** This function is called to generate code that runs when table pTab is
+** being dropped from the database. The SrcList passed as the second argument
+** to this function contains a single entry guaranteed to resolve to
+** table pTab.
+**
+** Normally, no code is required. However, if either
+**
+**   (a) The table is the parent table of a FK constraint, or
+**   (b) The table is the child table of a deferred FK constraint and it is
+**       determined at runtime that there are outstanding deferred FK 
+**       constraint violations in the database,
+**
+** then the equivalent of "DELETE FROM <tbl>" is executed before dropping
+** the table from the database. Triggers are disabled while running this
+** DELETE, but foreign key actions are not.
+*/
+void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){
+  sqlite3 *db = pParse->db;
+  if( (db->flags&SQLITE_ForeignKeys) && !IsVirtual(pTab) && !pTab->pSelect ){
+    int iSkip = 0;
+    Vdbe *v = sqlite3GetVdbe(pParse);
+
+    assert( v );                  /* VDBE has already been allocated */
+    if( sqlite3FkReferences(pTab)==0 ){
+      /* Search for a deferred foreign key constraint for which this table
+      ** is the child table. If one cannot be found, return without 
+      ** generating any VDBE code. If one can be found, then jump over
+      ** the entire DELETE if there are no outstanding deferred constraints
+      ** when this statement is run.  */
+      FKey *p;
+      for(p=pTab->pFKey; p; p=p->pNextFrom){
+        if( p->isDeferred ) break;
+      }
+      if( !p ) return;
+      iSkip = sqlite3VdbeMakeLabel(v);
+      sqlite3VdbeAddOp2(v, OP_FkIfZero, 1, iSkip);
+    }
+
+    pParse->disableTriggers = 1;
+    sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0);
+    pParse->disableTriggers = 0;
+
+    /* 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
+    );
+
+    if( iSkip ){
+      sqlite3VdbeResolveLabel(v, iSkip);
+    }
+  }
+}
+
+/*
 ** 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.
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 60414f7..2ae1ec8 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -2122,6 +2122,7 @@
   u32 oldmask;         /* Mask of old.* columns referenced */
   u8 eTriggerOp;       /* TK_UPDATE, TK_INSERT or TK_DELETE */
   u8 eOrconf;          /* Default ON CONFLICT policy for trigger steps */
+  u8 disableTriggers;  /* True to disable triggers */
 
   /* Above is constant between recursions.  Below is reset before and after
   ** each recursion */
@@ -2949,15 +2950,17 @@
 */
 #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER)
   void sqlite3FkCheck(Parse*, Table*, ExprList*, int, int);
+  void sqlite3FkDropTable(Parse*, SrcList *, Table*);
   void sqlite3FkActions(Parse*, Table*, ExprList*, int);
   int sqlite3FkRequired(Parse*, Table*, ExprList*);
   u32 sqlite3FkOldmask(Parse*, Table*, ExprList*);
   FKey *sqlite3FkReferences(Table *);
 #else
-  #define sqlite3FkCheck(a,b,c,d,e)
   #define sqlite3FkActions(a,b,c,d)
-  #define sqlite3FkRequired(a,b,c) 0
+  #define sqlite3FkCheck(a,b,c,d,e)
+  #define sqlite3FkDropTable(a,b,c)
   #define sqlite3FkOldmask(a,b,c)  0
+  #define sqlite3FkRequired(a,b,c) 0
 #endif
 #ifndef SQLITE_OMIT_FOREIGN_KEY
   void sqlite3FkDelete(Table*);
diff --git a/src/trigger.c b/src/trigger.c
index dfe8e9a..319f9bc 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -50,6 +50,10 @@
   Schema * const pTmpSchema = pParse->db->aDb[1].pSchema;
   Trigger *pList = 0;                  /* List of triggers to return */
 
+  if( pParse->disableTriggers ){
+    return 0;
+  }
+
   if( pTmpSchema!=pTab->pSchema ){
     HashElem *p;
     for(p=sqliteHashFirst(&pTmpSchema->trigHash); p; p=sqliteHashNext(p)){