The ATTACH and DETACH statements are now coded but are still mostly untested. (CVS 890)

FossilOrigin-Name: c7c5e927a54f0fbc2ca625754787aff4d9c4eff1
diff --git a/src/build.c b/src/build.c
index 141d15f..059c69b 100644
--- a/src/build.c
+++ b/src/build.c
@@ -25,7 +25,7 @@
 **     ROLLBACK
 **     PRAGMA
 **
-** $Id: build.c,v 1.136 2003/03/30 00:19:50 drh Exp $
+** $Id: build.c,v 1.137 2003/03/31 00:30:48 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -186,14 +186,20 @@
 ** database connection.  This routine is called to reclaim memory
 ** before the connection closes.  It is also called during a rollback
 ** if there were schema changes during the transaction.
+**
+** If iDb<=0 then reset the internal schema tables for all database
+** files.  If iDb>=2 then reset the internal schema for only the
+** single file indicates.
 */
-void sqliteResetInternalSchema(sqlite *db){
+void sqliteResetInternalSchema(sqlite *db, int iDb){
   HashElem *pElem;
   Hash temp1;
   Hash temp2;
-  int i;
+  int i, j;
 
-  for(i=0; i<db->nDb; i++){
+  assert( iDb>=0 && iDb<db->nDb );
+  db->flags &= ~SQLITE_Initialized;
+  for(i=iDb; i<db->nDb; i++){
     Db *pDb = &db->aDb[i];
     temp1 = pDb->tblHash;
     temp2 = pDb->trigHash;
@@ -211,8 +217,35 @@
       sqliteDeleteTable(db, pTab);
     }
     sqliteHashClear(&temp1);
+    db->aDb[i].flags &= ~SQLITE_Initialized;
+    if( iDb>0 ) return;
   }
-  db->flags &= ~(SQLITE_Initialized|SQLITE_InternChanges);
+  assert( iDb==0 );
+  db->flags &= ~SQLITE_InternChanges;
+
+  /* If one or more of the auxiliary database files has been closed,
+  ** then remove then from the auxiliary database list.  We take the
+  ** opportunity to do this here since we have just deleted all of the
+  ** schema hash tables and therefore do not have to make any changes
+  ** to any of those tables.
+  */
+  for(i=j=2; i<db->nDb; i++){
+    if( db->aDb[i].pBt==0 ){
+      sqliteFree(db->aDb[i].zName);
+      db->aDb[i].zName = 0;
+      continue;
+    }
+    if( j<i ){
+      db->aDb[j++] = db->aDb[i];
+    }
+  }
+  memset(&db->aDb[j], 0, (db->nDb-j)*sizeof(db->aDb[j]));
+  db->nDb = j;
+  if( db->nDb<=2 && db->aDb!=db->aDbStatic ){
+    memcpy(db->aDbStatic, db->aDb, 2*sizeof(db->aDb[0]));
+    sqliteFree(db->aDb);
+    db->aDb = db->aDbStatic;
+  }
 }
 
 /*
@@ -222,7 +255,7 @@
 */
 void sqliteRollbackInternalChanges(sqlite *db){
   if( db->flags & SQLITE_InternChanges ){
-    sqliteResetInternalSchema(db);
+    sqliteResetInternalSchema(db, 0);
   }
 }
 
@@ -366,6 +399,7 @@
   char *zName;
   sqlite *db = pParse->db;
   Vdbe *v;
+  int iDb;
 
   pParse->sFirstToken = *pStart;
   zName = sqliteTableNameFromToken(pName);
@@ -430,7 +464,8 @@
   ** an existing temporary table, that is not an error.
   */
   pTable = sqliteFindTable(db, zName, 0);
-  if( pTable!=0 && (pTable->iDb==isTemp || !pParse->initFlag) ){
+  iDb = isTemp ? 1 : pParse->iDb;
+  if( pTable!=0 && (pTable->iDb==iDb || !pParse->initFlag) ){
     sqliteSetNString(&pParse->zErrMsg, "table ", 0, pName->z, pName->n,
         " already exists", 0, 0);
     sqliteFree(zName);
@@ -455,7 +490,7 @@
   pTable->aCol = 0;
   pTable->iPKey = -1;
   pTable->pIndex = 0;
-  pTable->iDb = isTemp ? 1 : pParse->iDb;
+  pTable->iDb = iDb;
   if( pParse->pNewTable ) sqliteDeleteTable(db, pParse->pNewTable);
   pParse->pNewTable = pTable;
 
@@ -1458,7 +1493,7 @@
     pParse->nErr++;
     goto exit_create_index;
   }
-  if( !isTemp && pTab->iDb>=2 ){
+  if( !isTemp && pTab->iDb>=2 && pParse->initFlag==0 ){
     sqliteSetString(&pParse->zErrMsg, "table ", pTab->zName, 
       " may not have non-temporary indices added", 0);
     pParse->nErr++;
@@ -1473,20 +1508,6 @@
     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
-  ** one whose name is hidden behind a temporary table with the same name.
-  ** Since its table has been suppressed, we need to also suppress the
-  ** index.
-  */
-  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
   ** index or table with the same name.  
@@ -2151,7 +2172,7 @@
   Vdbe *v = sqliteGetVdbe(pParse);
   for(i=0; i<db->nDb; i++){
     if( i==1 || db->aDb[i].pBt==0 ) continue;
-    sqliteVdbeAddOp(v, OP_VerifyCookie, 0, db->aDb[i].schema_cookie);
+    sqliteVdbeAddOp(v, OP_VerifyCookie, i, db->aDb[i].schema_cookie);
   }
   pParse->schemaVerified = 1;
 }
@@ -2641,3 +2662,101 @@
   sqliteFree(zLeft);
   sqliteFree(zRight);
 }
+
+/*
+** This routine is called by the parser to process an ATTACH statement:
+**
+**     ATTACH DATABASE filename AS dbname
+**
+** The pFilename and pDbname arguments are the tokens that define the
+** filename and dbname in the ATTACH statement.
+*/
+void sqliteAttach(Parse *pParse, Token *pFilename, Token *pDbname){
+  Db *aNew;
+  int rc, i;
+  char *zFile, *zName;
+  sqlite *db;
+
+  if( pParse->explain ) return;
+  db = pParse->db;
+  if( db->aDb==db->aDbStatic ){
+    aNew = sqliteMalloc( sizeof(db->aDb[0])*3 );
+    if( aNew==0 ) return;
+    memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2);
+  }else{
+    aNew = sqliteRealloc(db->aDb, sizeof(db->aDb[0])*(db->nDb+1) );
+    if( aNew==0 ) return;
+  }
+  db->aDb = aNew;
+  aNew = &db->aDb[db->nDb++];
+  memset(aNew, 0, sizeof(*aNew));
+  sqliteHashInit(&aNew->tblHash, SQLITE_HASH_STRING, 0);
+  sqliteHashInit(&aNew->idxHash, SQLITE_HASH_STRING, 0);
+  sqliteHashInit(&aNew->trigHash, SQLITE_HASH_STRING, 0);
+  sqliteHashInit(&aNew->aFKey, SQLITE_HASH_STRING, 1);
+  
+  zName = 0;
+  sqliteSetNString(&zName, pDbname->z, pDbname->n, 0);
+  if( zName==0 ) return;
+  sqliteDequote(zName);
+  for(i=0; i<db->nDb; i++){
+    if( db->aDb[i].zName && sqliteStrICmp(db->aDb[i].zName, zName)==0 ){
+      sqliteSetString(&pParse->zErrMsg, "database \"", zName, 
+         "\" already in use", 0);
+      sqliteFree(zName);
+      pParse->nErr++;
+      return;
+    }
+  }
+  aNew->zName = zName;
+  zFile = 0;
+  sqliteSetNString(&zFile, pFilename->z, pFilename->n, 0);
+  if( zFile==0 ) return;
+  sqliteDequote(zFile);
+  rc = sqliteBtreeOpen(zFile, 0, MAX_PAGES, &aNew->pBt);
+  if( rc ){
+    sqliteSetString(&pParse->zErrMsg, "unable to open database: ", zFile, 0);
+    pParse->nErr++;
+  }
+  sqliteFree(zFile);
+  db->flags &= ~SQLITE_Initialized;
+  if( pParse->nErr ) return;
+  rc = sqliteInit(pParse->db, &pParse->zErrMsg);
+  if( rc ){
+    pParse->nErr++;
+  }
+}
+
+/*
+** This routine is called by the parser to process a DETACH statement:
+**
+**    DETACH DATABASE dbname
+**
+** The pDbname argument is the name of the database in the DETACH statement.
+*/
+void sqliteDetach(Parse *pParse, Token *pDbname){
+  int i;
+  sqlite *db;
+
+  if( pParse->explain ) return;
+  db = pParse->db;
+  for(i=0; i<db->nDb; i++){
+    if( db->aDb[i].pBt==0 || db->aDb[i].zName==0 ) continue;
+    if( strlen(db->aDb[i].zName)!=pDbname->n ) continue;
+    if( sqliteStrNICmp(db->aDb[i].zName, pDbname->z, pDbname->n)==0 ) break;
+  }
+  if( i>=db->nDb ){
+    sqliteSetNString(&pParse->zErrMsg, "no such database: ", -1,
+        pDbname->z, pDbname->n, 0);
+    pParse->nErr++;
+    return;
+  }
+  if( i<2 ){
+    sqliteSetString(&pParse->zErrMsg, "cannot detached \"main\" or \"temp\"",0);
+    pParse->nErr++;
+    return;
+  }
+  sqliteBtreeClose(db->aDb[i].pBt);
+  db->aDb[i].pBt = 0;
+  sqliteResetInternalSchema(db, 0);
+}
diff --git a/src/main.c b/src/main.c
index ee1985f..aa4e4b3 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.118 2003/03/30 19:17:02 drh Exp $
+** $Id: main.c,v 1.119 2003/03/31 00:30:48 drh Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -40,7 +40,8 @@
 **     argv[1] = table or index name or meta statement type.
 **     argv[2] = root page number for table or index.  NULL for meta.
 **     argv[3] = SQL text for a CREATE TABLE or CREATE INDEX statement.
-**     argv[4] = "1" for temporary files, "0" for main database
+**     argv[4] = "1" for temporary files, "0" for main database, "2" or more
+**               for auxiliary database files.
 **
 */
 static
@@ -158,22 +159,19 @@
 
 /*
 ** Attempt to read the database schema and initialize internal
-** data structures.  Return one of the SQLITE_ error codes to
+** data structures for a single database file.  The index of the
+** database file is given by iDb.  iDb==0 is used for the main
+** database.  iDb==1 should never be used.  iDb>=2 is used for
+** auxiliary databases.  Return one of the SQLITE_ error codes to
 ** indicate success or failure.
-**
-** After the database is initialized, the SQLITE_Initialized
-** bit is set in the flags field of the sqlite structure.  An
-** attempt is made to initialize the database as soon as it
-** is opened.  If that fails (perhaps because another process
-** has the sqlite_master table locked) than another attempt
-** is made the first time the database is accessed.
 */
-int sqliteInit(sqlite *db, char **pzErrMsg){
+static int sqliteInitOne(sqlite *db, int iDb, char **pzErrMsg){
   int rc;
   BtCursor *curMain;
   int size;
   Table *pTab;
   char *azArg[6];
+  char zDbNum[30];
   int meta[SQLITE_N_BTREE_META];
   Parse sParse;
   InitData initData;
@@ -228,13 +226,16 @@
      "WHERE type='index'";
 
 
+  assert( iDb>=0 && iDb!=1 && iDb<db->nDb );
+
   /* Construct the schema tables: sqlite_master and sqlite_temp_master
   */
   azArg[0] = "table";
   azArg[1] = MASTER_NAME;
   azArg[2] = "2";
   azArg[3] = master_schema;
-  azArg[4] = "0";
+  sprintf(zDbNum, "%d", iDb);
+  azArg[4] = zDbNum;
   azArg[5] = 0;
   initData.db = db;
   initData.pzErrMsg = pzErrMsg;
@@ -243,59 +244,68 @@
   if( pTab ){
     pTab->readOnly = 1;
   }
-  azArg[1] = TEMP_MASTER_NAME;
-  azArg[3] = temp_master_schema;
-  azArg[4] = "1";
-  sqliteInitCallback(&initData, 5, azArg, 0);
-  pTab = sqliteFindTable(db, TEMP_MASTER_NAME, "temp");
-  if( pTab ){
-    pTab->readOnly = 1;
+  if( iDb==0 ){
+    azArg[1] = TEMP_MASTER_NAME;
+    azArg[3] = temp_master_schema;
+    azArg[4] = "1";
+    sqliteInitCallback(&initData, 5, azArg, 0);
+    pTab = sqliteFindTable(db, TEMP_MASTER_NAME, "temp");
+    if( pTab ){
+      pTab->readOnly = 1;
+    }
   }
 
   /* Create a cursor to hold the database open
   */
-  if( db->aDb[0].pBt==0 ) return SQLITE_OK;
-  rc = sqliteBtreeCursor(db->aDb[0].pBt, 2, 0, &curMain);
+  if( db->aDb[iDb].pBt==0 ) return SQLITE_OK;
+  rc = sqliteBtreeCursor(db->aDb[iDb].pBt, 2, 0, &curMain);
   if( rc ){
     sqliteSetString(pzErrMsg, sqlite_error_string(rc), 0);
-    sqliteResetInternalSchema(db);
     return rc;
   }
 
   /* Get the database meta information
   */
-  rc = sqliteBtreeGetMeta(db->aDb[0].pBt, meta);
+  rc = sqliteBtreeGetMeta(db->aDb[iDb].pBt, meta);
   if( rc ){
     sqliteSetString(pzErrMsg, sqlite_error_string(rc), 0);
-    sqliteResetInternalSchema(db);
     sqliteBtreeCloseCursor(curMain);
     return rc;
   }
-  db->next_cookie = db->aDb[0].schema_cookie = meta[1];
-  db->file_format = meta[2];
-  size = meta[3];
-  if( size==0 ){ size = MAX_PAGES; }
-  db->cache_size = size;
-  sqliteBtreeSetCacheSize(db->aDb[0].pBt, size);
-  db->safety_level = meta[4];
-  if( db->safety_level==0 ) db->safety_level = 2;
-  sqliteBtreeSetSafetyLevel(db->aDb[0].pBt, db->safety_level);
+  db->aDb[iDb].schema_cookie = meta[1];
+  if( iDb==0 ){
+    db->next_cookie = meta[1];
+    db->file_format = meta[2];
+    size = meta[3];
+    if( size==0 ){ size = MAX_PAGES; }
+    db->cache_size = size;
+    db->safety_level = meta[4];
+    if( db->safety_level==0 ) db->safety_level = 2;
 
-  /*
-  **     file_format==1    Version 2.1.0.
-  **     file_format==2    Version 2.2.0. Add support for INTEGER PRIMARY KEY.
-  **     file_format==3    Version 2.6.0. Fix empty-string index bug.
-  **     file_format==4    Version 2.7.0. Add support for separate numeric and
-  **                       text datatypes.
-  */
-  if( db->file_format==0 ){
-    /* This happens if the database was initially empty */
-    db->file_format = 4;
-  }else if( db->file_format>4 ){
-    sqliteBtreeCloseCursor(curMain);
-    sqliteSetString(pzErrMsg, "unsupported file format", 0);
-    return SQLITE_ERROR;
+    /*
+    **  file_format==1    Version 2.1.0.
+    **  file_format==2    Version 2.2.0. Add support for INTEGER PRIMARY KEY.
+    **  file_format==3    Version 2.6.0. Fix empty-string index bug.
+    **  file_format==4    Version 2.7.0. Add support for separate numeric and
+    **                    text datatypes.
+    */
+    if( db->file_format==0 ){
+      /* This happens if the database was initially empty */
+      db->file_format = 4;
+    }else if( db->file_format>4 ){
+      sqliteBtreeCloseCursor(curMain);
+      sqliteSetString(pzErrMsg, "unsupported file format", 0);
+      return SQLITE_ERROR;
+    }
+  }else if( db->file_format<4 || db->file_format!=meta[2] ){
+    sqliteSetString(pzErrMsg, "incompatible file format in auxiliary "
+       "database \"", db->aDb[iDb].zName, "\"", 0);
+    sqliteBtreeClose(db->aDb[iDb].pBt);
+    db->aDb[iDb].pBt = 0;
+    return SQLITE_FORMAT;
   }
+  sqliteBtreeSetCacheSize(db->aDb[iDb].pBt, size);
+  sqliteBtreeSetSafetyLevel(db->aDb[iDb].pBt, meta[4]==0 ? 2 : meta[4]);
 
   /* Read the schema information out of the schema tables
   */
@@ -305,24 +315,62 @@
   sParse.pArg = (void*)&initData;
   sParse.initFlag = 1;
   sParse.useCallback = 1;
-  sqliteRunParser(&sParse,
-      db->file_format>=2 ? init_script : older_init_script,
-      pzErrMsg);
+  if( iDb==0 ){
+    sqliteRunParser(&sParse,
+        db->file_format>=2 ? init_script : older_init_script,
+        pzErrMsg);
+  }else{
+    char *zSql = 0;
+    sqliteSetString(&zSql, 
+       "SELECT type, name, rootpage, sql, ", zDbNum, " FROM \"",
+       db->aDb[iDb].zName, "\".sqlite_master", 0);
+    sqliteRunParser(&sParse, zSql, pzErrMsg);
+    sqliteFree(zSql);
+  }
+  sqliteBtreeCloseCursor(curMain);
   if( sqlite_malloc_failed ){
     sqliteSetString(pzErrMsg, "out of memory", 0);
     sParse.rc = SQLITE_NOMEM;
-    sqliteBtreeRollback(db->aDb[0].pBt);
-    sqliteResetInternalSchema(db);
+    sqliteResetInternalSchema(db, 0);
   }
   if( sParse.rc==SQLITE_OK ){
+    db->aDb[iDb].flags |= SQLITE_Initialized;
+  }else{
+    sqliteResetInternalSchema(db, iDb);
+  }
+  return sParse.rc;
+}
+
+/*
+** Initialize all database files - the main database file, the file
+** used to store temporary tables, and any additional database files
+** created using ATTACH statements.  Return a success code.  If an
+** error occurs, write an error message into *pzErrMsg.
+**
+** After the database is initialized, the SQLITE_Initialized
+** bit is set in the flags field of the sqlite structure.  An
+** attempt is made to initialize the database as soon as it
+** is opened.  If that fails (perhaps because another process
+** has the sqlite_master table locked) than another attempt
+** is made the first time the database is accessed.
+*/
+int sqliteInit(sqlite *db, char **pzErrMsg){
+  int i, rc;
+  
+  assert( (db->flags & SQLITE_Initialized)==0 );
+  rc = SQLITE_OK;
+  for(i=0; rc==SQLITE_OK && i<db->nDb; i++){
+    if( db->aDb[i].flags & SQLITE_Initialized ) continue;
+    if( i==1 ) continue;  /* Skip the temp database - initialized with 0 */
+    rc = sqliteInitOne(db, i, pzErrMsg);
+  }
+  if( rc==SQLITE_OK ){
     db->flags |= SQLITE_Initialized;
     sqliteCommitInternalChanges(db);
   }else{
     db->flags &= ~SQLITE_Initialized;
-    sqliteResetInternalSchema(db);
   }
-  sqliteBtreeCloseCursor(curMain);
-  return sParse.rc;
+  return rc;
 }
 
 /*
@@ -476,15 +524,16 @@
   for(j=0; j<db->nDb; j++){
     if( db->aDb[j].pBt ){
       sqliteBtreeClose(db->aDb[j].pBt);
+      db->aDb[j].pBt = 0;
     }
     if( j>=2 ){
       sqliteFree(db->aDb[j].zName);
+      db->aDb[j].zName = 0;
     }
   }
-  if( db->aDb!=db->aDbStatic ){
-    sqliteFree(db->aDb);
-  }
-  sqliteResetInternalSchema(db);
+  sqliteResetInternalSchema(db, 0);
+  assert( db->nDb<=2 );
+  assert( db->aDb==db->aDbStatic );
   for(i=sqliteHashFirst(&db->aFunc); i; i=sqliteHashNext(i)){
     FuncDef *pFunc, *pNext;
     for(pFunc = (FuncDef*)sqliteHashData(i); pFunc; pFunc=pNext){
@@ -673,7 +722,7 @@
     sqliteSetString(pzErrMsg, "out of memory", 0);
     sParse.rc = SQLITE_NOMEM;
     sqliteRollbackAll(db);
-    sqliteResetInternalSchema(db);
+    sqliteResetInternalSchema(db, 0);
     db->flags &= ~SQLITE_InTrans;
   }
   if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK;
@@ -682,7 +731,7 @@
   }
   sqliteStrRealloc(pzErrMsg);
   if( sParse.rc==SQLITE_SCHEMA ){
-    sqliteResetInternalSchema(db);
+    sqliteResetInternalSchema(db, 0);
   }
   if( sParse.useCallback==0 ){
     assert( ppVm );
diff --git a/src/parse.y b/src/parse.y
index e1feb99..605cc6c 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.93 2003/03/27 12:51:25 drh Exp $
+** @(#) $Id: parse.y,v 1.94 2003/03/31 00:30:48 drh Exp $
 */
 %token_prefix TK_
 %token_type {Token}
@@ -841,14 +841,18 @@
 
 ////////////////////////  DROP TRIGGER statement //////////////////////////////
 cmd ::= DROP TRIGGER nm(X) dbnm(D). {
-    sqliteDropTrigger(pParse,sqliteSrcListAppend(0,&X,&D),0);
+  sqliteDropTrigger(pParse,sqliteSrcListAppend(0,&X,&D),0);
 }
 
 //////////////////////// ATTACH DATABASE file AS name /////////////////////////
-cmd ::= ATTACH database_kw_opt ids AS nm.
+cmd ::= ATTACH database_kw_opt ids(F) AS nm(D). {
+  sqliteAttach(pParse, &F, &D);
+}
 
 database_kw_opt ::= DATABASE.
 database_kw_opt ::= .
 
 //////////////////////// DETACH DATABASE name /////////////////////////////////
-cmd ::= DETACH database_kw_opt nm.
+cmd ::= DETACH database_kw_opt nm(D). {
+  sqliteDetach(pParse, &D);
+}
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 552f351..7b224ea 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -12,7 +12,7 @@
 ** This header file defines the interface that the SQLite library
 ** presents to client programs.
 **
-** @(#) $Id: sqlite.h.in,v 1.42 2003/03/30 19:17:03 drh Exp $
+** @(#) $Id: sqlite.h.in,v 1.43 2003/03/31 00:30:49 drh Exp $
 */
 #ifndef _SQLITE_H_
 #define _SQLITE_H_
@@ -165,6 +165,7 @@
 #define SQLITE_MISUSE      21   /* Library used incorrectly */
 #define SQLITE_NOLFS       22   /* Uses OS features not supported on host */
 #define SQLITE_AUTH        23   /* Authorization denied */
+#define SQLITE_FORMAT      24   /* Auxiliary database format error */
 #define SQLITE_ROW         100  /* sqlite_step() has another row ready */
 #define SQLITE_DONE        101  /* sqlite_step() has finished executing */
 
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index dcc325f..bffef93 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.166 2003/03/27 13:50:00 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.167 2003/03/31 00:30:49 drh Exp $
 */
 #include "config.h"
 #include "sqlite.h"
@@ -971,9 +971,9 @@
 void sqliteExprDelete(Expr*);
 ExprList *sqliteExprListAppend(ExprList*,Expr*,Token*);
 void sqliteExprListDelete(ExprList*);
-void sqlitePragma(Parse*,Token*,Token*,int);
-void sqliteResetInternalSchema(sqlite*);
 int sqliteInit(sqlite*, char**);
+void sqlitePragma(Parse*,Token*,Token*,int);
+void sqliteResetInternalSchema(sqlite*, int);
 void sqliteBeginParse(Parse*,int);
 void sqliteRollbackInternalChanges(sqlite*);
 void sqliteCommitInternalChanges(sqlite*);
@@ -1082,3 +1082,5 @@
 # define sqliteAuthRead(a,b,c,d)
 # define sqliteAuthCheck(a,b,c,d)    SQLITE_OK
 #endif
+void sqliteAttach(Parse*, Token*, Token*);
+void sqliteDetach(Parse*, Token*);