Index: ext/rbu/rbuvacuum.test ================================================================== --- ext/rbu/rbuvacuum.test +++ ext/rbu/rbuvacuum.test @@ -15,19 +15,19 @@ # source [file join [file dirname [info script]] rbu_common.tcl] set ::testprefix rbuvacuum -proc do_rbu_vacuum_test {tn} { +proc do_rbu_vacuum_test {tn step} { uplevel [list do_test $tn.1 { forcedelete state.db - if {$::step==0} { sqlite3rbu_vacuum rbu test.db state.db } + if {$step==0} { sqlite3rbu_vacuum rbu test.db state.db } while 1 { - if {$::step==1} { sqlite3rbu_vacuum rbu test.db state.db } + if {$step==1} { sqlite3rbu_vacuum rbu test.db state.db } set rc [rbu step] if {$rc!="SQLITE_OK"} break - if {$::step==1} { rbu close } + if {$step==1} { rbu close } } rbu close } {SQLITE_DONE}] uplevel [list do_execsql_test $tn.2 { @@ -47,11 +47,11 @@ INSERT INTO t1 VALUES(1, 2, 3); INSERT INTO t1 VALUES(4, 5, 6); INSERT INTO t1 VALUES(7, 8, 9); PRAGMA integrity_check; } {ok} - do_rbu_vacuum_test 1.1 + do_rbu_vacuum_test 1.1 $step # A vacuum that actually reclaims space. do_execsql_test 1.2.1 { INSERT INTO t1 VALUES(8, randomblob(900), randomblob(900)); INSERT INTO t1 VALUES(9, randomblob(900), randomblob(900)); @@ -62,11 +62,11 @@ } {12} do_execsql_test 1.2.2 { DELETE FROM t1 WHERE rowid BETWEEN 8 AND 11; PRAGMA page_count; } {12} - do_rbu_vacuum_test 1.2.3 + do_rbu_vacuum_test 1.2.3 $step do_execsql_test 1.2.4 { PRAGMA page_count; } {3} # Add an index to the table. @@ -80,11 +80,11 @@ } {18} do_execsql_test 1.3.2 { DELETE FROM t1 WHERE rowid BETWEEN 12 AND 15; PRAGMA page_count; } {18} - do_rbu_vacuum_test 1.3.3 + do_rbu_vacuum_test 1.3.3 $step do_execsql_test 1.3.4 { PRAGMA page_count; } {5} # WITHOUT ROWID table. @@ -100,11 +100,11 @@ INSERT INTO t2 VALUES(randomblob(900), 8, randomblob(900)); DELETE FROM t2 WHERE b BETWEEN 2 AND 7; PRAGMA page_count; } {20} - do_rbu_vacuum_test 1.4.2 + do_rbu_vacuum_test 1.4.2 $step do_execsql_test 1.4.3 { PRAGMA page_count; } {10} # WITHOUT ROWID table with an index. @@ -118,11 +118,11 @@ INSERT INTO t2 VALUES(randomblob(900), 13, randomblob(900)); DELETE FROM t2 WHERE b BETWEEN 8 AND 12; PRAGMA page_count; } {35} - do_rbu_vacuum_test 1.4.2 + do_rbu_vacuum_test 1.4.2 $step do_execsql_test 1.4.3 { PRAGMA page_count; } {15} do_execsql_test 1.4.4 { VACUUM; @@ -133,11 +133,11 @@ CREATE TABLE t3(a, b, c); INSERT INTO t3 VALUES('a', 'b', 'c'); INSERT INTO t3 VALUES('d', 'e', 'f'); INSERT INTO t3 VALUES('g', 'h', 'i'); } - do_rbu_vacuum_test 1.5.2 + do_rbu_vacuum_test 1.5.2 $step do_execsql_test 1.5.3 { SELECT * FROM t3 } {a b c d e f g h i} do_execsql_test 1.5.4 { CREATE INDEX t3a ON t3(a); @@ -144,11 +144,11 @@ CREATE INDEX t3b ON t3(b); CREATE INDEX t3c ON t3(c); INSERT INTO t3 VALUES('j', 'k', 'l'); DELETE FROM t3 WHERE a = 'g'; } - do_rbu_vacuum_test 1.5.5 + do_rbu_vacuum_test 1.5.5 $step do_execsql_test 1.5.6 { SELECT rowid, * FROM t3 ORDER BY b } {1 a b c 2 d e f 4 j k l} do_execsql_test 1.6.1 { @@ -155,11 +155,11 @@ CREATE TABLE t4(a PRIMARY KEY, b, c); INSERT INTO t4 VALUES('a', 'b', 'c'); INSERT INTO t4 VALUES('d', 'e', 'f'); INSERT INTO t4 VALUES('g', 'h', 'i'); } - do_rbu_vacuum_test 1.6.2 + do_rbu_vacuum_test 1.6.2 $step do_execsql_test 1.6.3 { SELECT * FROM t4 } {a b c d e f g h i} do_execsql_test 1.6.4 { CREATE INDEX t4a ON t4(a); @@ -167,15 +167,71 @@ CREATE INDEX t4c ON t4(c); INSERT INTO t4 VALUES('j', 'k', 'l'); DELETE FROM t4 WHERE a='g'; } - do_rbu_vacuum_test 1.6.5 + do_rbu_vacuum_test 1.6.5 $step do_execsql_test 1.6.6 { SELECT * FROM t4 ORDER BY b } {a b c d e f j k l} +} + +#------------------------------------------------------------------------- +# Test some error cases: +# +# 2.1.* the db being vacuumed being in wal mode already. +# 2.2.* database modified mid vacuum. +# +reset_db +do_execsql_test 2.1.0 { + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + INSERT INTO t1 VALUES(5, 6); + INSERT INTO t1 VALUES(7, 8); + PRAGMA journal_mode = wal; + INSERT INTO t1 VALUES(9, 10); +} wal +do_test 2.1.1 { + forcedelete state.db + sqlite3rbu_vacuum rbu test.db state.db + rbu step +} {SQLITE_ERROR} +do_test 2.1.2 { + list [catch { rbu close } msg] $msg +} {1 {SQLITE_ERROR - cannot vacuum wal mode database}} + +reset_db +do_execsql_test 2.2.0 { + CREATE TABLE tx(a PRIMARY KEY, b BLOB); + INSERT INTO tx VALUES(1, randomblob(900)); + INSERT INTO tx SELECT a+1, randomblob(900) FROM tx; + INSERT INTO tx SELECT a+2, randomblob(900) FROM tx; + INSERT INTO tx SELECT a+4, randomblob(900) FROM tx; + INSERT INTO tx SELECT a+8, randomblob(900) FROM tx; +} +db_save_and_close +for {set i 1} 1 {incr i} { + forcedelete state.db + db_restore_and_reopen + + sqlite3rbu_vacuum rbu test.db state.db + for {set step 0} {$step<$i} {incr step} { rbu step } + rbu close + if {[file exists test.db-wal]} break + + execsql { INSERT INTO tx VALUES(20, 20) } + + do_test 2.2.$i.1 { + sqlite3rbu_vacuum rbu test.db state.db + rbu step + } {SQLITE_BUSY} + do_test 2.2.$i.2 { + list [catch { rbu close } msg] $msg + } {1 {SQLITE_BUSY - database modified during rbu vacuum}} + } catch { db close } finish_test Index: ext/rbu/sqlite3rbu.c ================================================================== --- ext/rbu/sqlite3rbu.c +++ ext/rbu/sqlite3rbu.c @@ -397,11 +397,11 @@ sqlite3rbu *pRbu; /* Pointer to rbu object (rbu target only) */ int openFlags; /* Flags this file was opened with */ u32 iCookie; /* Cookie value for main db files */ u8 iWriteVer; /* "write-version" value for main db files */ - u8 bNolock; + u8 bNolock; /* True to fail EXCLUSIVE locks */ int nShm; /* Number of entries in apShm[] array */ char **apShm; /* Array of mmap'd *-shm regions */ char *zDel; /* Delete this when closing file */ @@ -2371,13 +2371,16 @@ } if( bOpen ) p->dbMain = rbuOpenDbhandle(p, p->zRbu, p->nRbu<=1); } p->eStage = 0; - if( p->dbMain==0 ){ + if( p->rc==SQLITE_OK && p->dbMain==0 ){ if( !rbuIsVacuum(p) ){ p->dbMain = rbuOpenDbhandle(p, p->zTarget, 1); + }else if( p->pRbuFd->pWalFd ){ + p->rc = SQLITE_ERROR; + p->zErrmsg = sqlite3_mprintf("cannot vacuum wal mode database"); }else{ char *zTarget = sqlite3_mprintf("file:%s-vacuum?rbu_memory=1", p->zRbu); if( zTarget==0 ){ p->rc = SQLITE_NOMEM; return; @@ -2985,10 +2988,11 @@ ** are determined by inspecting the rbu handle passed as the first argument. */ static void rbuSaveState(sqlite3rbu *p, int eStage){ if( p->rc==SQLITE_OK || p->rc==SQLITE_DONE ){ sqlite3_stmt *pInsert = 0; + rbu_file *pFd = (rbuIsVacuum(p) ? p->pRbuFd : p->pTargetFd); int rc; assert( p->zErrmsg==0 ); rc = prepareFreeAndCollectError(p->dbRbu, &pInsert, &p->zErrmsg, sqlite3_mprintf( @@ -3007,11 +3011,11 @@ RBU_STATE_TBL, p->objiter.zTbl, RBU_STATE_IDX, p->objiter.zIdx, RBU_STATE_ROW, p->nStep, RBU_STATE_PROGRESS, p->nProgress, RBU_STATE_CKPT, p->iWalCksum, - RBU_STATE_COOKIE, (i64)p->pTargetFd->iCookie, + RBU_STATE_COOKIE, (i64)pFd->iCookie, RBU_STATE_OALSZ, p->iOalSz, RBU_STATE_PHASEONESTEP, p->nPhaseOneStep ) ); assert( pInsert==0 || rc==SQLITE_OK ); @@ -3429,21 +3433,25 @@ p->eStage = RBU_STAGE_CKPT; p->nStep = 0; } } - if( p->rc==SQLITE_OK - && !rbuIsVacuum(p) + if( p->rc==SQLITE_OK && (p->eStage==RBU_STAGE_OAL || p->eStage==RBU_STAGE_MOVE) - && pState->eStage!=0 && p->pTargetFd->iCookie!=pState->iCookie - ){ - /* At this point (pTargetFd->iCookie) contains the value of the - ** change-counter cookie (the thing that gets incremented when a - ** transaction is committed in rollback mode) currently stored on - ** page 1 of the database file. */ - p->rc = SQLITE_BUSY; - p->zErrmsg = sqlite3_mprintf("database modified during rbu update"); + && pState->eStage!=0 + ){ + rbu_file *pFd = (rbuIsVacuum(p) ? p->pRbuFd : p->pTargetFd); + if( pFd->iCookie!=pState->iCookie ){ + /* At this point (pTargetFd->iCookie) contains the value of the + ** change-counter cookie (the thing that gets incremented when a + ** transaction is committed in rollback mode) currently stored on + ** page 1 of the database file. */ + p->rc = SQLITE_BUSY; + p->zErrmsg = sqlite3_mprintf("database modified during rbu %s", + (rbuIsVacuum(p) ? "vacuum" : "update") + ); + } } if( p->rc==SQLITE_OK ){ if( p->eStage==RBU_STAGE_OAL ){ sqlite3 *db = p->dbMain; @@ -3839,10 +3847,11 @@ u8 *aBuf = (u8*)zBuf; rbuPutU32(&aBuf[52], 0); /* largest root page number */ rbuPutU32(&aBuf[36], 0); /* number of free pages */ rbuPutU32(&aBuf[32], 0); /* first page on free list trunk */ rbuPutU32(&aBuf[28], 1); /* size of db file in pages */ + rbuPutU32(&aBuf[24], pRbu->pRbuFd->iCookie+1); /* Change counter */ if( iAmt>100 ){ assert( iAmt>=101 ); memset(&aBuf[101], 0, iAmt-101); rbuPutU16(&aBuf[105], iAmt & 0xFFFF); @@ -3855,17 +3864,10 @@ /* These look like magic numbers. But they are stable, as they are part ** of the definition of the SQLite file format, which may not change. */ u8 *pBuf = (u8*)zBuf; p->iCookie = rbuGetU32(&pBuf[24]); p->iWriteVer = pBuf[19]; - if( pRbu && rbuIsVacuum(p->pRbu) ){ - rbu_file *pRbuFd = 0; - sqlite3_file_control(pRbu->dbRbu, "main", - SQLITE_FCNTL_FILE_POINTER, (void*)&pRbuFd - ); - rbuPutU32(&pBuf[24], pRbuFd->iCookie+1); - } } } return rc; }