Fix triggers to work in an ATTACHed database.  Ticket #295. (CVS 915)

FossilOrigin-Name: 1e5e00fb73c308378efd034cb291caf338c9fe84
diff --git a/src/build.c b/src/build.c
index 6db4092..44c4827 100644
--- a/src/build.c
+++ b/src/build.c
@@ -23,7 +23,7 @@
 **     ROLLBACK
 **     PRAGMA
 **
-** $Id: build.c,v 1.145 2003/04/15 01:19:48 drh Exp $
+** $Id: build.c,v 1.146 2003/04/17 22:57:53 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -109,7 +109,10 @@
 /*
 ** Locate the in-memory structure that describes 
 ** a particular database table given the name
-** of that table.  Return NULL if not found.
+** of that table and (optionally) the name of the database
+** containing the table.  Return NULL if not found.
+**
+** See also sqliteLocateTable().
 */
 Table *sqliteFindTable(sqlite *db, const char *zName, const char *zDatabase){
   Table *p = 0;
@@ -125,7 +128,48 @@
 
 /*
 ** Locate the in-memory structure that describes 
-** a particular index given the name of that index.
+** a particular database table given the name
+** of that table and (optionally) the name of the database
+** containing the table.  Return NULL if not found.
+**
+** If pParse->useDb is not negative, then the table must be
+** located in that database.  If a different database is specified,
+** an error message is generated into pParse->zErrMsg.
+*/
+Table *sqliteLocateTable(Parse *pParse, const char *zName, const char *zDbase){
+  sqlite *db;
+  const char *zUse;
+  Table *p;
+  db = pParse->db;
+  if( pParse->useDb<0 ){
+    p = sqliteFindTable(db, zName, zDbase);
+  }else {
+    assert( pParse->useDb<db->nDb );
+    assert( db->aDb[pParse->useDb].pBt!=0 );
+    zUse = db->aDb[pParse->useDb].zName;
+    if( zDbase && pParse->useDb!=1 && sqliteStrICmp(zDbase, zUse)!=0 ){
+      sqliteErrorMsg(pParse,"cannot use database %s in this context", zDbase);
+      return 0;
+    }
+    p = sqliteFindTable(db, zName, zUse);
+    if( p==0 && pParse->useDb==1 && zDbase==0 ){
+      p = sqliteFindTable(db, zName, 0);
+    }
+  }
+  if( p==0 ){
+    if( zDbase ){
+      sqliteErrorMsg(pParse, "no such table: %s.%s", zDbase, zName);
+    }else{
+      sqliteErrorMsg(pParse, "no such table: %s", zName);
+    }
+  }
+  return p;
+}
+
+/*
+** Locate the in-memory structure that describes 
+** a particular index given the name of that index
+** and the name of the database that contains the index.
 ** Return NULL if not found.
 */
 Index *sqliteFindIndex(sqlite *db, const char *zName, const char *zDb){
@@ -2078,7 +2122,13 @@
   if( (pParse->db->flags & SQLITE_InTrans)==0 ){
     sqliteVdbeAddOp(v, OP_Transaction, 1, 0);
     if( !tempOnly ){
+      int i;
+      sqlite *db = pParse->db;
       sqliteVdbeAddOp(v, OP_Transaction, 0, 0);
+      for(i=2; i<db->nDb; i++){
+        if( db->aDb[i].pBt==0 ) continue;
+        sqliteVdbeAddOp(v, OP_Transaction, i, 0);
+      }
       sqliteCodeVerifySchema(pParse);
     }
   }else if( setCheckpoint ){
diff --git a/src/delete.c b/src/delete.c
index c2c6d5b..9d88de4 100644
--- a/src/delete.c
+++ b/src/delete.c
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle DELETE FROM statements.
 **
-** $Id: delete.c,v 1.51 2003/04/15 19:22:23 drh Exp $
+** $Id: delete.c,v 1.52 2003/04/17 22:57:53 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -27,11 +27,7 @@
   for(i=0; i<pSrc->nSrc; i++){
     const char *zTab = pSrc->a[i].zName;
     const char *zDb = pSrc->a[i].zDatabase;
-    pTab = sqliteFindTable(pParse->db, zTab, zDb);
-    if( pTab==0 ){
-      sqliteErrorMsg(pParse, "no such table: %S", pSrc, 0);
-      break;
-    }
+    pTab = sqliteLocateTable(pParse, zTab, zDb);
     pSrc->a[i].pTab = pTab;
   }
   return pTab;
@@ -62,8 +58,6 @@
 ){
   Vdbe *v;               /* The virtual database engine */
   Table *pTab;           /* The table from which records will be deleted */
-  char *zTab;            /* Name of the table from which we are deleting */
-  char *zDb;             /* Name of database containing table zTab */
   int end, addr;         /* A couple addresses of generated code */
   int i;                 /* Loop counter */
   WhereInfo *pWInfo;     /* Information about the WHERE clause */
@@ -83,37 +77,25 @@
   db = pParse->db;
   assert( pTabList->nSrc==1 );
 
-  /* Check for the special case of a VIEW with one or more ON DELETE triggers 
-  ** defined 
-  */
-  zTab = pTabList->a[0].zName;
-  zDb = pTabList->a[0].zDatabase;
-  if( zTab != 0 ){
-    pTab = sqliteFindTable(pParse->db, zTab, zDb);
-    if( pTab ){
-      before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
-                             TK_DELETE, TK_BEFORE, TK_ROW, 0);
-      after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
-                             TK_DELETE, TK_AFTER, TK_ROW, 0);
-      row_triggers_exist = before_triggers || after_triggers;
-    }
-    if( row_triggers_exist &&  pTab->pSelect ){
-      /* Just fire VIEW triggers */
-      sqliteSrcListDelete(pTabList);
-      sqliteViewTriggers(pParse, pTab, pWhere, OE_Replace, 0);
-      return;
-    }
-  }
-
   /* Locate the table which we want to delete.  This table has to be
   ** put in an SrcList structure because some of the subroutines we
   ** will be calling are designed to work with multiple tables and expect
   ** an SrcList* parameter instead of just a Table* parameter.
   */
   pTab = sqliteSrcListLookup(pParse, pTabList);
-  if( pTab==0 || sqliteIsReadOnly(pParse, pTab) ){
-    goto delete_from_cleanup;
+  if( pTab==0 )  goto delete_from_cleanup;
+  before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
+                         TK_DELETE, TK_BEFORE, TK_ROW, 0);
+  after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
+                         TK_DELETE, TK_AFTER, TK_ROW, 0);
+  row_triggers_exist = before_triggers || after_triggers;
+  if( row_triggers_exist &&  pTab->pSelect ){
+    /* Just fire VIEW triggers */
+    sqliteSrcListDelete(pTabList);
+    sqliteViewTriggers(pParse, pTab, pWhere, OE_Replace, 0);
+    return;
   }
+  if( sqliteIsReadOnly(pParse, pTab) ) goto delete_from_cleanup;
   assert( pTab->pSelect==0 );  /* This table is not a view */
   if( sqliteAuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0) ){
     goto delete_from_cleanup;
diff --git a/src/main.c b/src/main.c
index c38e3d5..d2900b1 100644
--- a/src/main.c
+++ b/src/main.c
@@ -14,7 +14,7 @@
 ** other files are for internal use by SQLite and should not be
 ** accessed by users of the library.
 **
-** $Id: main.c,v 1.125 2003/04/16 20:24:52 drh Exp $
+** $Id: main.c,v 1.126 2003/04/17 22:57:54 drh Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -69,6 +69,7 @@
         sParse.db = pData->db;
         sParse.initFlag = 1;
         sParse.iDb = atoi(argv[4]);
+        sParse.useDb = -1;
         sParse.newTnum = atoi(argv[2]);
         sParse.useCallback = 1;
         sqliteRunParser(&sParse, argv[3], pData->pzErrMsg);
@@ -320,6 +321,7 @@
   sParse.xCallback = sqliteInitCallback;
   sParse.pArg = (void*)&initData;
   sParse.initFlag = 1;
+  sParse.useDb = -1;
   sParse.useCallback = 1;
   if( iDb==0 ){
     sqliteRunParser(&sParse,
@@ -719,6 +721,7 @@
   sParse.db = db;
   sParse.xCallback = xCallback;
   sParse.pArg = pArg;
+  sParse.useDb = -1;
   sParse.useCallback = ppVm==0;
 #ifndef SQLITE_OMIT_TRACE
   if( db->xTrace ) db->xTrace(db->pTraceArg, zSql);
diff --git a/src/parse.y b/src/parse.y
index 605cc6c..c3e14fa 100644
--- a/src/parse.y
+++ b/src/parse.y
@@ -14,7 +14,7 @@
 ** the parser.  Lemon will also generate a header file containing
 ** numeric codes for all of the tokens.
 **
-** @(#) $Id: parse.y,v 1.94 2003/03/31 00:30:48 drh Exp $
+** @(#) $Id: parse.y,v 1.95 2003/04/17 22:57:54 drh Exp $
 */
 %token_prefix TK_
 %token_type {Token}
@@ -794,7 +794,9 @@
 
 %type trigger_cmd_list {TriggerStep *}
 trigger_cmd_list(A) ::= trigger_cmd(X) SEMI trigger_cmd_list(Y). {
-  X->pNext = Y ; A = X; }
+  X->pNext = Y;
+  A = X;
+}
 trigger_cmd_list(A) ::= . { A = 0; }
 
 %type trigger_cmd {TriggerStep *}
diff --git a/src/select.c b/src/select.c
index 013db33..3aef002 100644
--- a/src/select.c
+++ b/src/select.c
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle SELECT statements in SQLite.
 **
-** $Id: select.c,v 1.131 2003/04/17 12:44:24 drh Exp $
+** $Id: select.c,v 1.132 2003/04/17 22:57:54 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -908,10 +908,8 @@
     }else{
       /* An ordinary table or view name in the FROM clause */
       pTabList->a[i].pTab = pTab = 
-        sqliteFindTable(pParse->db, pTabList->a[i].zName,
-                                    pTabList->a[i].zDatabase);
+        sqliteLocateTable(pParse,pTabList->a[i].zName,pTabList->a[i].zDatabase);
       if( pTab==0 ){
-        sqliteErrorMsg(pParse, "no such table: %S", pTabList, i);
         return 1;
       }
       if( pTab->pSelect ){
diff --git a/src/shell.c b/src/shell.c
index 00601df..4873d35 100644
--- a/src/shell.c
+++ b/src/shell.c
@@ -12,7 +12,7 @@
 ** This file contains code to implement the "sqlite" command line
 ** utility for accessing SQLite databases.
 **
-** $Id: shell.c,v 1.69 2003/04/17 02:54:14 drh Exp $
+** $Id: shell.c,v 1.70 2003/04/17 22:57:54 drh Exp $
 */
 #include <stdlib.h>
 #include <string.h>
@@ -285,7 +285,8 @@
       }
       if( p->cnt++>0 ) fprintf(p->out,"\n");
       for(i=0; i<nArg; i++){
-        fprintf(p->out,"%*s = %s\n", w, azCol[i], azArg[i] ? azArg[i] : p->nullvalue);
+        fprintf(p->out,"%*s = %s\n", w, azCol[i], 
+                azArg[i] ? azArg[i] : p->nullvalue);
       }
       break;
     }
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 473492d..74a8752 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.174 2003/04/16 02:17:35 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.175 2003/04/17 22:57:54 drh Exp $
 */
 #include "config.h"
 #include "sqlite.h"
@@ -225,8 +225,8 @@
 ** Each database file to be accessed by the system is an instance
 ** of the following structure.  There are normally two of these structures
 ** in the sqlite.aDb[] array.  aDb[0] is the main database file and
-** aDb[1] is the database file used to hold temporary tables.  But
-** additional databases may be attached to the engine.
+** aDb[1] is the database file used to hold temporary tables.  Additional
+** databases may be attached.
 */
 struct Db {
   char *zName;         /* Name of this database */
@@ -825,6 +825,7 @@
                        ** other than after an OP_Transaction */
   u8 iDb;              /* Index of database whose schema is being parsed */
   u8 useCallback;      /* True if callbacks should be used to report results */
+  int useDb;           /* Restrict references to tables in this database */
   int newTnum;         /* Table number to use when reparsing CREATE TABLEs */
   int nErr;            /* Number of errors seen */
   int nTab;            /* Number of previously allocated VDBE cursors */
@@ -918,6 +919,7 @@
 struct TriggerStep {
   int op;              /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */
   int orconf;          /* OE_Rollback etc. */
+  Trigger *pTrig;      /* The trigger that this step is a part of */
 
   Select *pSelect;     /* Valid for SELECT and sometimes 
 			  INSERT steps (when pExprList == 0) */
@@ -1062,6 +1064,7 @@
 void sqliteExprIfTrue(Parse*, Expr*, int, int);
 void sqliteExprIfFalse(Parse*, Expr*, int, int);
 Table *sqliteFindTable(sqlite*,const char*, const char*);
+Table *sqliteLocateTable(Parse*,const char*, const char*);
 Index *sqliteFindIndex(sqlite*,const char*, const char*);
 void sqliteUnlinkAndDeleteIndex(sqlite*,Index*);
 void sqliteCopy(Parse*, SrcList*, Token*, Token*, int);
diff --git a/src/trigger.c b/src/trigger.c
index 71a784a..ca6dfa6 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -65,7 +65,7 @@
   if( !tab ){
     goto trigger_cleanup;
   }
-  if( tab->iDb>=2 ){
+  if( tab->iDb>=2 && !pParse->initFlag ){
     sqliteErrorMsg(pParse, "triggers may not be added to auxiliary "
        "database %s", db->aDb[tab->iDb].zName);
     goto trigger_cleanup;
@@ -124,6 +124,11 @@
   sqliteIdListDelete(pColumns);
   nt->foreach = foreach;
   nt->step_list = pStepList;
+  while( pStepList ){
+    pStepList->pTrig = nt;
+    pStepList = pStepList->pNext;
+  }
+  pStepList = nt->step_list;
 
   /* if we are not initializing, and this trigger is not on a TEMP table, 
   ** build the sqlite_master entry
@@ -512,8 +517,10 @@
 
   while( pTriggerStep ){
     int saveNTab = pParse->nTab;
+    int saveUseDb = pParse->useDb;
     orconf = (orconfin == OE_Default)?pTriggerStep->orconf:orconfin;
     pParse->trigStack->orconf = orconf;
+    pParse->useDb = pTriggerStep->pTrig->iDb;
     switch( pTriggerStep->op ){
       case TK_SELECT: {
 	Select * ss = sqliteSelectDup(pTriggerStep->pSelect);		  
@@ -554,6 +561,7 @@
         assert(0);
     } 
     pParse->nTab = saveNTab;
+    pParse->useDb = saveUseDb;
     pTriggerStep = pTriggerStep->pNext;
   }
 
diff --git a/src/update.c b/src/update.c
index 721dbaa..53dd62e 100644
--- a/src/update.c
+++ b/src/update.c
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle UPDATE statements.
 **
-** $Id: update.c,v 1.59 2003/04/15 19:22:24 drh Exp $
+** $Id: update.c,v 1.60 2003/04/17 22:57:54 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -27,8 +27,6 @@
   int onError            /* How to handle constraint errors */
 ){
   int i, j;              /* Loop counters */
-  char *zTab;            /* Name of the table to be updated */
-  char *zDb;             /* Name of the database holding zTab */
   Table *pTab;           /* The table to be updated */
   int addr;              /* VDBE instruction address of the start of the loop */
   WhereInfo *pWInfo;     /* Information about the WHERE clause */
@@ -58,38 +56,25 @@
   db = pParse->db;
   assert( pTabList->nSrc==1 );
 
-  /* Check for the special case of a VIEW with one or more ON UPDATE triggers 
-   * defined 
-   */
-  zTab = pTabList->a[0].zName;
-  zDb = pTabList->a[0].zDatabase;
-  if( zTab != 0 ){
-    pTab = sqliteFindTable(pParse->db, zTab, zDb);
-    if( pTab ){
-      before_triggers =
-        sqliteTriggersExist(pParse, pTab->pTrigger, 
-            TK_UPDATE, TK_BEFORE, TK_ROW, pChanges);
-      after_triggers = 
-        sqliteTriggersExist(pParse, pTab->pTrigger, 
-            TK_UPDATE, TK_AFTER, TK_ROW, pChanges);
-      row_triggers_exist = before_triggers || after_triggers;
-    }
-
-    if( row_triggers_exist &&  pTab->pSelect ){
-      /* Just fire VIEW triggers */
-      sqliteSrcListDelete(pTabList);
-      sqliteViewTriggers(pParse, pTab, pWhere, onError, pChanges);
-      return;
-    }
-  }
-
   /* Locate the table which we want to update.  This table has to be
   ** put in an SrcList structure because some of the subroutines we
   ** will be calling are designed to work with multiple tables and expect
   ** an SrcList* parameter instead of just a Table* parameter.
   */
   pTab = sqliteSrcListLookup(pParse, pTabList);
-  if( pTab==0 || sqliteIsReadOnly(pParse, pTab) ) goto update_cleanup;
+  if( pTab==0 ) goto update_cleanup;
+  before_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
+            TK_UPDATE, TK_BEFORE, TK_ROW, pChanges);
+  after_triggers = sqliteTriggersExist(pParse, pTab->pTrigger, 
+            TK_UPDATE, TK_AFTER, TK_ROW, pChanges);
+  row_triggers_exist = before_triggers || after_triggers;
+  if( row_triggers_exist &&  pTab->pSelect ){
+    /* Just fire VIEW triggers */
+    sqliteSrcListDelete(pTabList);
+    sqliteViewTriggers(pParse, pTab, pWhere, onError, pChanges);
+    return;
+  }
+  if( sqliteIsReadOnly(pParse, pTab) ) goto update_cleanup;
   assert( pTab->pSelect==0 );  /* This table is not a VIEW */
   aXRef = sqliteMalloc( sizeof(int) * pTab->nCol );
   if( aXRef==0 ) goto update_cleanup;