Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -5090,11 +5090,11 @@ /* The dbOrigSize is never set if journal_mode=OFF */ assert( pPager->journalMode!=PAGER_JOURNALMODE_OFF || pPager->dbOrigSize==0 ); /* If a prior error occurred, report that error again. */ - if( NEVER(pPager->errCode) ) return pPager->errCode; + if( pPager->errCode ) return pPager->errCode; PAGERTRACE(("DATABASE SYNC: File=%s zMaster=%s nSize=%d\n", pPager->zFilename, zMaster, pPager->dbSize)); if( MEMDB && pPager->dbModified ){ Index: test/malloc_common.tcl ================================================================== --- test/malloc_common.tcl +++ test/malloc_common.tcl @@ -185,11 +185,10 @@ set ::sqlite_io_error_hit 0 set ::sqlite_io_error_pending 0 return $sv } - # The following procs are used as [do_one_faultsim_test] callbacks when # injecting shared-memory related error faults into test cases. # proc shmerr_injectinstall {} { testvfs shmfault -default true @@ -205,10 +204,13 @@ } proc shmerr_injectstop {} { shmfault ioerr 0 0 } +# The following procs are used as [do_one_faultsim_test] callbacks when +# injecting SQLITE_FULL error faults into test cases. +# proc fullerr_injectinstall {} { testvfs shmfault -default true } proc fullerr_injectuninstall {} { catch {db close} Index: test/pager1.test ================================================================== --- test/pager1.test +++ test/pager1.test @@ -36,10 +36,12 @@ # # pager1-7.*: Cases specific to "PRAGMA journal_mode=TRUNCATE" # # pager1-8.*: Cases using temporary and in-memory databases. # +# pager1-9.*: Tests related to the backup API. +# set a_string_counter 1 proc a_string {n} { global a_string_counter incr a_string_counter @@ -183,10 +185,13 @@ do_test pager1-$tn.28 { sql3 { SELECT * FROM t1 } } {21 one 22 two 23 three} } #------------------------------------------------------------------------- # Savepoint related test cases. +# +# pager1-3.1.2.*: Force a savepoint rollback to cause the database file +# to grow. # do_test pager1-3.1.1 { faultsim_delete_and_reopen execsql { CREATE TABLE t1(a PRIMARY KEY, b); @@ -216,10 +221,43 @@ INSERT INTO t1 SELECT a+3, randomblob(1500) FROM t1 } {1 {constraint failed}} do_execsql_test pager1-3.4 { SELECT * FROM counter } {3 0} do_execsql_test pager1-3.5 { SELECT a FROM t1 } {1 2 3} do_execsql_test pager1-3.6 { COMMIT } {} + +do_test pager1-3.2.1 { + faultsim_delete_and_reopen + db func a_string a_string + execsql { + PRAGMA auto_vacuum = 2; + PRAGMA cache_size = 10; + CREATE TABLE z(x INTEGER PRIMARY KEY, y); + BEGIN; + INSERT INTO z VALUES(NULL, a_string(800)); + INSERT INTO z SELECT NULL, a_string(800) FROM z; -- 2 + INSERT INTO z SELECT NULL, a_string(800) FROM z; -- 4 + INSERT INTO z SELECT NULL, a_string(800) FROM z; -- 8 + INSERT INTO z SELECT NULL, a_string(800) FROM z; -- 16 + INSERT INTO z SELECT NULL, a_string(800) FROM z; -- 32 + INSERT INTO z SELECT NULL, a_string(800) FROM z; -- 64 + INSERT INTO z SELECT NULL, a_string(800) FROM z; -- 128 + INSERT INTO z SELECT NULL, a_string(800) FROM z; -- 256 + COMMIT; + } + execsql { PRAGMA auto_vacuum } +} {2} +do_execsql_test pager1-3.2.2 { + BEGIN; + INSERT INTO z VALUES(NULL, a_string(800)); + INSERT INTO z VALUES(NULL, a_string(800)); + SAVEPOINT one; + DELETE FROM z WHERE x>256; + PRAGMA incremental_vacuum; + SELECT count(*) FROM z WHERE x < 100; + ROLLBACK TO one; + COMMIT; +} {99} #------------------------------------------------------------------------- # Hot journal rollback related test cases. # # pager1.4.1.*: Test that the pager module deletes very small invalid @@ -839,8 +877,79 @@ INSERT INTO x1 VALUES('William'); INSERT INTO x1 VALUES('Anne'); ROLLBACK; } {} } + +#------------------------------------------------------------------------- +# The next block of tests - pager1-9.* - deal with interactions between +# the pager and the backup API. Test cases: +# +# pager1-9.1.*: Test that a backup completes successfully even if the +# source db is written to during the backup op. +# +# pager1-9.2.*: Test that a backup completes successfully even if the +# source db is written to and then rolled back during a +# backup operation. +# +do_test pager1-9.0.1 { + faultsim_delete_and_reopen + db func a_string a_string + execsql { + PRAGMA cache_size = 10; + BEGIN; + CREATE TABLE ab(a, b, UNIQUE(a, b)); + INSERT INTO ab VALUES( a_string(200), a_string(300) ); + INSERT INTO ab SELECT a_string(200), a_string(300) FROM ab; + INSERT INTO ab SELECT a_string(200), a_string(300) FROM ab; + INSERT INTO ab SELECT a_string(200), a_string(300) FROM ab; + INSERT INTO ab SELECT a_string(200), a_string(300) FROM ab; + INSERT INTO ab SELECT a_string(200), a_string(300) FROM ab; + INSERT INTO ab SELECT a_string(200), a_string(300) FROM ab; + INSERT INTO ab SELECT a_string(200), a_string(300) FROM ab; + COMMIT; + } +} {} +do_test pager1-9.0.2 { + sqlite3 db2 test.db2 + db2 eval { PRAGMA cache_size = 10 } + sqlite3_backup B db2 main db main + list [B step 10000] [B finish] +} {SQLITE_DONE SQLITE_OK} +do_test pager1-9.0.3 { + db one {SELECT md5sum(a, b) FROM ab} +} [db2 one {SELECT md5sum(a, b) FROM ab}] + +do_test pager1-9.1.1 { + execsql { UPDATE ab SET a = a_string(201) } + sqlite3_backup B db2 main db main + B step 30 +} {SQLITE_OK} +do_test pager1-9.1.2 { + execsql { UPDATE ab SET b = a_string(301) } + list [B step 10000] [B finish] +} {SQLITE_DONE SQLITE_OK} +do_test pager1-9.1.3 { + db one {SELECT md5sum(a, b) FROM ab} +} [db2 one {SELECT md5sum(a, b) FROM ab}] +do_test pager1-9.1.4 { execsql { SELECT count(*) FROM ab } } {128} + +do_test pager1-9.2.1 { + execsql { UPDATE ab SET a = a_string(202) } + sqlite3_backup B db2 main db main + B step 30 +} {SQLITE_OK} +do_test pager1-9.2.2 { + execsql { + BEGIN; + UPDATE ab SET b = a_string(301); + ROLLBACK; + } + list [B step 10000] [B finish] +} {SQLITE_DONE SQLITE_OK} +do_test pager1-9.2.3 { + db one {SELECT md5sum(a, b) FROM ab} +} [db2 one {SELECT md5sum(a, b) FROM ab}] +do_test pager1-9.2.4 { execsql { SELECT count(*) FROM ab } } {128} finish_test Index: test/pagerfault.test ================================================================== --- test/pagerfault.test +++ test/pagerfault.test @@ -305,10 +305,23 @@ # } -test { # faultsim_test_result {0 {5 6 7 8}} # faultsim_integrity_check # } # + +# This is designed to provoke a special case in the pager code: +# +# If an error (specifically, a FULL or IOERR error) occurs while writing a +# dirty page to the file-system in order to free up memory, the pager enters +# the "error state". An IO error causes SQLite to roll back the current +# transaction (exiting the error state). A FULL error, however, may only +# rollback the current statement. +# +# This block tests that nothing goes wrong if a FULL error occurs while +# writing a dirty page out to free memory from within a statement that has +# opened a statement transaction. +# do_test pagerfault-7-pre1 { faultsim_delete_and_reopen execsql { CREATE TABLE t2(a INTEGER PRIMARY KEY, b); BEGIN; @@ -333,12 +346,50 @@ BEGIN; UPDATE t1 SET b = randomblob(1500); } } -body { execsql { UPDATE t1 SET a = 65, b = randomblob(1500) WHERE (a+1)>200 } + execsql COMMIT +} -test { + faultsim_test_result {0 {}} + faultsim_integrity_check +} + +do_test pagerfault-8-pre1 { + faultsim_delete_and_reopen + execsql { + PRAGMA auto_vacuum = 1; + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + BEGIN; + INSERT INTO t1 VALUES(NULL, randomblob(1500)); + INSERT INTO t1 VALUES(NULL, randomblob(1500)); + INSERT INTO t1 SELECT NULL, randomblob(1500) FROM t1; -- 4 + INSERT INTO t1 SELECT NULL, randomblob(1500) FROM t1; -- 8 + INSERT INTO t1 SELECT NULL, randomblob(1500) FROM t1; -- 16 + INSERT INTO t1 SELECT NULL, randomblob(1500) FROM t1; -- 32 + INSERT INTO t1 SELECT NULL, randomblob(1500) FROM t1; -- 64 + COMMIT; + } + faultsim_save_and_close + set filesize [file size test.db] + set {} {} +} {} +do_test pagerfault-8-pre2 { + faultsim_restore_and_reopen + execsql { DELETE FROM t1 WHERE a>32 } + expr {[file size test.db] < $filesize} +} {1} +breakpoint +do_faultsim_test pagerfault-8 -prep { + faultsim_restore_and_reopen + execsql { + BEGIN; + DELETE FROM t1 WHERE a>32; + } +} -body { execsql COMMIT } -test { faultsim_test_result {0 {}} faultsim_integrity_check } finish_test