Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -219,10 +219,11 @@ i64 iOffset; /* Starting offset in main journal */ i64 iHdrOffset; /* See above */ Bitvec *pInSavepoint; /* Set of pages in this savepoint */ Pgno nOrig; /* Original number of pages in file */ Pgno iSubRec; /* Index of first record in sub-journal */ + u32 iFrame; /* Last frame in WAL when savepoint opened */ }; /* ** A open page cache is an instance of the following structure. ** @@ -1537,10 +1538,11 @@ assert( isMainJrnl || pDone ); /* pDone always used on sub-journals */ assert( isSavepnt || pDone==0 ); /* pDone never used on non-savepoint */ aData = pPager->pTmpSpace; assert( aData ); /* Temp storage must have already been allocated */ + assert( pagerUseLog(pPager)==0 || (!isMainJrnl && isSavepnt) ); /* Read the page number and page data from the journal or sub-journal ** file. Return an error code to the caller if an IO error occurs. */ jfd = isMainJrnl ? pPager->jfd : pPager->sjfd; @@ -1606,11 +1608,15 @@ ** ** 2008-04-14: When attempting to vacuum a corrupt database file, it ** is possible to fail a statement on a database that does not yet exist. ** Do not attempt to write if database file has never been opened. */ - pPg = pager_lookup(pPager, pgno); + if( pagerUseLog(pPager) ){ + pPg = 0; + }else{ + pPg = pager_lookup(pPager, pgno); + } assert( pPg || !MEMDB ); PAGERTRACE(("PLAYBACK %d page %d hash(%08x) %s\n", PAGERID(pPager), pgno, pager_datahash(pPager->pageSize, (u8*)aData), (isMainJrnl?"main-journal":"sub-journal") )); @@ -1623,10 +1629,11 @@ && isOpen(pPager->fd) && isSynced ){ i64 ofst = (pgno-1)*(i64)pPager->pageSize; testcase( !isSavepnt && pPg!=0 && (pPg->flags&PGHDR_NEED_SYNC)!=0 ); + assert( !pagerUseLog(pPager) ); rc = sqlite3OsWrite(pPager->fd, (u8*)aData, pPager->pageSize, ofst); if( pgno>pPager->dbFileSize ){ pPager->dbFileSize = pgno; } if( pPager->pBackup ){ @@ -1687,10 +1694,11 @@ ** the PGHDR_NEED_SYNC flag will not be set. It could then potentially ** be written out into the database file before its journal file ** segment is synced. If a crash occurs during or following this, ** database corruption may ensue. */ + assert( !pagerUseLog(pPager) ); sqlite3PcacheMakeClean(pPg); } #ifdef SQLITE_CHECK_PAGES pPg->pageHash = pager_pagehash(pPg); #endif @@ -2426,10 +2434,14 @@ ** will be skipped. Out-of-range pages are also skipped. */ if( pSavepoint ){ u32 ii; /* Loop counter */ i64 offset = pSavepoint->iSubRec*(4+pPager->pageSize); + + if( pagerUseLog(pPager) ){ + rc = sqlite3WalSavepointUndo(pPager->pLog, pSavepoint->iFrame); + } for(ii=pSavepoint->iSubRec; rc==SQLITE_OK && iinSubRec; ii++){ assert( offset==ii*(4+pPager->pageSize) ); rc = pager_playback_one_page(pPager, &offset, pDone, 0, 1); } assert( rc!=SQLITE_DONE ); @@ -2437,10 +2449,11 @@ sqlite3BitvecDestroy(pDone); if( rc==SQLITE_OK ){ pPager->journalOff = szJ; } + return rc; } /* ** Change the maximum number of in-memory pages that are allowed. @@ -3324,11 +3337,16 @@ assert( pPg->flags&PGHDR_DIRTY ); pPg->pDirty = 0; if( pagerUseLog(pPager) ){ /* Write a single frame for this page to the log. */ - rc = pagerLogFrames(pPager, pPg, 0, 0, 0); + if( subjRequiresPage(pPg) ){ + rc = subjournalPage(pPg); + } + if( rc==SQLITE_OK ){ + rc = pagerLogFrames(pPager, pPg, 0, 0, 0); + } }else{ /* The doNotSync flag is set by the sqlite3PagerWrite() function while it ** is journalling a set of two or more database pages that are stored ** on the same disk sector. Syncing the journal is not allowed while ** this is happening as it is important that all members of such a @@ -5340,10 +5358,13 @@ aNew[ii].iSubRec = pPager->nSubRec; aNew[ii].pInSavepoint = sqlite3BitvecCreate(nPage); if( !aNew[ii].pInSavepoint ){ return SQLITE_NOMEM; } + if( pagerUseLog(pPager) ){ + aNew[ii].iFrame = sqlite3WalSavepoint(pPager->pLog); + } } /* Open the sub-journal, if it is not already opened. */ rc = openSubJournal(pPager); assertTruncateConstraint(pPager); Index: src/wal.c ================================================================== --- src/wal.c +++ src/wal.c @@ -1666,10 +1666,31 @@ for(iFrame=pLog->hdr.iLastPg+1; iFrame<=iMax && rc==SQLITE_OK; iFrame++){ rc = xUndo(pUndoCtx, pLog->pSummary->aData[logSummaryEntry(iFrame)]); } return rc; } + +u32 sqlite3WalSavepoint(Log *pLog){ + assert( pLog->isWriteLocked ); + return pLog->hdr.iLastPg; +} + +int sqlite3WalSavepointUndo(Log *pLog, u32 iFrame){ + int rc = SQLITE_OK; + u8 aCksum[8]; + assert( pLog->isWriteLocked ); + + pLog->hdr.iLastPg = iFrame; + if( iFrame>0 ){ + i64 iOffset = logFrameOffset(iFrame, pLog->hdr.pgsz) + sizeof(u32)*2; + rc = sqlite3OsRead(pLog->pFd, aCksum, sizeof(aCksum), iOffset); + pLog->hdr.iCheck1 = sqlite3Get4byte(&aCksum[0]); + pLog->hdr.iCheck2 = sqlite3Get4byte(&aCksum[4]); + } + + return rc; +} /* ** Return true if data has been written but not committed to the log file. */ int sqlite3WalDirty(Log *pLog){ @@ -1676,12 +1697,12 @@ assert( pLog->isWriteLocked ); return( pLog->hdr.iLastPg!=((LogSummaryHdr*)pLog->pSummary->aData)->iLastPg ); } /* -** Write a set of frames to the log. The caller must hold at least a -** RESERVED lock on the database file. +** Write a set of frames to the log. The caller must hold the write-lock +** on the log file (obtained using sqlite3WalWriteLock()). */ int sqlite3WalFrames( Log *pLog, /* Log handle to write to */ int nPgsz, /* Database page-size in bytes */ PgHdr *pList, /* List of dirty pages to write */ Index: src/wal.h ================================================================== --- src/wal.h +++ src/wal.h @@ -38,10 +38,13 @@ int sqlite3WalWriteLock(Log *pLog, int op); /* Undo any frames written (but not committed) to the log */ int sqlite3WalUndo(Log *pLog, int (*xUndo)(void *, Pgno), void *pUndoCtx); +u32 sqlite3WalSavepoint(Log *pLog); +int sqlite3WalSavepointUndo(Log *pLog, u32 iFrame); + /* Return true if data has been written but not committed to the log file. */ int sqlite3WalDirty(Log *pLog); /* Write a frame or frames to the log. */ int sqlite3WalFrames(Log *pLog, int, PgHdr *, Pgno, int, int); Index: test/permutations.test ================================================================== --- test/permutations.test +++ test/permutations.test @@ -759,10 +759,11 @@ run_tests "wal" -description { Run tests with journal_mode=WAL } -include { savepoint.test savepoint2.test + savepoint6.test } # End of tests ############################################################################# Index: test/savepoint6.test ================================================================== --- test/savepoint6.test +++ test/savepoint6.test @@ -220,10 +220,11 @@ foreach zSetup [list { set testname normal sqlite3 db test.db } { + if {[wal_is_wal_mode]} continue set testname tempdb sqlite3 db "" } { if {[catch {set ::permutations_test_prefix} z] == 0 && $z eq "journaltest"} { continue @@ -239,22 +240,24 @@ unset -nocomplain ::lSavepoint unset -nocomplain ::aEntry catch { db close } - file delete -force test.db + file delete -force test.db test.db-wal test.db-journal eval $zSetup sql $DATABASE_SCHEMA + + wal_set_journal_mode do_test savepoint6-$testname.setup { savepoint one insert_rows [random_integers 100 1000] release one checkdb } {ok} - for {set i 0} {$i < 1000} {incr i} { + for {set i 0} {$i < 50} {incr i} { do_test savepoint6-$testname.$i.1 { savepoint_op checkdb } {ok} @@ -262,11 +265,13 @@ database_op database_op checkdb } {ok} } + + wal_check_journal_mode savepoint6-$testname.walok } unset -nocomplain ::lSavepoint unset -nocomplain ::aEntry finish_test Index: test/wal.test ================================================================== --- test/wal.test +++ test/wal.test @@ -164,20 +164,77 @@ COMMIT; SELECT * FROM t1; } } {a b} -do_test wal-4.4 { +do_test wal-4.4.1 { db close sqlite3 db test.db db func blob blob list [execsql { SELECT * FROM t1 }] [file size test.db-wal] } {{a b} 0} -do_test wal-4.5 { +do_test wal-4.4.2 { + execsql { PRAGMA cache_size = 10 } + execsql { + CREATE TABLE t2(a, b); + INSERT INTO t2 VALUES(blob(400), blob(400)); + SAVEPOINT tr; + INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /* 2 */ + INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /* 4 */ + INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /* 8 */ + INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /* 16 */ + INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /* 32 */ + INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /* 2 */ + INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /* 4 */ + INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /* 8 */ + INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /* 16 */ + INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /* 32 */ + SELECT count(*) FROM t2; + } +} {32} +do_test wal-4.4.3 { + execsql { ROLLBACK TO tr } +} {} +do_test wal-4.4.4 { + set logsize [file size test.db-wal] + execsql { + INSERT INTO t1 VALUES('x', 'y'); + RELEASE tr; + } + expr { $logsize == [file size test.db-wal] } +} {1} +do_test wal-4.4.5 { + execsql { SELECT count(*) FROM t2 } +} {1} +do_test wal-4.4.6 { + file copy -force test.db test2.db + file copy -force test.db-wal test2.db-wal + sqlite3 db2 test2.db + execsql { SELECT count(*) FROM t2 ; SELECT count(*) FROM t1 } db2 +} {1 2} +do_test wal-4.4.7 { + execsql { PRAGMA integrity_check } db2 +} {ok} +db2 close + +do_test wal-4.5.1 { + reopen_db + db func blob blob + execsql { + PRAGMA journal_mode = WAL; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES('a', 'b'); + } + sqlite3 db test.db + db func blob blob + list [execsql { SELECT * FROM t1 }] [file size test.db-wal] +} {{a b} 0} +do_test wal-4.5.2 { execsql { PRAGMA cache_size = 10 } execsql { CREATE TABLE t2(a, b); + BEGIN; INSERT INTO t2 VALUES(blob(400), blob(400)); SAVEPOINT tr; INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /* 2 */ INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /* 4 */ INSERT INTO t2 SELECT blob(400), blob(400) FROM t2; /* 8 */ @@ -189,34 +246,39 @@ INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /* 16 */ INSERT INTO t1 SELECT blob(400), blob(400) FROM t1; /* 32 */ SELECT count(*) FROM t2; } } {32} -do_test wal-4.6 { +do_test wal-4.5.3 { +breakpoint execsql { ROLLBACK TO tr } } {} -do_test wal-4.7 { +do_test wal-4.5.4 { set logsize [file size test.db-wal] +breakpoint execsql { INSERT INTO t1 VALUES('x', 'y'); RELEASE tr; + COMMIT; } expr { $logsize == [file size test.db-wal] } } {1} -do_test wal-4.8 { - execsql { SELECT count(*) FROM t2 } -} {1} -do_test wal-4.9 { +do_test wal-4.5.5 { + execsql { SELECT count(*) FROM t2 ; SELECT count(*) FROM t1 } +} {1 2} +do_test wal-4.5.6 { file copy -force test.db test2.db file copy -force test.db-wal test2.db-wal sqlite3 db2 test2.db +breakpoint execsql { SELECT count(*) FROM t2 ; SELECT count(*) FROM t1 } db2 } {1 2} -do_test wal-4.10 { +do_test wal-4.5.7 { execsql { PRAGMA integrity_check } db2 } {ok} db2 close + reopen_db do_test wal-5.1 { execsql { CREATE TEMP TABLE t2(a, b); @@ -678,31 +740,31 @@ BEGIN; INSERT INTO t1 SELECT blob(900) FROM t1; -- 32 SELECT count(*) FROM t1; } list [expr [file size test.db]/1024] [file size test.db-wal] -} [list 37 [log_file_size 35 1024]] +} [list 37 [log_file_size 37 1024]] do_test wal-11.11 { execsql { SELECT count(*) FROM t1; ROLLBACK; SELECT count(*) FROM t1; } } {32 16} do_test wal-11.12 { list [expr [file size test.db]/1024] [file size test.db-wal] -} [list 37 [log_file_size 35 1024]] +} [list 37 [log_file_size 37 1024]] do_test wal-11.13 { execsql { INSERT INTO t1 VALUES( blob(900) ); SELECT count(*) FROM t1; PRAGMA integrity_check; } } {17 ok} do_test wal-11.14 { list [expr [file size test.db]/1024] [file size test.db-wal] -} [list 37 [log_file_size 35 1024]] +} [list 37 [log_file_size 37 1024]] #------------------------------------------------------------------------- # This block of tests, wal-12.*, tests the fix for a problem that # could occur if a log that is a prefix of an older log is written