/* ** 2003 April 6 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** ************************************************************************* ** This file contains code used to implement the VACUUM command. ** ** Most of the code in this file may be omitted by defining the ** SQLITE_OMIT_VACUUM macro. ** ** $Id: vacuum.c,v 1.2 2003/04/15 01:19:49 drh Exp $ */ #include "sqliteInt.h" #define SQLITE_OMIT_VACUUM 1 /* ** A structure for holding a dynamic string - a string that can grow ** without bound. */ typedef struct dynStr dynStr; struct dynStr { char *z; /* Text of the string in space obtained from sqliteMalloc() */ int nAlloc; /* Amount of space allocated to z[] */ int nUsed; /* Next unused slot in z[] */ }; #ifndef SQLITE_OMIT_VACUUM /* ** Append text to a dynamic string */ static void appendText(dynStr *p, const char *zText, int nText){ if( nText<0 ) nText = strlen(zText); if( p->z==0 || p->nUsed + nText + 1 >= p->nAlloc ){ char *zNew; p->nAlloc = p->nUsed + nText + 1000; zNew = sqliteRealloc(p->z, p->nAlloc); if( zNew==0 ){ sqliteFree(p->z); memset(p, 0, sizeof(*p)); return; } p->z = zNew; } memcpy(&p->z[p->nUsed], zText, nText+1); p->nUsed += nText; } /* ** Append text to a dynamic string, having first put the text in quotes. */ static void appendQuoted(dynStr *p, const char *zText){ int i, j; appendText(p, "'", 1); for(i=j=0; zText[i]; i++){ if( zText[i]='\'' ){ appendText(p, &zText[j], i-j+1); j = i + 1; appendText(p, "'", 1); } } if( jexplain ){ return; } db = pParse->db; if( db->flags & SQLITE_InTrans ){ sqliteErrorMsg(pParse, "cannot VACUUM from within a transaction"); return; } memset(&sStr, 0, sizeof(sStr)); /* Get the full pathname of the database file and create two ** temporary filenames in the same directory as the original file. */ zFilename = sqliteBtreeGetFilename(db->aDb[0].pBt); if( zFilename==0 ){ /* This only happens with the in-memory database. VACUUM is a no-op ** there, so just return */ return; } nFilename = strlen(zFilename); zTemp = sqliteMalloc( 2*(nFilename+40) ); if( zTemp==0 ) return; zTemp2 = &zTemp[nFilename+40]; strcpy(zTemp, zFilename); strcpy(zTemp2, zFilename); for(i=0; i<10; i++){ zTemp[nFilename] = '-'; randomName(&zTemp[nFilename+1]); randomName(&zTemp2[nFilename+1]); if( !sqliteOsFileExists(zTemp) && !sqliteOsFileExists(zTemp2) ) break; } if( i>=10 ){ sqliteErrorMsg(pParse, "unable to create a temporary database files " "in the same directory as the original database"); goto end_of_vacuum; } dbNew = sqlite_open(zTemp, 0, &zErrMsg); if( dbNew==0 ){ sqliteErrorMsg(pParse, "unable to open a temporary database at %s - %s", zTemp, zErrMsg); goto end_of_vacuum; } appendText(&sStr, "ATTACH DATABASE ", -1); appendQuoted(&sStr, zFilename); appendText(&sStr, " AS orig;\nBEGIN;\n", -1); if( execsql(pParse, dbNew, sStr.z) ) goto end_of_vacuum; sStr.nUsed = 0; rc = sqlite_exec(dbNew, "SELECT type, name, sql FROM sqlite_master " "WHERE sql NOT NULL", vacuumCallback, &sStr, &zErrMsg); if( rc ){ sqliteErrorMsg(pParse, "unable to vacuum database - %s", zErrMsg); goto end_of_vacuum; } appendText(&sStr, "COMMIT;\n", -1); if( execsql(pParse, dbNew, sStr.z) ) goto end_of_vacuum; end_of_vacuum: sqliteFree(zTemp); sqliteFree(zSql); sqliteFree(sStr.z); if( zErrMsg ) sqlite_freemem(zErrMsg); if( dbNew ) sqlite_close(dbNew); #endif }