Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -5896,24 +5896,10 @@ sqlite3OsClose(pPager->jfd); } /* Change the journal mode. */ pPager->journalMode = (u8)eMode; - - /* When transistioning from TRUNCATE or PERSIST to any other journal - ** mode (and we are not in locking_mode=EXCLUSIVE) then delete the - ** journal file. - */ - assert( (PAGER_JOURNALMODE_TRUNCATE & 5)==1 ); - assert( (PAGER_JOURNALMODE_PERSIST & 5)==1 ); - assert( (PAGER_JOURNALMODE_DELETE & 5)!=1 ); - assert( (PAGER_JOURNALMODE_MEMORY & 5)!=1 ); - assert( (PAGER_JOURNALMODE_OFF & 5)!=1 ); - assert( (PAGER_JOURNALMODE_WAL & 5)!=1 ); - if( (eOld & 5)==1 && (eMode & 5)!=1 && !pPager->exclusiveMode ){ - sqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0); - } } /* Return the new journal mode */ return (int)pPager->journalMode; } Index: test/journal2.test ================================================================== --- test/journal2.test +++ test/journal2.test @@ -35,43 +35,47 @@ # segment of the journal file-name (i.e. "test.db-journal") are appended to # global list variable $::oplog. # tvfs filter {xOpen xClose xDelete} tvfs script journal_op_catcher - proc journal_op_catcher {method filename args} { # If global variable ::tvfs_error_on_write is defined, then return an # IO error to every attempt to modify the file-system. Otherwise, return # SQLITE_OK. # if {[info exists ::tvfs_error_on_write]} { - if {$method == "xDelete" || $method == "xWrite" || $method == "xTruncate"} { + if {[lsearch {xDelete xWrite xTruncate} $method]>=0} { return SQLITE_IOERR } return SQLITE_OK } - if {[string match *journal* $filename]==0} return - + # The rest of this command only deals with xOpen(), xClose() and xDelete() + # operations on journal files. If this invocation does not represent such + # an operation, return with no further ado. + # set f [file tail $filename] + if {[string match *journal $f]==0} return + if {[lsearch {xOpen xDelete xClose} $method]<0} return + + # Append a record of this operation to global list variable $::oplog. + # lappend ::oplog $method $f + # If this is an attempt to delete a journal file for which there exists + # one ore more open handles, return an error. The code in test_vfs.c + # will not invoke the xDelete method of the "real" VFS in this case. + # if {[info exists ::open_journals($f)]==0} { set ::open_journals($f) 0 } switch -- $method { - xOpen { - incr ::open_journals($f) +1 - } - xClose { - incr ::open_journals($f) -1 - } - xDelete { - if {$::open_journals($f)>0} { return SQLITE_IOERR } - } + xOpen { incr ::open_journals($f) +1 } + xClose { incr ::open_journals($f) -1 } + xDelete { if {$::open_journals($f)>0} { puts EEE;return SQLITE_IOERR } } } - return + return "" } do_test journal2-1.1 { set ::oplog [list] @@ -110,14 +114,11 @@ execsql { PRAGMA journal_mode = truncate } db2 execsql { INSERT INTO t1 VALUES(5, 6) } db2 } {} do_test journal2-1.9 { execsql { SELECT * FROM t1 } } {1 2 3 4 5 6} -# Grow the database until it is reasonably large. Then, from a -# journal_mode=DELETE connection, attempt to commit a large transaction (one -# that involves upgrading to an exclusive lock and writing the database -# before the transaction is committed). +# Grow the database until it is reasonably large. # do_test journal2-1.10 { db2 close db func a_string a_string execsql { @@ -135,10 +136,19 @@ do_test journal2-1.11 { set sz [expr [file size test.db] / 1024] expr {$sz>120 && $sz<200} } 1 +# Using new connection [db2] (with journal_mode=DELETE), write a lot of +# data to the database. So that many pages within the database file are +# modified before the transaction is committed. +# +# Then, enable simulated IO errors in all calls to xDelete, xWrite +# and xTruncate before committing the transaction and closing the +# database file. From the point of view of other file-system users, it +# appears as if the process hosting [db2] unexpectedly exited. +# do_test journal2-1.12 { sqlite3 db2 test.db execsql { PRAGMA cache_size = 10; BEGIN; @@ -179,10 +189,41 @@ expr {[catchsql { PRAGMA integrity_check } db2] == "0 ok"} } {0} do_test journal2-1.21 { db2 close } {} - db close + +#------------------------------------------------------------------------- +# Test that it is possible to switch from journal_mode=truncate to +# journal_mode=WAL on a SAFE_DELETE file-system. SQLite should close and +# delete the journal file when committing the transaction that switches +# the system to WAL mode. +# +ifcapable wal { + do_test journal2-2.1 { + faultsim_delete_and_reopen + set ::oplog [list] + execsql { PRAGMA journal_mode = persist } + set ::oplog + } {} + do_test journal2-2.2 { + execsql { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(3.14159); + } + set ::oplog + } {xOpen test.db-journal} + do_test journal2-2.3 { + expr {[file size test.db-journal] > 512} + } {1} + do_test journal2-2.3 { + set ::oplog [list] + execsql { PRAGMA journal_mode = WAL } + set ::oplog + } {xClose test.db-journal xDelete test.db-journal} + db close +} + tvfs delete finish_test Index: test/jrnlmode.test ================================================================== --- test/jrnlmode.test +++ test/jrnlmode.test @@ -482,20 +482,20 @@ PRAGMA journal_mode = MEMORY; BEGIN; INSERT INTO t4 VALUES(3, 4); } file exists test.db-journal - } {0} + } {1} do_test jrnlmode-6.7 { execsql { COMMIT; SELECT * FROM t4; } } {1 2 3 4} do_test jrnlmode-6.8 { file exists test.db-journal - } {0} + } {1} do_test jrnlmode-6.9 { execsql { PRAGMA journal_mode = DELETE; BEGIN IMMEDIATE; INSERT INTO t4 VALUES(1,2); COMMIT; } ADDED test/tkt-fc62af4523.test Index: test/tkt-fc62af4523.test ================================================================== --- /dev/null +++ test/tkt-fc62af4523.test @@ -0,0 +1,85 @@ +# 2010 June 16 +# +# 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 implements regression tests for SQLite library. Specifically, +# it tests that ticket [fc62af4523] has been resolved. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl + +do_test tkt-fc62af4523.1 { + execsql { + PRAGMA cache_size = 10; + PRAGMA journal_mode = persist; + CREATE TABLE t1(a UNIQUE, b UNIQUE); + INSERT INTO t1 SELECT randomblob(200), randomblob(300); + INSERT INTO t1 SELECT randomblob(200), randomblob(300) FROM t1; -- 2 + INSERT INTO t1 SELECT randomblob(200), randomblob(300) FROM t1; -- 4 + INSERT INTO t1 SELECT randomblob(200), randomblob(300) FROM t1; -- 8 + INSERT INTO t1 SELECT randomblob(200), randomblob(300) FROM t1; -- 16 + INSERT INTO t1 SELECT randomblob(200), randomblob(300) FROM t1; -- 32 + INSERT INTO t1 SELECT randomblob(200), randomblob(300) FROM t1; -- 64 + } + execsql { + PRAGMA integrity_check; + SELECT count(*) FROM t1; + } +} {ok 64} + +# Launch an external process. Have it write (but not commit) a large +# transaction to the database. +# +set ::chan [launch_testfixture] +proc buddy {code} { testfixture $::chan $code } +do_test tkt-fc62af4523.2 { + testfixture $::chan { + sqlite3 db test.db + db eval { + PRAGMA cache_size = 10; + BEGIN; + UPDATE t1 SET b = randomblob(400); + UPDATE t1 SET a = randomblob(200); + } + } + file exists test.db-journal +} {1} + +# Now do "PRAGMA journal_mode = DELETE" in this process. At one point +# this was causing SQLite to delete the journal file from the file-system, +# even though the external process is currently using it. +# +do_test tkt-fc62af4523.3 { execsql { PRAGMA journal_mode = DELETE } } {delete} +do_test tkt-fc62af4523.4 { file exists test.db-journal } {1} + +# Cause the external process to crash. Since it has already written +# uncommitted data into the database file, the next reader will have +# to do a hot-journal rollback to recover the database. +# +# Or, if this test is run in a version with the bug present, the journal +# file has already been deleted. In this case we are left with a corrupt +# database file and no hot-journal to fix it with. +# +do_test tkt-fc62af4523.5 { + testfixture $::chan sqlite_abort +} {ERROR: Child process hung up} +after 200 +do_test tkt-fc62af4523.6 { + execsql { + PRAGMA integrity_check; + SELECT count(*) FROM t1; + } +} {ok 64} + +catch { close $::chan } +finish_test +