Changes to the "sqlite" structure that allow simultaneous operations on
multiple database files.  Many regession tests pass - but not all of them.
Do not use this version except for debugging SQLite itself. (CVS 883)

FossilOrigin-Name: d2fb2bb50cf1e13feb90995079f291384abd6ba9
diff --git a/src/build.c b/src/build.c
index ba6da93..4900e92 100644
--- a/src/build.c
+++ b/src/build.c
@@ -25,7 +25,7 @@
 **     ROLLBACK
 **     PRAGMA
 **
-** $Id: build.c,v 1.133 2003/03/20 01:16:58 drh Exp $
+** $Id: build.c,v 1.134 2003/03/27 12:51:24 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -113,9 +113,14 @@
 ** a particular database table given the name
 ** of that table.  Return NULL if not found.
 */
-Table *sqliteFindTable(sqlite *db, const char *zName){
-  Table *p;
-  p = sqliteHashFind(&db->tblHash, zName, strlen(zName)+1);
+Table *sqliteFindTable(sqlite *db, const char *zName, const char *zDatabase){
+  Table *p = 0;
+  int i;
+  for(i=0; i<db->nDb; i++){
+    if( zDatabase!=0 && sqliteStrICmp(zDatabase, db->aDb[i].zName) ) continue;
+    p = sqliteHashFind(&db->aDb[i].tblHash, zName, strlen(zName)+1);
+    if( p ) break;
+  }
   return p;
 }
 
@@ -124,9 +129,14 @@
 ** a particular index given the name of that index.
 ** Return NULL if not found.
 */
-Index *sqliteFindIndex(sqlite *db, const char *zName){
-  Index *p;
-  p = sqliteHashFind(&db->idxHash, zName, strlen(zName)+1);
+Index *sqliteFindIndex(sqlite *db, const char *zName, const char *zDb){
+  Index *p = 0;
+  int i;
+  for(i=0; i<db->nDb; i++){
+    if( zDb && sqliteStrICmp(zDb, db->aDb[i].zName) ) continue;
+    p = sqliteHashFind(&db->aDb[i].idxHash, zName, strlen(zName)+1);
+    if( p ) break;
+  }
   return p;
 }
 
@@ -140,10 +150,13 @@
 */
 static void sqliteDeleteIndex(sqlite *db, Index *p){
   Index *pOld;
+
   assert( db!=0 && p->zName!=0 );
-  pOld = sqliteHashInsert(&db->idxHash, p->zName, strlen(p->zName)+1, 0);
+  pOld = sqliteHashInsert(&db->aDb[p->iDb].idxHash, p->zName,
+                          strlen(p->zName)+1, 0);
   if( pOld!=0 && pOld!=p ){
-    sqliteHashInsert(&db->idxHash, pOld->zName, strlen(pOld->zName)+1, pOld);
+    sqliteHashInsert(&db->aDb[p->iDb].idxHash, pOld->zName,
+                     strlen(pOld->zName)+1, pOld);
   }
   sqliteFree(p);
 }
@@ -176,23 +189,27 @@
   HashElem *pElem;
   Hash temp1;
   Hash temp2;
+  int i;
 
-  sqliteHashClear(&db->aFKey);
-  temp1 = db->tblHash;
-  temp2 = db->trigHash;
-  sqliteHashInit(&db->trigHash, SQLITE_HASH_STRING, 0);
-  sqliteHashClear(&db->idxHash);
-  for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
-    Trigger *pTrigger = sqliteHashData(pElem);
-    sqliteDeleteTrigger(pTrigger);
+  for(i=0; i<db->nDb; i++){
+    Db *pDb = &db->aDb[i];
+    temp1 = pDb->tblHash;
+    temp2 = pDb->trigHash;
+    sqliteHashInit(&pDb->trigHash, SQLITE_HASH_STRING, 0);
+    sqliteHashClear(&pDb->aFKey);
+    sqliteHashClear(&pDb->idxHash);
+    for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
+      Trigger *pTrigger = sqliteHashData(pElem);
+      sqliteDeleteTrigger(pTrigger);
+    }
+    sqliteHashClear(&temp2);
+    sqliteHashInit(&pDb->tblHash, SQLITE_HASH_STRING, 0);
+    for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
+      Table *pTab = sqliteHashData(pElem);
+      sqliteDeleteTable(db, pTab);
+    }
+    sqliteHashClear(&temp1);
   }
-  sqliteHashClear(&temp2);
-  sqliteHashInit(&db->tblHash, SQLITE_HASH_STRING, 0);
-  for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
-    Table *pTab = sqliteHashData(pElem);
-    sqliteDeleteTable(db, pTab);
-  }
-  sqliteHashClear(&temp1);
   db->flags &= ~(SQLITE_Initialized|SQLITE_InternChanges);
 }
 
@@ -241,6 +258,7 @@
   */
   for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){
     pNext = pIndex->pNext;
+    assert( pIndex->iDb==pTable->iDb || (pTable->iDb==0 && pIndex->iDb==1) );
     sqliteDeleteIndex(db, pIndex);
   }
 
@@ -249,7 +267,9 @@
   */
   for(pFKey=pTable->pFKey; pFKey; pFKey=pNextFKey){
     pNextFKey = pFKey->pNextFrom;
-    assert( sqliteHashFind(&db->aFKey,pFKey->zTo,strlen(pFKey->zTo)+1)!=pFKey );
+    assert( pTable->iDb<db->nDb );
+    assert( sqliteHashFind(&db->aDb[pTable->iDb].aFKey,
+                           pFKey->zTo, strlen(pFKey->zTo)+1)!=pFKey );
     sqliteFree(pFKey);
   }
 
@@ -273,14 +293,15 @@
 static void sqliteUnlinkAndDeleteTable(sqlite *db, Table *p){
   Table *pOld;
   FKey *pF1, *pF2;
+  int i = p->iDb;
   assert( db!=0 );
-  pOld = sqliteHashInsert(&db->tblHash, p->zName, strlen(p->zName)+1, 0);
+  pOld = sqliteHashInsert(&db->aDb[i].tblHash, p->zName, strlen(p->zName)+1, 0);
   assert( pOld==0 || pOld==p );
   for(pF1=p->pFKey; pF1; pF1=pF1->pNextFrom){
     int nTo = strlen(pF1->zTo) + 1;
-    pF2 = sqliteHashFind(&db->aFKey, pF1->zTo, nTo);
+    pF2 = sqliteHashFind(&db->aDb[i].aFKey, pF1->zTo, nTo);
     if( pF2==pF1 ){
-      sqliteHashInsert(&db->aFKey, pF1->zTo, nTo, pF1->pNextTo);
+      sqliteHashInsert(&db->aDb[i].aFKey, pF1->zTo, nTo, pF1->pNextTo);
     }else{
       while( pF2 && pF2->pNextTo!=pF1 ){ pF2=pF2->pNextTo; }
       if( pF2 ){
@@ -347,7 +368,9 @@
   pParse->sFirstToken = *pStart;
   zName = sqliteTableNameFromToken(pName);
   if( zName==0 ) return;
+  if( pParse->iDb==1 ) isTemp = 1;
 #ifndef SQLITE_OMIT_AUTHORIZATION
+  assert( (isTemp & 1)==isTemp );
   if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0) ){
     sqliteFree(zName);
     return;
@@ -378,7 +401,7 @@
   /* Before trying to create a temporary table, make sure the Btree for
   ** holding temporary tables is open.
   */
-  if( isTemp && db->aDb[1].pBt==0 ){
+  if( isTemp && db->aDb[1].pBt==0 && !pParse->explain ){
     int rc = sqliteBtreeOpen(0, 0, MAX_PAGES, &db->aDb[1].pBt);
     if( rc!=SQLITE_OK ){
       sqliteSetString(&pParse->zErrMsg, "unable to open a temporary database "
@@ -402,26 +425,18 @@
   **
   ** If we are re-reading the sqlite_master table because of a schema
   ** change and a new permanent table is found whose name collides with
-  ** an existing temporary table, then ignore the new permanent table.
-  ** We will continue parsing, but the pParse->nameClash flag will be set
-  ** so we will know to discard the table record once parsing has finished.
+  ** an existing temporary table, that is not an error.
   */
-  pTable = sqliteFindTable(db, zName);
-  if( pTable!=0 ){
-    if( pTable->isTemp && pParse->initFlag ){
-      pParse->nameClash = 1;
-    }else{
-      sqliteSetNString(&pParse->zErrMsg, "table ", 0, pName->z, pName->n,
-          " already exists", 0, 0);
-      sqliteFree(zName);
-      pParse->nErr++;
-      return;
-    }
-  }else{
-    pParse->nameClash = 0;
+  pTable = sqliteFindTable(db, zName, 0);
+  if( pTable!=0 && (pTable->iDb==isTemp || !pParse->initFlag) ){
+    sqliteSetNString(&pParse->zErrMsg, "table ", 0, pName->z, pName->n,
+        " already exists", 0, 0);
+    sqliteFree(zName);
+    pParse->nErr++;
+    return;
   }
-  if( (pIdx = sqliteFindIndex(db, zName))!=0 &&
-          (!pIdx->pTable->isTemp || !pParse->initFlag) ){
+  if( (pIdx = sqliteFindIndex(db, zName, 0))!=0 &&
+          (pIdx->iDb==0 || !pParse->initFlag) ){
     sqliteSetString(&pParse->zErrMsg, "there is already an index named ", 
        zName, 0);
     sqliteFree(zName);
@@ -438,7 +453,7 @@
   pTable->aCol = 0;
   pTable->iPKey = -1;
   pTable->pIndex = 0;
-  pTable->isTemp = isTemp;
+  pTable->iDb = isTemp ? 1 : pParse->iDb;
   if( pParse->pNewTable ) sqliteDeleteTable(db, pParse->pNewTable);
   pParse->pNewTable = pTable;
 
@@ -624,7 +639,7 @@
     pTab->iPKey = iCol;
     pTab->keyConf = onError;
   }else{
-    sqliteCreateIndex(pParse, 0, 0, pList, onError, 0, 0);
+    sqliteCreateIndex(pParse, 0, 0, pList, onError, 0, 0, 0);
     pList = 0;
   }
 
@@ -778,7 +793,7 @@
   n += 35 + 6*p->nCol;
   zStmt = sqliteMallocRaw( n );
   if( zStmt==0 ) return 0;
-  strcpy(zStmt, p->isTemp ? "CREATE TEMP TABLE " : "CREATE TABLE ");
+  strcpy(zStmt, p->iDb==1 ? "CREATE TEMP TABLE " : "CREATE TABLE ");
   k = strlen(zStmt);
   identPut(zStmt, &k, p->zName);
   zStmt[k++] = '(';
@@ -859,7 +874,7 @@
     if( v==0 ) return;
     if( p->pSelect==0 ){
       /* A regular table */
-      sqliteVdbeAddOp(v, OP_CreateTable, 0, p->isTemp);
+      sqliteVdbeAddOp(v, OP_CreateTable, 0, p->iDb);
       sqliteVdbeChangeP3(v, -1, (char *)&p->tnum, P3_POINTER);
     }else{
       /* A view */
@@ -891,12 +906,12 @@
     }
     sqliteVdbeAddOp(v, OP_MakeRecord, 5, 0);
     sqliteVdbeAddOp(v, OP_PutIntKey, 0, 0);
-    if( !p->isTemp ){
+    if( !p->iDb ){
       sqliteChangeCookie(db, v);
     }
     sqliteVdbeAddOp(v, OP_Close, 0, 0);
     if( pSelect ){
-      sqliteVdbeAddOp(v, OP_Integer, p->isTemp, 0);
+      sqliteVdbeAddOp(v, OP_Integer, p->iDb, 0);
       sqliteVdbeAddOp(v, OP_OpenWrite, 1, 0);
       pParse->nTab = 2;
       sqliteSelect(pParse, pSelect, SRT_Table, 1, 0, 0, 0);
@@ -906,19 +921,19 @@
 
   /* Add the table to the in-memory representation of the database.
   */
-  assert( pParse->nameClash==0 || pParse->initFlag==1 );
-  if( pParse->explain==0 && pParse->nameClash==0 && pParse->nErr==0 ){
+  if( pParse->explain==0 && pParse->nErr==0 ){
     Table *pOld;
     FKey *pFKey;
-    pOld = sqliteHashInsert(&db->tblHash, p->zName, strlen(p->zName)+1, p);
+    pOld = sqliteHashInsert(&db->aDb[p->iDb].tblHash, 
+                            p->zName, strlen(p->zName)+1, p);
     if( pOld ){
       assert( p==pOld );  /* Malloc must have failed inside HashInsert() */
       return;
     }
     for(pFKey=p->pFKey; pFKey; pFKey=pFKey->pNextFrom){
       int nTo = strlen(pFKey->zTo) + 1;
-      pFKey->pNextTo = sqliteHashFind(&db->aFKey, pFKey->zTo, nTo);
-      sqliteHashInsert(&db->aFKey, pFKey->zTo, nTo, pFKey);
+      pFKey->pNextTo = sqliteHashFind(&db->aDb[p->iDb].aFKey, pFKey->zTo, nTo);
+      sqliteHashInsert(&db->aDb[p->iDb].aFKey, pFKey->zTo, nTo, pFKey);
     }
     pParse->pNewTable = 0;
     db->nTable++;
@@ -1038,7 +1053,7 @@
     pSelTab->nCol = 0;
     pSelTab->aCol = 0;
     sqliteDeleteTable(0, pSelTab);
-    pParse->db->flags |= SQLITE_UnresetViews;
+    pParse->db->aDb[pTable->iDb].flags |= SQLITE_UnresetViews;
   }else{
     pTable->nCol = 0;
     nErr++;
@@ -1074,16 +1089,16 @@
 /*
 ** Clear the column names from every VIEW.
 */
-void sqliteViewResetAll(sqlite *db){
+static void sqliteViewResetAll(sqlite *db, int idx){
   HashElem *i;
-  if( (db->flags & SQLITE_UnresetViews)==0 ) return;
-  for(i=sqliteHashFirst(&db->tblHash); i; i=sqliteHashNext(i)){
+  if( (db->aDb[idx].flags & SQLITE_UnresetViews)==0 ) return;
+  for(i=sqliteHashFirst(&db->aDb[idx].tblHash); i; i=sqliteHashNext(i)){
     Table *pTab = sqliteHashData(i);
     if( pTab->pSelect ){
       sqliteViewResetColumnNames(pTab);
     }
   }
-  db->flags &= ~SQLITE_UnresetViews;
+  db->aDb[idx].flags &= ~SQLITE_UnresetViews;
 }
 
 /*
@@ -1095,7 +1110,7 @@
   Table *pTab;
   zName = sqliteTableNameFromToken(pTok);
   if( zName==0 ) return 0;
-  pTab = sqliteFindTable(pParse->db, zName);
+  pTab = sqliteFindTable(pParse->db, zName, 0);
   sqliteFree(zName);
   if( pTab==0 ){
     sqliteSetNString(&pParse->zErrMsg, "no such table: ", 0, 
@@ -1114,24 +1129,26 @@
   Vdbe *v;
   int base;
   sqlite *db = pParse->db;
+  int iDb;
 
   if( pParse->nErr || sqlite_malloc_failed ) return;
   pTable = sqliteTableFromToken(pParse, pName);
   if( pTable==0 ) return;
+  iDb = pTable->iDb;
 #ifndef SQLITE_OMIT_AUTHORIZATION
-  if( sqliteAuthCheck(pParse, SQLITE_DELETE, SCHEMA_TABLE(pTable->isTemp),0)){
+  if( sqliteAuthCheck(pParse, SQLITE_DELETE, SCHEMA_TABLE(pTable->iDb),0)){
     return;
   }
   {
     int code;
     if( isView ){
-      if( pTable->isTemp ){
+      if( iDb==1 ){
         code = SQLITE_DROP_TEMP_VIEW;
       }else{
         code = SQLITE_DROP_VIEW;
       }
     }else{
-      if( pTable->isTemp ){
+      if( iDb==1 ){
         code = SQLITE_DROP_TEMP_TABLE;
       }else{
         code = SQLITE_DROP_TABLE;
@@ -1181,15 +1198,17 @@
     };
     Index *pIdx;
     Trigger *pTrigger;
-    sqliteBeginWriteOperation(pParse, 0, pTable->isTemp);
-    sqliteOpenMasterTable(v, pTable->isTemp);
+    sqliteBeginWriteOperation(pParse, 0, pTable->iDb);
+    sqliteOpenMasterTable(v, pTable->iDb);
     /* Drop all triggers associated with the table being dropped */
     pTrigger = pTable->pTrigger;
     while( pTrigger ){
-      Token tt;
-      tt.z = pTable->pTrigger->name;
-      tt.n = strlen(pTable->pTrigger->name);
-      sqliteDropTrigger(pParse, &tt, 1);
+      SrcList *pNm;
+      assert( pTrigger->iDb==pTable->iDb );
+      pNm = sqliteSrcListAppend(0, 0, 0);
+      pNm->a[0].zName = sqliteStrDup(pTrigger->name);
+      pNm->a[0].zDatabase = sqliteStrDup(db->aDb[pTable->iDb].zName);
+      sqliteDropTrigger(pParse, pNm, 1);
       if( pParse->explain ){
         pTrigger = pTrigger->pNext;
       }else{
@@ -1198,14 +1217,14 @@
     }
     base = sqliteVdbeAddOpList(v, ArraySize(dropTable), dropTable);
     sqliteVdbeChangeP3(v, base+1, pTable->zName, 0);
-    if( !pTable->isTemp ){
+    if( !pTable->iDb ){
       sqliteChangeCookie(db, v);
     }
     sqliteVdbeAddOp(v, OP_Close, 0, 0);
     if( !isView ){
-      sqliteVdbeAddOp(v, OP_Destroy, pTable->tnum, pTable->isTemp);
+      sqliteVdbeAddOp(v, OP_Destroy, pTable->tnum, pTable->iDb);
       for(pIdx=pTable->pIndex; pIdx; pIdx=pIdx->pNext){
-        sqliteVdbeAddOp(v, OP_Destroy, pIdx->tnum, pTable->isTemp);
+        sqliteVdbeAddOp(v, OP_Destroy, pIdx->tnum, pTable->iDb);
       }
     }
     sqliteEndWriteOperation(pParse);
@@ -1220,7 +1239,7 @@
     sqliteUnlinkAndDeleteTable(db, pTable);
     db->flags |= SQLITE_InternChanges;
   }
-  sqliteViewResetAll(db);
+  sqliteViewResetAll(db, iDb);
 }
 
 /*
@@ -1403,9 +1422,10 @@
 void sqliteCreateIndex(
   Parse *pParse,   /* All information about this parse */
   Token *pName,    /* Name of the index.  May be NULL */
-  Token *pTable,   /* Name of the table to index.  Use pParse->pNewTable if 0 */
+  SrcList *pTable, /* Name of the table to index.  Use pParse->pNewTable if 0 */
   IdList *pList,   /* A list of columns to be indexed */
   int onError,     /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
+  int isTemp,      /* True if this is a temporary index */
   Token *pStart,   /* The CREATE token that begins a CREATE TABLE statement */
   Token *pEnd      /* The ")" that closes the CREATE INDEX statement */
 ){
@@ -1415,7 +1435,6 @@
   int i, j;
   Token nullId;             /* Fake token for an empty ID list */
   sqlite *db = pParse->db;
-  int hideName = 0;         /* Do not put table name in the hash table */
 
   if( pParse->nErr || sqlite_malloc_failed ) goto exit_create_index;
 
@@ -1424,15 +1443,17 @@
   */
   if( pTable!=0 ){
     assert( pName!=0 );
-    pTab =  sqliteTableFromToken(pParse, pTable);
+    assert( pTable->nSrc==1 );
+    pTab =  sqliteTableNameToTable(pParse, 
+                 pTable->a[0].zName, pTable->a[0].zDatabase);
   }else{
     assert( pName==0 );
     pTab =  pParse->pNewTable;
   }
   if( pTab==0 || pParse->nErr ) goto exit_create_index;
-  if( pTab->readOnly ){
+  if( !isTemp && (pTab->readOnly || pTab->iDb>=2) ){
     sqliteSetString(&pParse->zErrMsg, "table ", pTab->zName, 
-      " may not have new indices added", 0);
+      " may not have non-temporary indices added", 0);
     pParse->nErr++;
     goto exit_create_index;
   }
@@ -1441,7 +1462,12 @@
     pParse->nErr++;
     goto exit_create_index;
   }
+  if( pTab->iDb==1 ){
+    isTemp = 1;
+  }
 
+
+#if 0
   /* If this index is created while re-reading the schema from sqlite_master
   ** but the table associated with this index is a temporary table, it can
   ** only mean that the table that this index is really associated with is
@@ -1449,9 +1475,10 @@
   ** Since its table has been suppressed, we need to also suppress the
   ** index.
   */
-  if( pParse->initFlag && !pParse->isTemp && pTab->isTemp ){
+  if( pParse->initFlag && !pParse->isTemp && pTab->iDb ){
     goto exit_create_index;
   }
+#endif
 
   /*
   ** Find the name of the index.  Make sure there is not already another
@@ -1460,40 +1487,30 @@
   ** Exception:  If we are reading the names of permanent indices from the
   ** sqlite_master table (because some other process changed the schema) and
   ** one of the index names collides with the name of a temporary table or
-  ** index, then we will continue to process this index, but we will not
-  ** store its name in the hash table.  Set the hideName flag to accomplish
-  ** this.
+  ** index, then we will continue to process this index.
   **
   ** If pName==0 it means that we are
   ** dealing with a primary key or UNIQUE constraint.  We have to invent our
   ** own name.
   */
-  if( pName ){
+  if( pName && !pParse->initFlag ){
     Index *pISameName;    /* Another index with the same name */
     Table *pTSameName;    /* A table with same name as the index */
-    zName = sqliteTableNameFromToken(pName);
+    zName = sqliteStrNDup(pName->z, pName->n);
     if( zName==0 ) goto exit_create_index;
-    if( (pISameName = sqliteFindIndex(db, zName))!=0 ){
-      if( pISameName->pTable->isTemp && pParse->initFlag ){
-        hideName = 1;
-      }else{
-        sqliteSetString(&pParse->zErrMsg, "index ", zName, 
-           " already exists", 0);
-        pParse->nErr++;
-        goto exit_create_index;
-      }
+    if( (pISameName = sqliteFindIndex(db, zName, 0))!=0 ){
+      sqliteSetString(&pParse->zErrMsg, "index ", zName, 
+         " already exists", 0);
+      pParse->nErr++;
+      goto exit_create_index;
     }
-    if( (pTSameName = sqliteFindTable(db, zName))!=0 ){
-      if( pTSameName->isTemp && pParse->initFlag ){
-        hideName = 1;
-      }else{
-        sqliteSetString(&pParse->zErrMsg, "there is already a table named ",
-           zName, 0);
-        pParse->nErr++;
-        goto exit_create_index;
-      }
+    if( (pTSameName = sqliteFindTable(db, zName, 0))!=0 ){
+      sqliteSetString(&pParse->zErrMsg, "there is already a table named ",
+         zName, 0);
+      pParse->nErr++;
+      goto exit_create_index;
     }
-  }else{
+  }else if( pName==0 ){
     char zBuf[30];
     int n;
     Index *pLoop;
@@ -1502,17 +1519,20 @@
     zName = 0;
     sqliteSetString(&zName, "(", pTab->zName, " autoindex ", zBuf, 0);
     if( zName==0 ) goto exit_create_index;
-    hideName = sqliteFindIndex(db, zName)!=0;
+  }else{
+    zName = sqliteStrNDup(pName->z, pName->n);
   }
 
   /* Check for authorization to create an index.
   */
 #ifndef SQLITE_OMIT_AUTHORIZATION
-  if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(pTab->isTemp), 0) ){
+  assert( isTemp==0 || isTemp==1 );
+  assert( pTab->iDb==pParse->iDb || isTemp==1 );
+  if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0) ){
     goto exit_create_index;
   }
   i = SQLITE_CREATE_INDEX;
-  if( pTab->isTemp ) i = SQLITE_CREATE_TEMP_INDEX;
+  if( isTemp ) i = SQLITE_CREATE_TEMP_INDEX;
   if( sqliteAuthCheck(pParse, i, zName, pTab->zName) ){
     goto exit_create_index;
   }
@@ -1542,6 +1562,7 @@
   pIndex->nColumn = pList->nId;
   pIndex->onError = pIndex->isUnique = onError;
   pIndex->autoIndex = pName==0;
+  pIndex->iDb = isTemp ? 1 : pParse->iDb;
 
   /* Scan the names of the columns of the table to be indexed and
   ** load the column indices into the Index structure.  Report an error
@@ -1564,9 +1585,10 @@
   /* Link the new Index structure to its table and to the other
   ** in-memory database structures. 
   */
-  if( !pParse->explain && !hideName ){
+  if( !pParse->explain ){
     Index *p;
-    p = sqliteHashInsert(&db->idxHash, pIndex->zName, strlen(zName)+1, pIndex);
+    p = sqliteHashInsert(&db->aDb[isTemp].idxHash, 
+                         pIndex->zName, strlen(zName)+1, pIndex);
     if( p ){
       assert( p==pIndex );  /* Malloc must have failed */
       sqliteFree(pIndex);
@@ -1622,7 +1644,6 @@
     int lbl1, lbl2;
     int i;
     int addr;
-    int isTemp = pTab->isTemp;
 
     v = sqliteGetVdbe(pParse);
     if( v==0 ) goto exit_create_index;
@@ -1653,7 +1674,7 @@
     sqliteVdbeAddOp(v, OP_MakeRecord, 5, 0);
     sqliteVdbeAddOp(v, OP_PutIntKey, 0, 0);
     if( pTable ){
-      sqliteVdbeAddOp(v, OP_Integer, isTemp, 0);
+      sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
       sqliteVdbeAddOp(v, OP_OpenRead, 2, pTab->tnum);
       sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
       lbl2 = sqliteVdbeMakeLabel(v);
@@ -1683,6 +1704,7 @@
   /* Clean up before exiting */
 exit_create_index:
   sqliteIdListDelete(pList);
+  sqliteSrcListDelete(pTable);
   sqliteFree(zName);
   return;
 }
@@ -1691,39 +1713,41 @@
 ** This routine will drop an existing named index.  This routine
 ** implements the DROP INDEX statement.
 */
-void sqliteDropIndex(Parse *pParse, Token *pName){
+void sqliteDropIndex(Parse *pParse, SrcList *pName){
   Index *pIndex;
-  char *zName;
   Vdbe *v;
   sqlite *db = pParse->db;
 
   if( pParse->nErr || sqlite_malloc_failed ) return;
-  zName = sqliteTableNameFromToken(pName);
-  if( zName==0 ) return;
-  pIndex = sqliteFindIndex(db, zName);
-  sqliteFree(zName);
+  assert( pName->nSrc==1 );
+  pIndex = sqliteFindIndex(db, pName->a[0].zName, pName->a[0].zDatabase);
   if( pIndex==0 ){
-    sqliteSetNString(&pParse->zErrMsg, "no such index: ", 0, 
-        pName->z, pName->n, 0);
+    sqliteSetString(&pParse->zErrMsg, "no such index: ", pName->a[0].zName, 0);
     pParse->nErr++;
-    return;
+    goto exit_drop_index;
   }
   if( pIndex->autoIndex ){
     sqliteSetString(&pParse->zErrMsg, "index associated with UNIQUE "
       "or PRIMARY KEY constraint cannot be dropped", 0);
     pParse->nErr++;
-    return;
+    goto exit_drop_index;
+  }
+  if( pIndex->iDb>1 ){
+    sqliteSetString(&pParse->zErrMsg, "cannot alter schema of attached "
+       "databases", 0);
+    pParse->nErr++;
+    goto exit_drop_index;
   }
 #ifndef SQLITE_OMIT_AUTHORIZATION
   {
     int code = SQLITE_DROP_INDEX;
     Table *pTab = pIndex->pTable;
-    if( sqliteAuthCheck(pParse, SQLITE_DELETE, SCHEMA_TABLE(pTab->isTemp), 0) ){
-      return;
+    if( sqliteAuthCheck(pParse, SQLITE_DELETE, SCHEMA_TABLE(pIndex->iDb), 0) ){
+      goto exit_drop_index;
     }
-    if( pTab->isTemp ) code = SQLITE_DROP_TEMP_INDEX;
+    if( pIndex->iDb ) code = SQLITE_DROP_TEMP_INDEX;
     if( sqliteAuthCheck(pParse, code, pIndex->zName, pTab->zName) ){
-      return;
+      goto exit_drop_index;
     }
   }
 #endif
@@ -1743,17 +1767,16 @@
       { OP_Delete,     0, 0,       0}, /* 8 */
     };
     int base;
-    Table *pTab = pIndex->pTable;
 
-    sqliteBeginWriteOperation(pParse, 0, pTab->isTemp);
-    sqliteOpenMasterTable(v, pTab->isTemp);
+    sqliteBeginWriteOperation(pParse, 0, pIndex->iDb);
+    sqliteOpenMasterTable(v, pIndex->iDb);
     base = sqliteVdbeAddOpList(v, ArraySize(dropIndex), dropIndex);
     sqliteVdbeChangeP3(v, base+1, pIndex->zName, 0);
-    if( !pTab->isTemp ){
+    if( pIndex->iDb==0 ){
       sqliteChangeCookie(db, v);
     }
     sqliteVdbeAddOp(v, OP_Close, 0, 0);
-    sqliteVdbeAddOp(v, OP_Destroy, pIndex->tnum, pTab->isTemp);
+    sqliteVdbeAddOp(v, OP_Destroy, pIndex->tnum, pIndex->iDb);
     sqliteEndWriteOperation(pParse);
   }
 
@@ -1763,6 +1786,9 @@
     sqliteUnlinkAndDeleteIndex(db, pIndex);
     db->flags |= SQLITE_InternChanges;
   }
+
+exit_drop_index:
+  sqliteSrcListDelete(pName);
 }
 
 /*
@@ -1943,13 +1969,12 @@
 */
 void sqliteCopy(
   Parse *pParse,       /* The parser context */
-  Token *pTableName,   /* The name of the table into which we will insert */
+  SrcList *pTableName, /* The name of the table into which we will insert */
   Token *pFilename,    /* The file from which to obtain information */
   Token *pDelimiter,   /* Use this as the field delimiter */
   int onError          /* What to do if a constraint fails */
 ){
   Table *pTab;
-  char *zTab;
   int i;
   Vdbe *v;
   int addr, end;
@@ -1958,10 +1983,10 @@
   sqlite *db = pParse->db;
 
 
-  zTab = sqliteTableNameFromToken(pTableName);
-  if( sqlite_malloc_failed || zTab==0 ) goto copy_cleanup;
-  pTab = sqliteTableNameToTable(pParse, zTab);
-  sqliteFree(zTab);
+  if( sqlite_malloc_failed  ) goto copy_cleanup;
+  assert( pTableName->nSrc==1 );
+  pTab = sqliteTableNameToTable(pParse, pTableName->a[0].zName,
+                                pTableName->a[0].zDatabase);
   if( pTab==0 ) goto copy_cleanup;
   zFile = sqliteStrNDup(pFilename->z, pFilename->n);
   sqliteDequote(zFile);
@@ -1971,15 +1996,16 @@
   }
   v = sqliteGetVdbe(pParse);
   if( v ){
-    sqliteBeginWriteOperation(pParse, 1, pTab->isTemp);
+    sqliteBeginWriteOperation(pParse, 1, pTab->iDb==1);
     addr = sqliteVdbeAddOp(v, OP_FileOpen, 0, 0);
     sqliteVdbeChangeP3(v, addr, pFilename->z, pFilename->n);
     sqliteVdbeDequoteP3(v, addr);
-    sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+    sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
     sqliteVdbeAddOp(v, OP_OpenWrite, 0, pTab->tnum);
     sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
     for(i=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
-      sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+      assert( pIdx->iDb==1 || pIdx->iDb==pTab->iDb );
+      sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
       sqliteVdbeAddOp(v, OP_OpenWrite, i, pIdx->tnum);
       sqliteVdbeChangeP3(v, -1, pIdx->zName, P3_STATIC);
     }
@@ -2026,6 +2052,7 @@
   }
   
 copy_cleanup:
+  sqliteSrcListDelete(pTableName);
   sqliteFree(zFile);
   return;
 }
@@ -2487,7 +2514,7 @@
 
   if( sqliteStrICmp(zLeft, "table_info")==0 ){
     Table *pTab;
-    pTab = sqliteFindTable(db, zRight);
+    pTab = sqliteFindTable(db, zRight, 0);
     if( pTab ){
       static VdbeOp tableInfoPreface[] = {
         { OP_ColumnName,  0, 0,       "cid"},
@@ -2517,7 +2544,7 @@
   if( sqliteStrICmp(zLeft, "index_info")==0 ){
     Index *pIdx;
     Table *pTab;
-    pIdx = sqliteFindIndex(db, zRight);
+    pIdx = sqliteFindIndex(db, zRight, 0);
     if( pIdx ){
       static VdbeOp tableInfoPreface[] = {
         { OP_ColumnName,  0, 0,       "seqno"},
@@ -2542,7 +2569,7 @@
   if( sqliteStrICmp(zLeft, "index_list")==0 ){
     Index *pIdx;
     Table *pTab;
-    pTab = sqliteFindTable(db, zRight);
+    pTab = sqliteFindTable(db, zRight, 0);
     if( pTab ){
       v = sqliteGetVdbe(pParse);
       pIdx = pTab->pIndex;
diff --git a/src/delete.c b/src/delete.c
index c55c6f3..770a400 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.47 2003/03/20 01:16:59 drh Exp $
+** $Id: delete.c,v 1.48 2003/03/27 12:51:24 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -22,11 +22,15 @@
 ** table is writeable.  Generate an error and return NULL if not.  If
 ** everything checks out, return a pointer to the Table structure.
 */
-Table *sqliteTableNameToTable(Parse *pParse, const char *zTab){
+Table *sqliteTableNameToTable(Parse *pParse, const char *zTab, const char *zDb){
   Table *pTab;
-  pTab = sqliteFindTable(pParse->db, zTab);
+  pTab = sqliteFindTable(pParse->db, zTab, zDb);
   if( pTab==0 ){
-    sqliteSetString(&pParse->zErrMsg, "no such table: ", zTab, 0);
+    if( zDb==0 || zDb[0]==0 ){
+      sqliteSetString(&pParse->zErrMsg, "no such table: ", zTab, 0);
+    }else{
+      sqliteSetString(&pParse->zErrMsg, "no such table: ", zDb, ".", zTab, 0);
+    }
     pParse->nErr++;
     return 0;
   }
@@ -52,6 +56,7 @@
   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 */
@@ -73,8 +78,9 @@
   ** defined 
   */
   zTab = pTabList->a[0].zName;
+  zDb = pTabList->a[0].zDatabase;
   if( zTab != 0 ){
-    pTab = sqliteFindTable(pParse->db, zTab);
+    pTab = sqliteFindTable(pParse->db, zTab, zDb);
     if( pTab ){
       row_triggers_exist = 
         sqliteTriggersExist(pParse, pTab->pTrigger, 
@@ -95,7 +101,7 @@
   ** will be calling are designed to work with multiple tables and expect
   ** an SrcList* parameter instead of just a Table* parameter.
   */
-  pTab = pTabList->a[0].pTab = sqliteTableNameToTable(pParse, zTab);
+  pTab = pTabList->a[0].pTab = sqliteTableNameToTable(pParse, zTab, zDb);
   if( pTab==0 ){
     goto delete_from_cleanup;
   }
@@ -129,7 +135,7 @@
     goto delete_from_cleanup;
   }
   sqliteBeginWriteOperation(pParse, row_triggers_exist,
-       !row_triggers_exist && pTab->isTemp);
+       !row_triggers_exist && pTab->iDb==1);
 
   /* Initialize the counter of the number of rows deleted, if
   ** we are counting rows.
@@ -148,7 +154,7 @@
       ** entries in the table. */
       int endOfLoop = sqliteVdbeMakeLabel(v);
       int addr;
-      sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+      sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
       sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum);
       sqliteVdbeAddOp(v, OP_Rewind, base, sqliteVdbeCurrentAddr(v)+2);
       addr = sqliteVdbeAddOp(v, OP_AddImm, 1, 0);
@@ -156,9 +162,9 @@
       sqliteVdbeResolveLabel(v, endOfLoop);
       sqliteVdbeAddOp(v, OP_Close, base, 0);
     }
-    sqliteVdbeAddOp(v, OP_Clear, pTab->tnum, pTab->isTemp);
+    sqliteVdbeAddOp(v, OP_Clear, pTab->tnum, pTab->iDb);
     for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
-      sqliteVdbeAddOp(v, OP_Clear, pIdx->tnum, pTab->isTemp);
+      sqliteVdbeAddOp(v, OP_Clear, pIdx->tnum, pIdx->iDb);
     }
   }
 
@@ -195,7 +201,7 @@
     if( row_triggers_exist ){
       addr = sqliteVdbeAddOp(v, OP_ListRead, 0, end);
       sqliteVdbeAddOp(v, OP_Dup, 0, 0);
-      sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+      sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
       sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum);
       sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
       sqliteVdbeAddOp(v, OP_OpenTemp, oldIdx, 0);
@@ -225,10 +231,10 @@
     ** cursors are opened only once on the outside the loop.
     */
     pParse->nTab = base + 1;
-    sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+    sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
     sqliteVdbeAddOp(v, OP_OpenWrite, base, pTab->tnum);
     for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
-      sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+      sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
       sqliteVdbeAddOp(v, OP_OpenWrite, pParse->nTab++, pIdx->tnum);
     }
 
diff --git a/src/expr.c b/src/expr.c
index 47a7dd1..576ae2d 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -12,7 +12,7 @@
 ** This file contains routines used for analyzing expressions and
 ** for generating VDBE code that evaluates expressions in SQLite.
 **
-** $Id: expr.c,v 1.89 2003/03/20 01:16:59 drh Exp $
+** $Id: expr.c,v 1.90 2003/03/27 12:51:25 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -493,16 +493,29 @@
       break; 
     }
   
-    /* A table name and column name:  ID.ID */
+    /* A table name and column name:     ID.ID
+    ** Or a database, table and column:  ID.ID.ID
+    */
     case TK_DOT: {
       int cnt = 0;             /* Number of matches */
       int cntTab = 0;          /* Number of matching tables */
       int i;                   /* Loop counter */
       Expr *pLeft, *pRight;    /* Left and right subbranches of the expr */
       char *zLeft, *zRight;    /* Text of an identifier */
+      char *zDb;               /* Name of database holding table */
+      sqlite *db = pParse->db;
 
-      pLeft = pExpr->pLeft;
       pRight = pExpr->pRight;
+      if( pRight->op==TK_ID ){
+        pLeft = pExpr->pLeft;
+        zDb = 0;
+      }else{
+        Expr *pDb = pExpr->pLeft;
+        assert( pDb && pDb->op==TK_ID && pDb->token.z );
+        zDb = sqliteStrNDup(pDb->token.z, pDb->token.n);
+        pLeft = pRight->pLeft;
+        pRight = pRight->pRight;
+      }
       assert( pLeft && pLeft->op==TK_ID && pLeft->token.z );
       assert( pRight && pRight->op==TK_ID && pRight->token.z );
       zLeft = sqliteStrNDup(pLeft->token.z, pLeft->token.n);
@@ -510,8 +523,10 @@
       if( zLeft==0 || zRight==0 ){
         sqliteFree(zLeft);
         sqliteFree(zRight);
+        sqliteFree(zDb);
         return 1;
       }
+      sqliteDequote(zDb);
       sqliteDequote(zLeft);
       sqliteDequote(zRight);
       pExpr->iTable = -1;
@@ -523,10 +538,14 @@
         assert( pTab->nCol>0 );
         if( pTabList->a[i].zAlias ){
           zTab = pTabList->a[i].zAlias;
+          if( sqliteStrICmp(zTab, zLeft)!=0 ) continue;
         }else{
           zTab = pTab->zName;
+          if( zTab==0 || sqliteStrICmp(zTab, zLeft)!=0 ) continue;
+          if( zDb!=0 && sqliteStrICmp(db->aDb[pTab->iDb].zName, zDb)!=0 ){
+            continue;
+          }
         }
-        if( zTab==0 || sqliteStrICmp(zTab, zLeft)!=0 ) continue;
         if( 0==(cntTab++) ) pExpr->iTable = i + base;
         for(j=0; j<pTab->nCol; j++){
           if( sqliteStrICmp(pTab->aCol[j].zName, zRight)==0 ){
@@ -577,6 +596,7 @@
         pExpr->iColumn = -1;
         pExpr->dataType = SQLITE_SO_NUM;
       }
+      sqliteFree(zDb);
       sqliteFree(zLeft);
       sqliteFree(zRight);
       if( cnt==0 ){
@@ -592,9 +612,9 @@
         pParse->nErr++;
         return 1;
       }
-      sqliteExprDelete(pLeft);
+      sqliteExprDelete(pExpr->pLeft);
       pExpr->pLeft = 0;
-      sqliteExprDelete(pRight);
+      sqliteExprDelete(pExpr->pRight);
       pExpr->pRight = 0;
       pExpr->op = TK_COLUMN;
       sqliteAuthRead(pParse, pExpr, pTabList, base);
diff --git a/src/func.c b/src/func.c
index 5892b18..98de567 100644
--- a/src/func.c
+++ b/src/func.c
@@ -16,7 +16,7 @@
 ** sqliteRegisterBuildinFunctions() found at the bottom of the file.
 ** All other code has file scope.
 **
-** $Id: func.c,v 1.23 2002/11/04 19:32:25 drh Exp $
+** $Id: func.c,v 1.24 2003/03/27 12:51:25 drh Exp $
 */
 #include <ctype.h>
 #include <math.h>
@@ -255,6 +255,46 @@
   sqlite_set_result_string(context, sqlite_version, -1);
 }
 
+#ifdef SQLITE_SOUNDEX
+/*
+** Compute the soundex encoding of a word.
+*/
+static void soundexFunc(sqlite_func *context, int argc, const char **argv){
+  char zResult[8];
+  const char *zIn;
+  int i, j;
+  static const unsigned char iCode[] = {
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0,
+    1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
+    0, 0, 1, 2, 3, 0, 1, 2, 0, 0, 2, 2, 4, 5, 5, 0,
+    1, 2, 6, 2, 3, 0, 1, 0, 2, 0, 2, 0, 0, 0, 0, 0,
+  };
+  assert( argc==1 );
+  zIn = argv[0];
+  for(i=0; zIn[i] && !isalpha(zIn[i]); i++){}
+  if( zIn[i] ){
+    zResult[0] = toupper(zIn[i]);
+    for(j=1; j<4 && zIn[i]; i++){
+      int code = iCode[zIn[i]&0x7f];
+      if( code>0 ){
+        zResult[j++] = code + '0';
+      }
+    }
+    while( j<4 ){
+      zResult[j++] = '0';
+    }
+    zResult[j] = 0;
+    sqlite_set_result_string(context, zResult, 4);
+  }else{
+    sqlite_set_result_string(context, zResult, "?000", 4);
+  }
+}
+#endif
+
 #ifdef SQLITE_TEST
 /*
 ** This function generates a string of random characters.  Used for
@@ -490,6 +530,9 @@
     { "glob",       2, SQLITE_NUMERIC, globFunc   },
     { "nullif",     2, SQLITE_ARGS,    nullifFunc },
     { "sqlite_version",0,SQLITE_TEXT,  versionFunc},
+#ifdef SQLITE_SOUNDEX
+    { "soundex",    1, SQLITE_TEXT,    soundexFunc},
+#endif
 #ifdef SQLITE_TEST
     { "randstr",    2, SQLITE_TEXT,    randStr    },
 #endif
diff --git a/src/insert.c b/src/insert.c
index 5ed6616..14f7824 100644
--- a/src/insert.c
+++ b/src/insert.c
@@ -12,7 +12,7 @@
 ** This file contains C code routines that are called by the parser
 ** to handle INSERT statements in SQLite.
 **
-** $Id: insert.c,v 1.74 2003/03/20 01:16:59 drh Exp $
+** $Id: insert.c,v 1.75 2003/03/27 12:51:25 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -93,6 +93,7 @@
 ){
   Table *pTab;          /* The table to insert into */
   char *zTab;           /* Name of the table into which we are inserting */
+  char *zDb;            /* Name of the database holding zTab */
   int i, j, idx;        /* Loop counters */
   Vdbe *v;              /* Generate code into this virtual machine */
   Index *pIdx;          /* For looping over indices of the table */
@@ -120,10 +121,9 @@
   assert( pTabList->nSrc==1 );
   zTab = pTabList->a[0].zName;
   if( zTab==0 ) goto insert_cleanup;
-  pTab = sqliteFindTable(pParse->db, zTab);
+  zDb = pTabList->a[0].zDatabase;
+  pTab = sqliteTableNameToTable(pParse, zTab, zDb);
   if( pTab==0 ){
-    sqliteSetString(&pParse->zErrMsg, "no such table: ", zTab, 0);
-    pParse->nErr++;
     goto insert_cleanup;
   }
   if( sqliteAuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0) ){
@@ -162,7 +162,7 @@
   v = sqliteGetVdbe(pParse);
   if( v==0 ) goto insert_cleanup;
   sqliteBeginWriteOperation(pParse, pSelect || row_triggers_exist,
-         !row_triggers_exist && pTab->isTemp);
+         !row_triggers_exist && pTab->iDb==1);
 
   /* if there are row triggers, allocate a temp table for new.* references. */
   if( row_triggers_exist ){
@@ -196,7 +196,7 @@
     ** should be written into a temporary table.  Set to FALSE if each
     ** row of the SELECT can be written directly into the result table.
     */
-    opCode = pTab->isTemp ? OP_OpenTemp : OP_OpenRead;
+    opCode = pTab->iDb==1 ? OP_OpenTemp : OP_OpenRead;
     useTempTable = row_triggers_exist || sqliteVdbeFindOp(v,opCode,pTab->tnum);
 
     if( useTempTable ){
@@ -327,11 +327,11 @@
   /* Open tables and indices if there are no row triggers */
   if( !row_triggers_exist ){
     base = pParse->nTab;
-    sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+    sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
     sqliteVdbeAddOp(v, OP_OpenWrite, base, pTab->tnum);
     sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
     for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
-      sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+      sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
       sqliteVdbeAddOp(v, OP_OpenWrite, idx+base, pIdx->tnum);
       sqliteVdbeChangeP3(v, -1, pIdx->zName, P3_STATIC);
     }
@@ -389,11 +389,11 @@
     /* Open the tables and indices for the INSERT */
     if( !pTab->pSelect ){
       base = pParse->nTab;
-      sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+      sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
       sqliteVdbeAddOp(v, OP_OpenWrite, base, pTab->tnum);
       sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
       for(idx=1, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, idx++){
-        sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+        sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
         sqliteVdbeAddOp(v, OP_OpenWrite, idx+base, pIdx->tnum);
         sqliteVdbeChangeP3(v, -1, pIdx->zName, P3_STATIC);
       }
diff --git a/src/main.c b/src/main.c
index 60cf794..161bb45 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.116 2003/03/20 01:16:59 drh Exp $
+** $Id: main.c,v 1.117 2003/03/27 12:51:25 drh Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -67,7 +67,7 @@
         memset(&sParse, 0, sizeof(sParse));
         sParse.db = pData->db;
         sParse.initFlag = 1;
-        sParse.isTemp = argv[4][0] - '0';
+        sParse.iDb = atoi(argv[4]);
         sParse.newTnum = atoi(argv[2]);
         sParse.useCallback = 1;
         sqliteRunParser(&sParse, argv[3], pData->pzErrMsg);
@@ -78,7 +78,12 @@
         ** been created when we processed the CREATE TABLE.  All we have
         ** to do here is record the root page number for that index.
         */
-        Index *pIndex = sqliteFindIndex(pData->db, argv[1]);
+        int iDb;
+        Index *pIndex;
+
+        iDb = atoi(argv[4]);
+        assert( iDb>=0 && iDb<pData->db->nDb );
+        pIndex = sqliteFindIndex(pData->db, argv[1], pData->db->aDb[iDb].zName);
         if( pIndex==0 || pIndex->tnum!=0 ){
           /* This can occur if there exists an index on a TEMP table which
           ** has the same name as another index on a permanent index.  Since
@@ -118,7 +123,7 @@
   Trigger *pTrig;
   char *zErr = 0;
 
-  pTab = sqliteFindTable(pData->db, argv[0]);
+  pTab = sqliteFindTable(pData->db, argv[0], 0);
   assert( pTab!=0 );
   assert( sqliteStrICmp(pTab->zName, argv[0])==0 );
   if( pTab ){
@@ -141,7 +146,7 @@
   ** cause the structure that pTab points to be deleted.  In case that
   ** happened, we need to refetch pTab.
   */
-  pTab = sqliteFindTable(pData->db, argv[0]);
+  pTab = sqliteFindTable(pData->db, argv[0], 0);
   if( pTab ){
     assert( sqliteStrICmp(pTab->zName, argv[0])==0 );
     pTab->pTrigger = pTrig;  /* Re-enable triggers */
@@ -234,7 +239,7 @@
   initData.db = db;
   initData.pzErrMsg = pzErrMsg;
   sqliteInitCallback(&initData, 5, azArg, 0);
-  pTab = sqliteFindTable(db, MASTER_NAME);
+  pTab = sqliteFindTable(db, MASTER_NAME, "main");
   if( pTab ){
     pTab->readOnly = 1;
   }
@@ -242,7 +247,7 @@
   azArg[3] = temp_master_schema;
   azArg[4] = "1";
   sqliteInitCallback(&initData, 5, azArg, 0);
-  pTab = sqliteFindTable(db, TEMP_MASTER_NAME);
+  pTab = sqliteFindTable(db, TEMP_MASTER_NAME, "temp");
   if( pTab ){
     pTab->readOnly = 1;
   }
@@ -347,22 +352,24 @@
 */
 sqlite *sqlite_open(const char *zFilename, int mode, char **pzErrMsg){
   sqlite *db;
-  int rc;
+  int rc, i;
 
   /* Allocate the sqlite data structure */
   db = sqliteMalloc( sizeof(sqlite) );
   if( pzErrMsg ) *pzErrMsg = 0;
   if( db==0 ) goto no_mem_on_open;
-  sqliteHashInit(&db->tblHash, SQLITE_HASH_STRING, 0);
-  sqliteHashInit(&db->idxHash, SQLITE_HASH_STRING, 0);
-  sqliteHashInit(&db->trigHash, SQLITE_HASH_STRING, 0);
-  sqliteHashInit(&db->aFunc, SQLITE_HASH_STRING, 1);
-  sqliteHashInit(&db->aFKey, SQLITE_HASH_STRING, 1);
   db->onError = OE_Default;
   db->priorNewRowid = 0;
   db->magic = SQLITE_MAGIC_BUSY;
   db->nDb = 2;
   db->aDb = db->aDbStatic;
+  sqliteHashInit(&db->aFunc, SQLITE_HASH_STRING, 1);
+  for(i=0; i<db->nDb; i++){
+    sqliteHashInit(&db->aDb[i].tblHash, SQLITE_HASH_STRING, 0);
+    sqliteHashInit(&db->aDb[i].idxHash, SQLITE_HASH_STRING, 0);
+    sqliteHashInit(&db->aDb[i].trigHash, SQLITE_HASH_STRING, 0);
+    sqliteHashInit(&db->aDb[i].aFKey, SQLITE_HASH_STRING, 1);
+  }
   
   /* Open the backend database driver */
   rc = sqliteBtreeOpen(zFilename, 0, MAX_PAGES, &db->aDb[0].pBt);
@@ -486,7 +493,6 @@
     }
   }
   sqliteHashClear(&db->aFunc);
-  sqliteHashClear(&db->aFKey);
   sqliteFree(db);
 }
 
diff --git a/src/parse.y b/src/parse.y
index 17d137d..e1feb99 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.92 2003/03/20 01:16:59 drh Exp $
+** @(#) $Id: parse.y,v 1.93 2003/03/27 12:51:25 drh Exp $
 */
 %token_prefix TK_
 %token_type {Token}
@@ -96,8 +96,8 @@
    sqliteStartTable(pParse,&X,&Y,T,0);
 }
 %type temp {int}
-temp(A) ::= TEMP.  {A = pParse->isTemp || !pParse->initFlag;}
-temp(A) ::= .      {A = pParse->isTemp;}
+temp(A) ::= TEMP.  {A = 1;}
+temp(A) ::= .      {A = 0;}
 create_table_args ::= LP columnlist conslist_opt RP(X). {
   sqliteEndTable(pParse,&X,0);
 }
@@ -176,7 +176,7 @@
 ccons ::= NULL onconf.
 ccons ::= NOT NULL onconf(R).               {sqliteAddNotNull(pParse, R);}
 ccons ::= PRIMARY KEY sortorder onconf(R).  {sqliteAddPrimaryKey(pParse,0,R);}
-ccons ::= UNIQUE onconf(R).            {sqliteCreateIndex(pParse,0,0,0,R,0,0);}
+ccons ::= UNIQUE onconf(R).           {sqliteCreateIndex(pParse,0,0,0,R,0,0,0);}
 ccons ::= CHECK LP expr RP onconf.
 ccons ::= REFERENCES nm(T) idxlist_opt(TA) refargs(R).
                                 {sqliteCreateForeignKey(pParse,0,&T,TA,R);}
@@ -223,7 +223,7 @@
 tcons ::= PRIMARY KEY LP idxlist(X) RP onconf(R).
                                              {sqliteAddPrimaryKey(pParse,X,R);}
 tcons ::= UNIQUE LP idxlist(X) RP onconf(R).
-                                       {sqliteCreateIndex(pParse,0,0,X,R,0,0);}
+                                     {sqliteCreateIndex(pParse,0,0,X,R,0,0,0);}
 tcons ::= CHECK expr onconf.
 tcons ::= FOREIGN KEY LP idxlist(FA) RP
           REFERENCES nm(T) idxlist_opt(TA) refargs(R) defer_subclause_opt(D). {
@@ -528,6 +528,13 @@
   Expr *temp2 = sqliteExpr(TK_ID, 0, 0, &Y);
   A = sqliteExpr(TK_DOT, temp1, temp2, 0);
 }
+expr(A) ::= nm(X) DOT nm(Y) DOT nm(Z). {
+  Expr *temp1 = sqliteExpr(TK_ID, 0, 0, &X);
+  Expr *temp2 = sqliteExpr(TK_ID, 0, 0, &Y);
+  Expr *temp3 = sqliteExpr(TK_ID, 0, 0, &Z);
+  Expr *temp4 = sqliteExpr(TK_DOT, temp2, temp3, 0);
+  A = sqliteExpr(TK_DOT, temp1, temp4, 0);
+}
 expr(A) ::= expr(B) ORACLE_OUTER_JOIN. 
                              {A = B; ExprSetProperty(A,EP_Oracle8Join);}
 expr(A) ::= INTEGER(X).      {A = sqliteExpr(TK_INTEGER, 0, 0, &X);}
@@ -692,11 +699,12 @@
 
 ///////////////////////////// The CREATE INDEX command ///////////////////////
 //
-cmd ::= CREATE(S) uniqueflag(U) INDEX nm(X)
-        ON nm(Y) LP idxlist(Z) RP(E) onconf(R). {
+cmd ::= CREATE(S) temp(T) uniqueflag(U) INDEX nm(X)
+        ON nm(Y) dbnm(D) LP idxlist(Z) RP(E) onconf(R). {
+  SrcList *pSrc = sqliteSrcListAppend(0, &Y, &D);
   if( U!=OE_None ) U = R;
   if( U==OE_Default) U = OE_Abort;
-  sqliteCreateIndex(pParse, &X, &Y, Z, U, &S, &E);
+  sqliteCreateIndex(pParse, &X, pSrc, Z, U, T, &S, &E);
 }
 
 %type uniqueflag {int}
@@ -718,15 +726,17 @@
 ///////////////////////////// The DROP INDEX command /////////////////////////
 //
 
-cmd ::= DROP INDEX nm(X).      {sqliteDropIndex(pParse, &X);}
+cmd ::= DROP INDEX nm(X) dbnm(Y).   {
+  sqliteDropIndex(pParse, sqliteSrcListAppend(0,&X,&Y));
+}
 
 
 ///////////////////////////// The COPY command ///////////////////////////////
 //
-cmd ::= COPY orconf(R) nm(X) FROM nm(Y) USING DELIMITERS STRING(Z).
-    {sqliteCopy(pParse,&X,&Y,&Z,R);}
-cmd ::= COPY orconf(R) nm(X) FROM nm(Y).
-    {sqliteCopy(pParse,&X,&Y,0,R);}
+cmd ::= COPY orconf(R) nm(X) dbnm(D) FROM nm(Y) USING DELIMITERS STRING(Z).
+    {sqliteCopy(pParse,sqliteSrcListAppend(0,&X,&D),&Y,&Z,R);}
+cmd ::= COPY orconf(R) nm(X) dbnm(D) FROM nm(Y).
+    {sqliteCopy(pParse,sqliteSrcListAppend(0,&X,&D),&Y,0,R);}
 
 ///////////////////////////// The VACUUM command /////////////////////////////
 //
@@ -749,13 +759,15 @@
 plus_opt ::= .
 
 //////////////////////////// The CREATE TRIGGER command /////////////////////
-cmd ::= CREATE(A) TRIGGER nm(B) trigger_time(C) trigger_event(D) ON nm(E) 
+cmd ::= CREATE(A) TRIGGER nm(B) trigger_time(C) trigger_event(D) 
+                  ON nm(E) dbnm(DB)
                   foreach_clause(F) when_clause(G)
                   BEGIN trigger_cmd_list(S) END(Z). {
+  SrcList *pTab = sqliteSrcListAppend(0, &E, &DB);
   Token all;
   all.z = A.z;
   all.n = (Z.z - A.z) + Z.n;
-  sqliteCreateTrigger(pParse, &B, C, D.a, D.b, &E, F, G, S, &all);
+  sqliteCreateTrigger(pParse, &B, C, D.a, D.b, pTab, F, G, S, &all);
 }
 
 %type trigger_time  {int}
@@ -828,8 +840,8 @@
 }
 
 ////////////////////////  DROP TRIGGER statement //////////////////////////////
-cmd ::= DROP TRIGGER nm(X). {
-    sqliteDropTrigger(pParse,&X,0);
+cmd ::= DROP TRIGGER nm(X) dbnm(D). {
+    sqliteDropTrigger(pParse,sqliteSrcListAppend(0,&X,&D),0);
 }
 
 //////////////////////// ATTACH DATABASE file AS name /////////////////////////
diff --git a/src/select.c b/src/select.c
index eb2892f..4828355 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.127 2003/03/19 03:14:02 drh Exp $
+** $Id: select.c,v 1.128 2003/03/27 12:51:25 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -912,7 +912,8 @@
     }else{
       /* An ordinary table or view name in the FROM clause */
       pTabList->a[i].pTab = pTab = 
-        sqliteFindTable(pParse->db, pTabList->a[i].zName);
+        sqliteFindTable(pParse->db, pTabList->a[i].zName,
+                                    pTabList->a[i].zDatabase);
       if( pTab==0 ){
         sqliteSetString(&pParse->zErrMsg, "no such table: ", 
            pTabList->a[i].zName, 0);
@@ -1830,13 +1831,13 @@
     sqliteCodeVerifySchema(pParse);
   }
   base = p->base;
-  sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+  sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
   sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum);
   sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
   if( pIdx==0 ){
     sqliteVdbeAddOp(v, seekOp, base, 0);
   }else{
-    sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+    sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
     sqliteVdbeAddOp(v, OP_OpenRead, base+1, pIdx->tnum);
     sqliteVdbeChangeP3(v, -1, pIdx->zName, P3_STATIC);
     sqliteVdbeAddOp(v, seekOp, base+1, 0);
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 0e7090e..854e255 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.164 2003/03/20 01:16:59 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.165 2003/03/27 12:51:25 drh Exp $
 */
 #include "config.h"
 #include "sqlite.h"
@@ -200,7 +200,12 @@
   char *zName;         /* Name of this database */
   Btree *pBt;          /* The B*Tree structure for this database file */
   int schema_cookie;   /* Database schema version number for this file */
+  Hash tblHash;        /* All tables indexed by name */
+  Hash idxHash;        /* All (named) indices indexed by name */
+  Hash trigHash;       /* All triggers indexed by name */
+  Hash aFKey;          /* Foreign keys indexed by to-table */
   u8 inTrans;          /* True if a transaction is underway for this backend */
+  u16 flags;           /* Flags associated with this database */
 };
 
 /*
@@ -231,11 +236,7 @@
   int nTable;                   /* Number of tables in the database */
   void *pBusyArg;               /* 1st Argument to the busy callback */
   int (*xBusyCallback)(void *,const char*,int);  /* The busy callback */
-  Hash tblHash;                 /* All tables indexed by name */
-  Hash idxHash;                 /* All (named) indices indexed by name */
-  Hash trigHash;                /* All triggers indexed by name */
   Hash aFunc;                   /* All functions that can be in SQL exprs */
-  Hash aFKey;                   /* Foreign keys indexed by to-table */
   int lastRowid;                /* ROWID of most recent insert */
   int priorNewRowid;            /* Last randomly generated ROWID */
   int onError;                  /* Default conflict algorithm */
@@ -329,26 +330,27 @@
 ** Each SQL table is represented in memory by an instance of the
 ** following structure.
 **
-** Expr.zName is the name of the table.  The case of the original
+** Table.zName is the name of the table.  The case of the original
 ** CREATE TABLE statement is stored, but case is not significant for
 ** comparisons.
 **
-** Expr.nCol is the number of columns in this table.  Expr.aCol is a
+** Table.nCol is the number of columns in this table.  Table.aCol is a
 ** pointer to an array of Column structures, one for each column.
 **
-** If the table has an INTEGER PRIMARY KEY, then Expr.iPKey is the index of
-** the column that is that key.   Otherwise Expr.iPKey is negative.  Note
+** If the table has an INTEGER PRIMARY KEY, then Table.iPKey is the index of
+** the column that is that key.   Otherwise Table.iPKey is negative.  Note
 ** that the datatype of the PRIMARY KEY must be INTEGER for this field to
 ** be set.  An INTEGER PRIMARY KEY is used as the rowid for each row of
 ** the table.  If a table has no INTEGER PRIMARY KEY, then a random rowid
-** is generated for each row of the table.  Expr.hasPrimKey is true if
+** is generated for each row of the table.  Table.hasPrimKey is true if
 ** the table has any PRIMARY KEY, INTEGER or otherwise.
 **
-** Expr.tnum is the page number for the root BTree page of the table in the
-** database file.  If Expr.isTemp is true, then this page occurs in the
-** auxiliary database file, not the main database file.  If Expr.isTransient
+** Table.tnum is the page number for the root BTree page of the table in the
+** database file.  If Table.iDb is the index of the database table backend
+** in sqlite.aDb[].  0 is for the main database and 1 is for the file that
+** holds temporary tables and indices.  If Table.isTransient
 ** is true, then the table is stored in a file that is automatically deleted
-** when the VDBE cursor to the table is closed.  In this case Expr.tnum 
+** when the VDBE cursor to the table is closed.  In this case Table.tnum 
 ** refers VDBE cursor number that holds the table open, not to the root
 ** page number.  Transient tables are used to hold the results of a
 ** sub-query that appears instead of a real table name in the FROM clause 
@@ -363,7 +365,7 @@
   int tnum;        /* Root BTree node for this table (see note above) */
   Select *pSelect; /* NULL for tables.  Points to definition if a view. */
   u8 readOnly;     /* True if this table should not be written by the user */
-  u8 isTemp;       /* Index into sqlite.aDb[] of the backend for this table */
+  u8 iDb;          /* Index into sqlite.aDb[] of the backend for this table */
   u8 isTransient;  /* True if automatically deleted when VDBE finishes */
   u8 hasPrimKey;   /* True if there exists a primary key */
   u8 keyConf;      /* What to do in case of uniqueness conflict on iPKey */
@@ -479,6 +481,7 @@
   u8 isUnique;     /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
   u8 onError;      /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */
   u8 autoIndex;    /* True if is automatically created (ex: by UNIQUE) */
+  u8 iDb;          /* Index in sqlite.aDb[] of where this index is stored */
   Index *pNext;    /* The next index associated with the same table */
 };
 
@@ -604,6 +607,12 @@
 ** The following structure describes the FROM clause of a SELECT statement.
 ** Each table or subquery in the FROM clause is a separate element of
 ** the SrcList.a[] array.
+**
+** With the addition of multiple database support, the following structure
+** can also be used to describe a particular table such as the table that
+** is modified by an INSERT, DELETE, or UPDATE statement.  In standard SQL,
+** such a table must be a simple name: ID.  But in SQLite, the table can
+** now be identified by a database name, a dot, then the table name: ID.ID.
 */
 struct SrcList {
   int nSrc;        /* Number of tables or subqueries in the FROM clause */
@@ -765,7 +774,7 @@
                        ** while generating expressions.  Normally false */
   u8 schemaVerified;   /* True if an OP_VerifySchema has been coded someplace
                        ** other than after an OP_Transaction */
-  u8 isTemp;           /* True if parsing temporary tables */
+  u8 iDb;              /* Index of database whose schema is being parsed */
   u8 useCallback;      /* True if callbacks should be used to report results */
   int newTnum;         /* Table number to use when reparsing CREATE TABLEs */
   int nErr;            /* Number of errors seen */
@@ -807,6 +816,7 @@
 struct Trigger {
   char *name;             /* The name of the trigger                        */
   char *table;            /* The table or view to which the trigger applies */
+  int iDb;                /* Database containing this trigger               */
   int op;                 /* One of TK_DELETE, TK_UPDATE, TK_INSERT         */
   int tr_tm;              /* One of TK_BEFORE, TK_AFTER */
   Expr *pWhen;            /* The WHEN clause of the expresion (may be NULL) */
@@ -973,7 +983,6 @@
 void sqliteEndTable(Parse*,Token*,Select*);
 void sqliteCreateView(Parse*,Token*,Token*,Select*,int);
 int sqliteViewGetColumnNames(Parse*,Table*);
-void sqliteViewResetAll(sqlite*);
 void sqliteDropTable(Parse*, Token*, int);
 void sqliteDeleteTable(sqlite*, Table*);
 void sqliteInsert(Parse*, SrcList*, ExprList*, Select*, IdList*, int);
@@ -983,8 +992,8 @@
 void sqliteSrcListAddAlias(SrcList*, Token*);
 void sqliteIdListDelete(IdList*);
 void sqliteSrcListDelete(SrcList*);
-void sqliteCreateIndex(Parse*, Token*, Token*, IdList*, int, Token*, Token*);
-void sqliteDropIndex(Parse*, Token*);
+void sqliteCreateIndex(Parse*,Token*,SrcList*,IdList*,int,int,Token*,Token*);
+void sqliteDropIndex(Parse*, SrcList*);
 void sqliteAddKeyType(Vdbe*, ExprList*);
 void sqliteAddIdxKeyType(Vdbe*, Index*);
 int sqliteSelect(Parse*, Select*, int, int, Select*, int, int*);
@@ -992,7 +1001,7 @@
                         int,int,int);
 void sqliteSelectDelete(Select*);
 void sqliteSelectUnbind(Select*);
-Table *sqliteTableNameToTable(Parse*, const char*);
+Table *sqliteTableNameToTable(Parse*, const char*, const char*);
 void sqliteDeleteFrom(Parse*, SrcList*, Expr*);
 void sqliteUpdate(Parse*, SrcList*, ExprList*, Expr*, int);
 WhereInfo *sqliteWhereBegin(Parse*, int, SrcList*, Expr*, int, ExprList**);
@@ -1000,10 +1009,10 @@
 void sqliteExprCode(Parse*, Expr*);
 void sqliteExprIfTrue(Parse*, Expr*, int, int);
 void sqliteExprIfFalse(Parse*, Expr*, int, int);
-Table *sqliteFindTable(sqlite*,const char*);
-Index *sqliteFindIndex(sqlite*,const char*);
+Table *sqliteFindTable(sqlite*,const char*, const char*);
+Index *sqliteFindIndex(sqlite*,const char*, const char*);
 void sqliteUnlinkAndDeleteIndex(sqlite*,Index*);
-void sqliteCopy(Parse*, Token*, Token*, Token*, int);
+void sqliteCopy(Parse*, SrcList*, Token*, Token*, int);
 void sqliteVacuum(Parse*, Token*);
 int sqliteGlobCompare(const unsigned char*,const unsigned char*);
 int sqliteLikeCompare(const unsigned char*,const unsigned char*);
@@ -1043,9 +1052,9 @@
 int sqliteSafetyOff(sqlite*);
 int sqliteSafetyCheck(sqlite*);
 void sqliteChangeCookie(sqlite*, Vdbe*);
-void sqliteCreateTrigger(Parse*, Token*, int, int, IdList*, Token*, 
+void sqliteCreateTrigger(Parse*, Token*, int, int, IdList*, SrcList*, 
                          int, Expr*, TriggerStep*, Token*);
-void sqliteDropTrigger(Parse*, Token*, int);
+void sqliteDropTrigger(Parse*, SrcList*, int);
 int sqliteTriggersExist(Parse* , Trigger* , int , int , int, ExprList*);
 int sqliteCodeRowTrigger(Parse*, int, ExprList*, int, Table *, int, int, 
                          int, int);
diff --git a/src/trigger.c b/src/trigger.c
index 76bdc77..4c0c007 100644
--- a/src/trigger.c
+++ b/src/trigger.c
@@ -41,7 +41,7 @@
   int tr_tm,          /* One of TK_BEFORE, TK_AFTER , TK_INSTEAD */
   int op,             /* One of TK_INSERT, TK_UPDATE, TK_DELETE */
   IdList *pColumns,   /* column list if this is an UPDATE OF trigger */
-  Token *pTableName,  /* The name of the table/view the trigger applies to */
+  SrcList *pTableName,/* The name of the table/view the trigger applies to */
   int foreach,        /* One of TK_ROW or TK_STATEMENT */
   Expr *pWhen,        /* WHEN clause */
   TriggerStep *pStepList, /* The triggered program */
@@ -50,6 +50,7 @@
   Trigger *nt;
   Table   *tab;
   char *zName = 0;    /* Name of the trigger */
+  sqlite *db = pParse->db;
 
   /* Check that: 
   ** 1. the trigger name does not already exist.
@@ -58,60 +59,55 @@
   ** 4. That we are not trying to create an INSTEAD OF trigger on a table.
   ** 5. That we are not trying to create a BEFORE or AFTER trigger on a view.
   */
+  if( sqlite_malloc_failed ) goto trigger_cleanup;
+  assert( pTableName->nSrc==1 );
+  tab = sqliteTableNameToTable(pParse, pTableName->a[0].zName,
+                                pTableName->a[0].zDatabase);
+  if( !tab ){
+    goto trigger_cleanup;
+  }
+  if( tab->iDb>=2 ){
+    sqliteSetString(&pParse->zErrMsg, "triggers may not be added to "
+       "auxiliary database \"", db->aDb[tab->iDb].zName, "\"", 0);
+    pParse->nErr++;
+    goto trigger_cleanup;
+  }
+
   zName = sqliteStrNDup(pName->z, pName->n);
-  if( sqliteHashFind(&(pParse->db->trigHash), zName, pName->n + 1) ){
+  if( sqliteHashFind(&(db->aDb[tab->iDb].trigHash), zName,pName->n+1) ){
     sqliteSetNString(&pParse->zErrMsg, "trigger ", -1,
         pName->z, pName->n, " already exists", -1, 0);
     pParse->nErr++;
     goto trigger_cleanup;
   }
-  {
-    char *tmp_str = sqliteStrNDup(pTableName->z, pTableName->n);
-    if( tmp_str==0 ) goto trigger_cleanup;
-    tab = sqliteFindTable(pParse->db, tmp_str);
-    sqliteFree(tmp_str);
-    if( !tab ){
-      sqliteSetNString(&pParse->zErrMsg, "no such table: ", -1,
-          pTableName->z, pTableName->n, 0);
-      pParse->nErr++;
-      goto trigger_cleanup;
-    }
-    if( sqliteStrICmp(tab->zName, MASTER_NAME)==0 ){
-      sqliteSetString(&pParse->zErrMsg, "cannot create trigger on system "
-         "table: " MASTER_NAME, 0);
-      pParse->nErr++;
-      goto trigger_cleanup;
-    }
-    if( sqliteStrICmp(tab->zName, TEMP_MASTER_NAME)==0 ){
-      sqliteSetString(&pParse->zErrMsg, "cannot create trigger on system "
-         "table: " TEMP_MASTER_NAME, 0);
-      pParse->nErr++;
-      goto trigger_cleanup;
-    }
-    if( tab->pSelect && tr_tm != TK_INSTEAD ){
-      sqliteSetNString(&pParse->zErrMsg, "cannot create ", -1,
-	  (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", -1, " trigger on view: ", -1
-          , pTableName->z, pTableName->n, 0);
-      goto trigger_cleanup;
-    }
-    if( !tab->pSelect && tr_tm == TK_INSTEAD ){
-      sqliteSetNString(&pParse->zErrMsg, "cannot create INSTEAD OF", -1, 
-	  " trigger on table: ", -1, pTableName->z, pTableName->n, 0);
-      goto trigger_cleanup;
-    }
-#ifndef SQLITE_OMIT_AUTHORIZATION
-    {
-      int code = SQLITE_CREATE_TRIGGER;
-      if( tab->isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER;
-      if( sqliteAuthCheck(pParse, code, zName, tab->zName) ){
-        goto trigger_cleanup;
-      }
-      if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(tab->isTemp), 0)){
-        goto trigger_cleanup;
-      }
-    }
-#endif
+  if( sqliteStrNICmp(tab->zName, "sqlite_", 7)==0 ){
+    sqliteSetString(&pParse->zErrMsg,"cannot create trigger on system table",0);
+    pParse->nErr++;
+    goto trigger_cleanup;
   }
+  if( tab->pSelect && tr_tm != TK_INSTEAD ){
+    sqliteSetNString(&pParse->zErrMsg, "cannot create ", 
+        (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", " trigger on view: ",
+        pTableName->a[0].zName, 0);
+    goto trigger_cleanup;
+  }
+  if( !tab->pSelect && tr_tm == TK_INSTEAD ){
+    sqliteSetNString(&pParse->zErrMsg, "cannot create INSTEAD OF", 
+        " trigger on table: ", pTableName->a[0].zName);
+    goto trigger_cleanup;
+  }
+#ifndef SQLITE_OMIT_AUTHORIZATION
+  {
+    int code = SQLITE_CREATE_TRIGGER;
+    if( tab->iDb==1 ) code = SQLITE_CREATE_TEMP_TRIGGER;
+    if( sqliteAuthCheck(pParse, code, zName, tab->zName) ){
+      goto trigger_cleanup;
+    }
+    if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(tab->iDb), 0)){
+      goto trigger_cleanup;
+    }
+  }
+#endif
 
   if (tr_tm == TK_INSTEAD){
     tr_tm = TK_BEFORE;
@@ -122,8 +118,9 @@
   if( nt==0 ) goto trigger_cleanup;
   nt->name = zName;
   zName = 0;
-  nt->table = sqliteStrNDup(pTableName->z, pTableName->n);
+  nt->table = sqliteStrDup(pTableName->a[0].zName);
   if( sqlite_malloc_failed ) goto trigger_cleanup;
+  nt->iDb = tab->iDb;
   nt->op = op;
   nt->tr_tm = tr_tm;
   nt->pWhen = sqliteExprDup(pWhen);
@@ -154,15 +151,15 @@
     v = sqliteGetVdbe(pParse);
     if( v==0 ) goto trigger_cleanup;
     sqliteBeginWriteOperation(pParse, 0, 0);
-    sqliteOpenMasterTable(v, tab->isTemp);
+    sqliteOpenMasterTable(v, tab->iDb);
     addr = sqliteVdbeAddOpList(v, ArraySize(insertTrig), insertTrig);
-    sqliteVdbeChangeP3(v, addr, tab->isTemp ? TEMP_MASTER_NAME : MASTER_NAME,
+    sqliteVdbeChangeP3(v, addr, tab->iDb ? TEMP_MASTER_NAME : MASTER_NAME,
                        P3_STATIC);
     sqliteVdbeChangeP3(v, addr+2, nt->name, 0); 
     sqliteVdbeChangeP3(v, addr+3, nt->table, 0); 
     sqliteVdbeChangeP3(v, addr+5, pAll->z, pAll->n);
-    if( !tab->isTemp ){
-      sqliteChangeCookie(pParse->db, v);
+    if( tab->iDb==0 ){
+      sqliteChangeCookie(db, v);
     }
     sqliteVdbeAddOp(v, OP_Close, 0, 0);
     sqliteEndWriteOperation(pParse);
@@ -170,7 +167,7 @@
 
   if( !pParse->explain ){
     /* Stick it in the hash-table */
-    sqliteHashInsert(&(pParse->db->trigHash), nt->name, pName->n + 1, nt);
+    sqliteHashInsert(&(db->aDb[nt->iDb].trigHash), nt->name, pName->n + 1, nt);
 
     /* Attach it to the table object */
     nt->pNext = tab->pTrigger;
@@ -185,6 +182,7 @@
 trigger_cleanup:
 
   sqliteFree(zName);
+  sqliteSrcListDelete(pTableName);
   sqliteIdListDelete(pColumns);
   sqliteExprDelete(pWhen);
   sqliteDeleteTriggerStep(pStepList);
@@ -342,31 +340,46 @@
  * table. This is so that the trigger can be restored into the database schema
  * if the transaction is rolled back.
  */
-void sqliteDropTrigger(Parse *pParse, Token *pName, int nested){
-  char *zName;
+void sqliteDropTrigger(Parse *pParse, SrcList *pName, int nested){
   Trigger *pTrigger;
   Table   *pTable;
   Vdbe *v;
+  int i;
+  const char *zDb;
+  const char *zName;
+  int nName;
+  sqlite *db = pParse->db;
 
-  zName = sqliteStrNDup(pName->z, pName->n);
-
-  /* ensure that the trigger being dropped exists */
-  pTrigger = sqliteHashFind(&(pParse->db->trigHash), zName, pName->n + 1); 
-  if( !pTrigger ){
-    sqliteSetNString(&pParse->zErrMsg, "no such trigger: ", -1,
-        zName, -1, 0);
-    sqliteFree(zName);
-    return;
+  if( sqlite_malloc_failed ) goto drop_trigger_cleanup;
+  assert( pName->nSrc==1 );
+  zDb = pName->a[0].zDatabase;
+  zName = pName->a[0].zName;
+  nName = strlen(zName);
+  for(i=0; i<db->nDb; i++){
+    if( zDb && sqliteStrICmp(db->aDb[i].zName, zDb) ) continue;
+    pTrigger = sqliteHashFind(&(db->aDb[i].trigHash), zName, nName+1);
+    if( pTrigger ) break;
   }
-  pTable = sqliteFindTable(pParse->db, pTrigger->table);
+  if( !pTrigger ){
+    sqliteSetString(&pParse->zErrMsg, "no such trigger: ", zName, 0);
+    goto drop_trigger_cleanup;
+  }
+  assert( pTrigger->iDb>=0 && pTrigger->iDb<db->nDb );
+  if( pTrigger->iDb>=2 ){
+    sqliteSetString(&pParse->zErrMsg, "triggers may not be removed from "
+       "auxiliary database \"", db->aDb[pTrigger->iDb].zName, "\"", 0);
+    pParse->nErr++;
+    goto drop_trigger_cleanup;
+  }
+  pTable = sqliteFindTable(db, pTrigger->table, db->aDb[pTrigger->iDb].zName);
   assert(pTable);
+  assert( pTable->iDb==pTrigger->iDb );
 #ifndef SQLITE_OMIT_AUTHORIZATION
   {
     int code = SQLITE_DROP_TRIGGER;
-    if( pTable->isTemp ) code = SQLITE_DROP_TEMP_TRIGGER;
+    if( pTable->iDb ) code = SQLITE_DROP_TEMP_TRIGGER;
     if( sqliteAuthCheck(pParse, code, pTrigger->name, pTable->zName) ||
-      sqliteAuthCheck(pParse, SQLITE_DELETE, SCHEMA_TABLE(pTable->isTemp),0) ){
-      sqliteFree(zName);
+      sqliteAuthCheck(pParse, SQLITE_DELETE, SCHEMA_TABLE(pTable->iDb),0) ){
       return;
     }
   }
@@ -389,7 +402,7 @@
       }
       assert(cc);
     }
-    sqliteHashInsert(&(pParse->db->trigHash), zName, pName->n + 1, NULL);
+    sqliteHashInsert(&(db->aDb[pTrigger->iDb].trigHash), zName, nName+1, 0);
     sqliteDeleteTrigger(pTrigger);
   }
 
@@ -409,17 +422,18 @@
     };
 
     sqliteBeginWriteOperation(pParse, 0, 0);
-    sqliteOpenMasterTable(v, pTable->isTemp);
+    sqliteOpenMasterTable(v, pTable->iDb);
     base = sqliteVdbeAddOpList(v,  ArraySize(dropTrigger), dropTrigger);
     sqliteVdbeChangeP3(v, base+1, zName, 0);
-    if( !pTable->isTemp ){
-      sqliteChangeCookie(pParse->db, v);
+    if( pTable->iDb==0 ){
+      sqliteChangeCookie(db, v);
     }
     sqliteVdbeAddOp(v, OP_Close, 0, 0);
     sqliteEndWriteOperation(pParse);
   }
 
-  sqliteFree(zName);
+drop_trigger_cleanup:
+  sqliteSrcListDelete(pName);
 }
 
 /*
diff --git a/src/update.c b/src/update.c
index 93a6744..f76686d 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.55 2003/03/20 01:16:59 drh Exp $
+** $Id: update.c,v 1.56 2003/03/27 12:51:25 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -28,6 +28,7 @@
 ){
   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 */
@@ -59,8 +60,9 @@
    * defined 
    */
   zTab = pTabList->a[0].zName;
+  zDb = pTabList->a[0].zDatabase;
   if( zTab != 0 ){
-    pTab = sqliteFindTable(pParse->db, zTab);
+    pTab = sqliteFindTable(pParse->db, zTab, zDb);
     if( pTab ){
       row_triggers_exist = 
         sqliteTriggersExist(pParse, pTab->pTrigger, 
@@ -82,7 +84,7 @@
   ** will be calling are designed to work with multiple tables and expect
   ** an SrcList* parameter instead of just a Table* parameter.
   */
-  pTab = pTabList->a[0].pTab = sqliteTableNameToTable(pParse, zTab);
+  pTab = pTabList->a[0].pTab = sqliteTableNameToTable(pParse, zTab, zDb);
   if( pTab==0 ) goto update_cleanup;
   assert( pTab->pSelect==0 );  /* This table is not a VIEW */
   aXRef = sqliteMalloc( sizeof(int) * pTab->nCol );
@@ -197,7 +199,7 @@
   */
   v = sqliteGetVdbe(pParse);
   if( v==0 ) goto update_cleanup;
-  sqliteBeginWriteOperation(pParse, 1, !row_triggers_exist && pTab->isTemp);
+  sqliteBeginWriteOperation(pParse, 1, !row_triggers_exist && pTab->iDb==1);
 
   /* Begin the database scan
   */
@@ -229,7 +231,7 @@
     sqliteVdbeAddOp(v, OP_Dup, 0, 0);
 
     sqliteVdbeAddOp(v, OP_Dup, 0, 0);
-    sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+    sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
     sqliteVdbeAddOp(v, OP_OpenRead, base, pTab->tnum);
     sqliteVdbeAddOp(v, OP_MoveTo, base, 0);
 
@@ -275,7 +277,7 @@
   ** action, then we need to open all indices because we might need
   ** to be deleting some records.
   */
-  sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+  sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
   sqliteVdbeAddOp(v, OP_OpenWrite, base, pTab->tnum);
   if( onError==OE_Replace ){
     openAll = 1;
@@ -290,7 +292,7 @@
   }
   for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){
     if( openAll || aIdxUsed[i] ){
-      sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+      sqliteVdbeAddOp(v, OP_Integer, pIdx->iDb, 0);
       sqliteVdbeAddOp(v, OP_OpenWrite, base+i+1, pIdx->tnum);
       assert( pParse->nTab>base+i+1 );
     }
diff --git a/src/where.c b/src/where.c
index 3f7f306..66b13b8 100644
--- a/src/where.c
+++ b/src/where.c
@@ -13,7 +13,7 @@
 ** the WHERE clause of SQL statements.  Also found here are subroutines
 ** to generate VDBE code to evaluate expressions.
 **
-** $Id: where.c,v 1.73 2003/03/19 03:14:03 drh Exp $
+** $Id: where.c,v 1.74 2003/03/27 12:51:26 drh Exp $
 */
 #include "sqliteInt.h"
 
@@ -640,7 +640,7 @@
 
     pTab = pTabList->a[i].pTab;
     if( pTab->isTransient || pTab->pSelect ) continue;
-    sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+    sqliteVdbeAddOp(v, OP_Integer, pTab->iDb, 0);
     sqliteVdbeAddOp(v, OP_OpenRead, base+i, pTab->tnum);
     sqliteVdbeChangeP3(v, -1, pTab->zName, P3_STATIC);
     if( i==0 && !pParse->schemaVerified &&
@@ -648,7 +648,7 @@
       sqliteCodeVerifySchema(pParse);
     }
     if( pWInfo->a[i].pIdx!=0 ){
-      sqliteVdbeAddOp(v, OP_Integer, pTab->isTemp, 0);
+      sqliteVdbeAddOp(v, OP_Integer, pWInfo->a[i].pIdx->iDb, 0);
       sqliteVdbeAddOp(v, OP_OpenRead,
                       pWInfo->a[i].iCur, pWInfo->a[i].pIdx->tnum);
       sqliteVdbeChangeP3(v, -1, pWInfo->a[i].pIdx->zName, P3_STATIC);