Make VACUUM work even if multiple processes have the database open at once. (CVS 933)

FossilOrigin-Name: caa960289f3d1f5e8f35a94e9e4321996c211ed2
This commit is contained in:
drh 2003-04-25 13:22:51 +00:00
parent fd9903de51
commit f7c5753174
10 changed files with 143 additions and 131 deletions

View File

@ -1,5 +1,5 @@
C Explicit\scasts\sto\ssquelch\sbogus\swarnings\sfor\svc++.\s\sTicket\s#194.\s(CVS\s932)
D 2003-04-25T03:13:25
C Make\sVACUUM\swork\seven\sif\smultiple\sprocesses\shave\sthe\sdatabase\sopen\sat\sonce.\s(CVS\s933)
D 2003-04-25T13:22:52
F Makefile.in 004acec253ecdde985c8ecd5b7c9accdb210378f
F Makefile.linux-gcc b86a99c493a5bfb402d1d9178dcdc4bd4b32f906
F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd
@ -21,9 +21,9 @@ F sqlite.1 83f4a9d37bdf2b7ef079a82d54eaf2e3509ee6ea
F sqlite.pc.in 30552343140c53304c2a658c080fbe810cd09ca2
F src/attach.c 7ebc7487de43e357a64226f8abef81f2669f2183
F src/auth.c a4afd27964fb9f661147115790c8ae2ee230ebcc
F src/btree.c 3de765f186a5d07d8764f970ecd07d83ccfdc51d
F src/btree.h dc899dd3a10ec9a0c9b51308610d2f53e36b4820
F src/btree_rb.c b427e6f2df7807cd338636259efadb4e43dba669
F src/btree.c a9e8aa96c2af6a81e442719d0cbc1a71bb9b3e08
F src/btree.h 6488448db856bb92729b1325b932864c9aa163f5
F src/btree_rb.c 8e00e40be264716e1929987878672e55d9e0e76d
F src/build.c d5a26baeffa5bc49b4b7009a7723c6ab7e1b02d9
F src/copy.c 44b13fd4d2444fb53bff8a5ecee1c5f6f86a8263
F src/delete.c 23d33fd8967c6cc492943bbecea93be6491edc6a
@ -35,10 +35,10 @@ F src/hash.h cd0433998bc1a3759d244e1637fe5a3c13b53bf8
F src/insert.c 19882be1edc4b1629b8f3097e2615164f2c9cecb
F src/main.c 5e4d4d081d82840a743c57269ca3c32640cefc06
F src/md5.c fe4f9c9c6f71dfc26af8da63e4d04489b1430565
F src/os.c 7274951ed6894f383cb889342267ded07caf339b
F src/os.c e56853eaea5dab258ab1ccb77b4743b453516e3a
F src/os.h 9e5bbddff123187295e3d00d49af06192cd1cd49
F src/pager.c df4c81350cbd80c1ab48341ae0768ba78d99ad49
F src/pager.h e3702f7d384921f6cd5ce0b3ed589185433e9f6c
F src/pager.c 18066f5057500dccc2948c258ea5ffd4139bbf7d
F src/pager.h 5da62c83443f26b1792cfd72c96c422f91aadd31
F src/parse.y 15ae47e7dd84304c1c6ae9205558405127977541
F src/pragma.c 118fe400d71b7fdcc03580d5eab6bb5aa00772a5
F src/printf.c fc5fdef6e92ad205005263661fe9716f55a49f3e
@ -58,7 +58,7 @@ F src/tokenize.c 067d1a477a94af7712ca74e09aaa6bd0f7299527
F src/trigger.c e763f4015c96e06b694184ead5754985c1dfdae0
F src/update.c b7fa7c427b74aee6db56ecfa09e5e151e6f9fa6a
F src/util.c 87635cfdfffa056a8d3147719357aa442374f78c
F src/vacuum.c 67199f3d626aed21940b3b428c25979a98fda03d
F src/vacuum.c 44420de0f02cc66a673469fee1f33b6d08bb717e
F src/vdbe.c f0868ac926d98395d28c2a29119364ff11b77852
F src/vdbe.h 985c24f312d10f9ef8f9a8b8ea62fcdf68e82f21
F src/where.c f632cd30f013163484a4d60c249d36fe31f5be12
@ -125,7 +125,7 @@ F test/trigger3.test 870afef7997a5b86bf3ea893ce0c2e85d6356c72
F test/trigger4.test 9a5c1406344d743020c2753ae8d6dfe6eb75f818
F test/unique.test 22a46df72a3e0a3fd1a2d39e96fb59f18448dd5f
F test/update.test 198360dfa14e65354dbcc66d5b98d8070780e42b
F test/vacuum.test baf8e0c44587322da5669996ffc55daf8cc60266
F test/vacuum.test 9262504c37e9d796e3d8d6c1730e878c8fa8787a
F test/version.test 605fd0d7e7d571370c32b12dbf395b58953de246
F test/view.test d356f445d481c04ffa6036a4c61cb8ba70289f69
F test/where.test d719129a052280fe245a2ddcbd09bcc0b8c17ce4
@ -165,7 +165,7 @@ F www/speed.tcl cb4c10a722614aea76d2c51f32ee43400d5951be
F www/sqlite.tcl ae3dcfb077e53833b59d4fcc94d8a12c50a44098
F www/tclsqlite.tcl 1db15abeb446aad0caf0b95b8b9579720e4ea331
F www/vdbe.tcl 2013852c27a02a091d39a766bc87cff329f21218
P 5afb88008fed253e6d1fc0ed5172370b61d3727b
R f2421cf3a297d027e70ffbccdb3ba489
P cb808c14bc14e7bd1cfff134ae5206ace451f2df
R 304a2370652d1a06037c5b9e50c2e06c
U drh
Z 38db8e55b84708279a57f38314d421c3
Z a3c697bfebd34cfeb9979168f63ba185

View File

@ -1 +1 @@
cb808c14bc14e7bd1cfff134ae5206ace451f2df
caa960289f3d1f5e8f35a94e9e4321996c211ed2

View File

@ -9,7 +9,7 @@
** May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree.c,v 1.90 2003/04/25 03:13:25 drh Exp $
** $Id: btree.c,v 1.91 2003/04/25 13:22:52 drh Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
@ -3490,10 +3490,34 @@ static const char *fileBtreeGetFilename(Btree *pBt){
}
/*
** Change the name of the underlying database file.
** Copy the complete content of pBtFrom into pBtTo. A transaction
** must be active for both files.
**
** The size of file pBtFrom may be reduced by this operation.
** If anything goes wrong, the transaction on pBtFrom is rolled back.
*/
static int fileBtreeChangeFilename(Btree *pBt, const char *zNew){
return sqlitepager_rename(pBt->pPager, zNew);
static int fileBtreeCopyFile(Btree *pBtTo, Btree *pBtFrom){
int rc = SQLITE_OK;
Pgno i, nPage;
if( !pBtTo->inTrans || !pBtFrom->inTrans ) return SQLITE_ERROR;
if( pBtTo->needSwab!=pBtFrom->needSwab ) return SQLITE_ERROR;
if( pBtTo->pCursor ) return SQLITE_BUSY;
memcpy(pBtTo->page1, pBtFrom->page1, SQLITE_PAGE_SIZE);
sqlitepager_overwrite(pBtTo->pPager, 1, pBtFrom->page1);
nPage = sqlitepager_pagecount(pBtFrom->pPager);
for(i=2; i<=nPage; i++){
void *pPage;
rc = sqlitepager_get(pBtFrom->pPager, i, &pPage);
if( rc ) break;
sqlitepager_overwrite(pBtTo->pPager, i, pPage);
sqlitepager_unref(pPage);
}
if( !rc ) rc = sqlitepager_truncate(pBtTo->pPager, nPage);
if( rc ){
fileBtreeRollback(pBtTo);
}
return rc;
}
/*
@ -3521,7 +3545,7 @@ static BtOps sqliteBtreeOps = {
fileBtreeUpdateMeta,
fileBtreeIntegrityCheck,
fileBtreeGetFilename,
fileBtreeChangeFilename,
fileBtreeCopyFile,
#ifdef SQLITE_TEST
fileBtreePageDump,
fileBtreePager

View File

@ -13,7 +13,7 @@
** subsystem. See comments in the source code for a detailed description
** of what each interface routine does.
**
** @(#) $Id: btree.h,v 1.32 2003/04/25 02:43:08 drh Exp $
** @(#) $Id: btree.h,v 1.33 2003/04/25 13:22:53 drh Exp $
*/
#ifndef _BTREE_H_
#define _BTREE_H_
@ -56,7 +56,7 @@ struct BtOps {
int (*UpdateMeta)(Btree*, int*);
char *(*IntegrityCheck)(Btree*, int*, int);
const char *(*GetFilename)(Btree*);
int (*ChangeFilename)(Btree*, const char *zNew);
int (*CopyFile)(Btree*,Btree*);
#ifdef SQLITE_TEST
int (*PageDump)(Btree*, int, int);
struct Pager *(*Pager)(Btree*);
@ -141,8 +141,7 @@ int sqliteRBtreeOpen(const char *zFilename, int mode, int nPg, Btree **ppBtree);
#define sqliteBtreeIntegrityCheck(pBt, aRoot, nRoot)\
(btOps(pBt)->IntegrityCheck(pBt, aRoot, nRoot))
#define sqliteBtreeGetFilename(pBt) (btOps(pBt)->GetFilename(pBt))
#define sqliteBtreeChangeFilename(pBt, zNew)\
(btOps(pBt)->ChangeFilename(pBt, zNew))
#define sqliteBtreeCopyFile(pBt1, pBt2) (btOps(pBt1)->CopyFile(pBt1, pBt2))
#ifdef SQLITE_TEST
#define sqliteBtreePageDump(pBt, pgno, recursive)\

View File

@ -9,7 +9,7 @@
** May you share freely, never taking more than you give.
**
*************************************************************************
** $Id: btree_rb.c,v 1.8 2003/04/25 02:43:08 drh Exp $
** $Id: btree_rb.c,v 1.9 2003/04/25 13:22:53 drh Exp $
**
** This file implements an in-core database using Red-Black balanced
** binary trees.
@ -1331,10 +1331,10 @@ static const char *memBtreeGetFilename(Btree *pBt){
}
/*
** Change the name of the underlying database file.
** The copy file function is not implemented for the in-memory database
*/
static int memBtreeChangeFilename(Btree *pBt, const char *zNew){
return SQLITE_OK;
static int memBtreeCopyFile(Btree *pBt, Btree *pBt2){
return SQLITE_INTERNAL; /* Not implemented */
}
static BtOps sqliteBtreeOps = {
@ -1356,7 +1356,7 @@ static BtOps sqliteBtreeOps = {
memBtreeUpdateMeta,
memBtreeIntegrityCheck,
memBtreeGetFilename,
memBtreeChangeFilename,
memBtreeCopyFile,
#ifdef SQLITE_TEST
memBtreePageDump,

View File

@ -282,6 +282,7 @@ int sqliteOsFileExists(const char *zFilename){
}
#if 0 /* NOT USED */
/*
** Change the name of an existing file.
*/
@ -304,7 +305,7 @@ int sqliteOsFileRename(const char *zOldName, const char *zNewName){
return SQLITE_ERROR;
#endif
}
#endif /* NOT USED */
/*
** Attempt to open a file for both reading and writing. If that

View File

@ -18,7 +18,7 @@
** file simultaneously, or one process from reading the database while
** another is writing.
**
** @(#) $Id: pager.c,v 1.81 2003/04/06 20:52:32 drh Exp $
** @(#) $Id: pager.c,v 1.82 2003/04/25 13:22:53 drh Exp $
*/
#include "os.h" /* Must be first to enable large file support */
#include "sqliteInt.h"
@ -927,6 +927,28 @@ int sqlitepager_pagecount(Pager *pPager){
return n;
}
/*
** Forward declaration
*/
static int syncAllPages(Pager*);
/*
** Truncate the file to the number of pages specified.
*/
int sqlitepager_truncate(Pager *pPager, Pgno nPage){
int rc;
if( pPager->dbSize<0 ) sqlitepager_pagecount(pPager);
if( nPage>=pPager->dbSize ){
return SQLITE_OK;
}
syncAllPages(pPager);
rc = sqliteOsTruncate(&pPager->fd, SQLITE_PAGE_SIZE*(off_t)nPage);
if( rc==SQLITE_OK ){
pPager->dbSize = nPage;
}
return rc;
}
/*
** Shutdown the page cache. Free all memory and close all files.
**
@ -2037,43 +2059,6 @@ const char *sqlitepager_filename(Pager *pPager){
return pPager->zFilename;
}
/*
** Rename the database file
*/
int sqlitepager_rename(Pager *pPager, const char *zNewName){
char *zNew;
char *zJournal;
int nName;
int rc;
nName = strlen(zNewName);
zNew = sqliteMalloc( nName*2 + 30 );
if( zNew==0 ){
return SQLITE_NOMEM;
}
memcpy(zNew, zNewName, nName+1);
zJournal = &zNew[nName+1];
memcpy(zJournal, zNew, nName);
strcpy(&zJournal[nName], "-journal");
if( pPager->journalOpen ){
rc = sqliteOsFileRename(pPager->zJournal, zJournal);
if( rc ){
sqliteFree(zNew);
return rc;
}
}
rc = sqliteOsFileRename(pPager->zFilename, zNew);
if( rc ){
sqliteFree(zNew);
return rc;
}
if( pPager->zFilename!=(char*)&pPager[1] ){
sqliteFree(pPager->zFilename);
}
pPager->zFilename = zNew;
return SQLITE_OK;
}
#ifdef SQLITE_TEST
/*
** Print a listing of all referenced pages and their ref count.

View File

@ -13,7 +13,7 @@
** subsystem. The page cache subsystem reads and writes a file a page
** at a time and provides a journal for rollback.
**
** @(#) $Id: pager.h,v 1.22 2003/04/06 20:44:45 drh Exp $
** @(#) $Id: pager.h,v 1.23 2003/04/25 13:22:53 drh Exp $
*/
/*
@ -61,6 +61,7 @@ int sqlitepager_write(void*);
int sqlitepager_iswriteable(void*);
int sqlitepager_overwrite(Pager *pPager, Pgno pgno, void*);
int sqlitepager_pagecount(Pager*);
int sqlitepager_truncate(Pager*,Pgno);
int sqlitepager_begin(void*);
int sqlitepager_commit(Pager*);
int sqlitepager_rollback(Pager*);

View File

@ -14,7 +14,7 @@
** Most of the code in this file may be omitted by defining the
** SQLITE_OMIT_VACUUM macro.
**
** $Id: vacuum.c,v 1.4 2003/04/25 02:43:08 drh Exp $
** $Id: vacuum.c,v 1.5 2003/04/25 13:22:53 drh Exp $
*/
#include "sqliteInt.h"
#include "os.h"
@ -209,14 +209,13 @@ void sqliteVacuum(Parse *pParse, Token *pTableName){
const char *zFilename; /* full pathname of the database file */
int nFilename; /* number of characters in zFilename[] */
char *zTemp = 0; /* a temporary file in same directory as zFilename */
char *zTemp2; /* Another temp file in the same directory */
sqlite *dbNew = 0; /* The new vacuumed database */
sqlite *db; /* The original database */
int rc, i;
char *zErrMsg = 0;
char *zSql = 0;
int safety = 0;
vacuumStruct sVac;
int rc = SQLITE_OK; /* Return code from service routines */
int i; /* Loop counter */
char *zErrMsg = 0; /* Error messages stored here */
int safety = 0; /* TRUE if safety is off */
vacuumStruct sVac; /* Information passed to callbacks */
/* These are all of the pragmas that need to be transferred over
** to the new database */
@ -248,20 +247,16 @@ void sqliteVacuum(Parse *pParse, Token *pTableName){
return;
}
nFilename = strlen(zFilename);
zTemp = sqliteMalloc( 2*(nFilename+40) );
zTemp = sqliteMalloc( nFilename+100 );
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]);
zTemp2[nFilename] = '-';
randomName(&zTemp2[nFilename+1]);
if( !sqliteOsFileExists(zTemp) && !sqliteOsFileExists(zTemp2) ) break;
if( !sqliteOsFileExists(zTemp) ) break;
}
if( i>=10 ){
sqliteErrorMsg(pParse, "unable to create a temporary database files "
sqliteErrorMsg(pParse, "unable to create a temporary database file "
"in the same directory as the original database");
goto end_of_vacuum;
}
@ -279,7 +274,9 @@ void sqliteVacuum(Parse *pParse, Token *pTableName){
}
safety = 1;
if( execsql(pParse, db, "BEGIN") ) goto end_of_vacuum;
if( execsql(pParse, dbNew, "BEGIN") ) goto end_of_vacuum;
if( execsql(pParse, dbNew, "PRAGMA synchronous=off; BEGIN") ){
goto end_of_vacuum;
}
sVac.dbOld = db;
sVac.dbNew = dbNew;
sVac.pParse = pParse;
@ -291,55 +288,33 @@ void sqliteVacuum(Parse *pParse, Token *pTableName){
rc = sqlite_exec(db, zBuf, vacuumCallback3, &sVac, &zErrMsg);
if( rc ) goto vacuum_error;
}
rc = sqlite_exec(db, "SELECT type, name, sql FROM sqlite_master "
"WHERE sql NOT NULL", vacuumCallback1, &sVac, &zErrMsg);
if( rc ) goto vacuum_error;
if( sqliteOsFileRename(zFilename, zTemp2) ){
sqliteErrorMsg(pParse, "unable to rename database file");
if( rc==SQLITE_OK ){
rc = sqlite_exec(db, "SELECT type, name, sql FROM sqlite_master "
"WHERE sql NOT NULL", vacuumCallback1, &sVac, &zErrMsg);
}
if( rc ){
if( pParse->zErrMsg==0 ){
sqliteErrorMsg(pParse, "unable to vacuum database - %s", zErrMsg);
}
goto end_of_vacuum;
}
if( sqliteOsFileRename(zTemp, zFilename) ){
sqliteOsFileRename(zTemp2, zFilename);
sqliteErrorMsg(pParse, "unable to rename database file");
goto end_of_vacuum;
}
if( execsql(pParse, dbNew, "COMMIT;") ){
sqliteOsDelete(zFilename);
sqliteOsFileRename(zTemp2, zFilename);
goto end_of_vacuum;
}
execsql(pParse, db, "COMMIT;"); /* Nothing was written so its gotta work */
sqlite_close(dbNew);
dbNew = 0;
if( sqliteOsDelete(zTemp2) ){
sqliteErrorMsg(pParse, "unable to delete old database: %s", zTemp2);
}
sqliteBtreeClose(db->aDb[0].pBt);
zTemp2[nFilename] = 0;
if( sqliteBtreeOpen(zTemp2, 0, MAX_PAGES, &db->aDb[0].pBt) ){
sqliteErrorMsg(pParse, "unable to reopen database after vacuuming");
}
rc = sqliteBtreeCopyFile(db->aDb[0].pBt, dbNew->aDb[0].pBt);
sqlite_exec(db, "COMMIT", 0, 0, 0);
sqliteResetInternalSchema(db, 0);
end_of_vacuum:
sqlite_exec(db, "COMMIT", 0, 0, 0);
if( safety) {
if( safety ) {
sqliteSafetyOn(db);
}
if( dbNew ) sqlite_close(dbNew);
sqliteOsDelete(zTemp);
sqliteFree(zTemp);
sqliteFree(zSql);
sqliteFree(sVac.s1.z);
sqliteFree(sVac.s2.z);
if( zErrMsg ) sqlite_freemem(zErrMsg);
return;
vacuum_error:
if( pParse->zErrMsg==0 ){
sqliteErrorMsg(pParse, "unable to vacuum database - %s", zErrMsg);
}
goto end_of_vacuum;
#endif
}

View File

@ -11,23 +11,22 @@
# This file implements regression tests for SQLite library. The
# focus of this file is testing the VACUUM statement.
#
# $Id: vacuum.test,v 1.8 2003/04/25 02:43:08 drh Exp $
# $Id: vacuum.test,v 1.9 2003/04/25 13:22:53 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
proc cksum {filename} {
set txt [db eval {SELECT name, type, sql FROM sqlite_master}]\n
foreach tbl [db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
append txt [db eval "SELECT * FROM $tbl"]\n
proc cksum {{db db}} {
set txt [$db eval {SELECT name, type, sql FROM sqlite_master}]\n
foreach tbl [$db eval {SELECT name FROM sqlite_master WHERE type='table'}] {
append txt [$db eval "SELECT * FROM $tbl"]\n
}
foreach prag {default_synchronous default_cache_size} {
append txt $prag-[db eval "PRAGMA $prag"]\n
append txt $prag-[$db eval "PRAGMA $prag"]\n
}
# set fd [open $filename w]
# puts $fd $txt
# close $fd
return [string length $txt]-[md5 $txt]
set cksum [string length $txt]-[md5 $txt]
puts $cksum-[file size test.db]
return $cksum
}
do_test vacuum-1.1 {
@ -49,14 +48,14 @@ do_test vacuum-1.1 {
DROP TABLE t2;
}
set ::size1 [file size test.db]
set ::cksum [cksum vacuum1.txt]
set ::cksum [cksum]
expr {$::cksum!=""}
} {1}
do_test vacuum-1.2 {
execsql {
VACUUM;
}
cksum vacuum2.txt
cksum
} $cksum
do_test vacuum-1.3 {
expr {[file size test.db]<$::size1}
@ -74,14 +73,14 @@ do_test vacuum-1.4 {
DROP TABLE t2;
}
set ::size1 [file size test.db]
set ::cksum [cksum vacuum3.txt]
set ::cksum [cksum]
expr {$::cksum!=""}
} {1}
do_test vacuum-1.5 {
execsql {
VACUUM;
}
cksum vacuum4.txt
cksum
} $cksum
do_test vacuum-1.6 {
expr {[file size test.db]<$::size1}
@ -94,6 +93,34 @@ do_test vacuum-2.1 {
COMMIT;
}
} {1 {cannot VACUUM from within a transaction}}
execsql COMMIT
catch {db eval COMMIT}
do_test vacuum-2.2 {
sqlite db2 test.db
execsql {
BEGIN;
CREATE TABLE t4 AS SELECT * FROM t1;
CREATE TABLE t5 AS SELECT * FROM t1;
COMMIT;
DROP TABLE t4;
DROP TABLE t5;
} db2
set ::cksum [cksum db2]
catchsql {
VACUUM
}
} {1 {database schema has changed}}
do_test vacuum-2.3 {
execsql {
VACUUM;
}
cksum
} $cksum
do_test vacuum-2.4 {
catch {db2 eval {SELECT count(*) FROM sqlite_master}}
cksum db2
} $cksum
catch {db2 close}
# finish_test