Index: src/wal.c ================================================================== --- src/wal.c +++ src/wal.c @@ -1318,13 +1318,11 @@ testcase( mxFrame==HASHTABLE_NPAGE_ONE+1 ); if( mxFrame==0 ) return; /* Obtain pointers to the hash-table and page-number array containing - ** the entry that corresponds to frame pWal->hdr.mxFrame. It is guaranteed - ** that the page said hash-table and array reside on is already mapped. - */ + ** the entry that corresponds to frame pWal->hdr.mxFrame. */ assert( pWal->nWiData>walFramePage(iExternal) ); assert( pWal->apWiData[walFramePage(iExternal)] ); walHashGet(pWal, walFramePage(iExternal), &sLoc); /* Zero all hash-table entries that correspond to frame numbers greater @@ -3868,17 +3866,16 @@ ** was opened. */ rc = SQLITE_BUSY_SNAPSHOT; }else if( memcmp(&pWal->hdr, (void*)&head, sizeof(WalIndexHdr))!=0 ){ int bWal2 = isWalMode2(pWal); int iHash; - int iLastHash = walFramePage(head.mxFrame); int nLoop = 1+(bWal2 && walidxGetFile(&head)!=walidxGetFile(&pWal->hdr)); int iLoop; assert( nLoop==1 || nLoop==2 ); - for(iLoop=0; iLooppData)[40]; int sz; i64 iOff; - int iFrame = sLoc.iZero + i; + u32 iFrame = sLoc.iZero + i; int iWal = 0; if( bWal2 ){ iWal = walExternalDecode(iFrame, &iFrame); } sz = pWal->hdr.szPage; @@ -4047,12 +4044,33 @@ /* Restore the clients cache of the wal-index header to the state it ** was in before the client began writing to the database. */ memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr)); - assert( walidxGetFile(&pWal->hdr)==iWal ); iNew = walidxGetMxFrame(&pWal->hdr, walidxGetFile(&pWal->hdr)); + + /* BEGIN CONCURRENT transactions are different, as the header just + ** memcpy()d into pWal->hdr may not be the same as the current header + ** when the transaction was started. Instead, pWal->hdr now contains + ** the header written by the most recent successful COMMIT. Because + ** Wal.writeLock is set, if this is a BEGIN CONCURRENT transaction, + ** the rollback must be taking place because an error occurred during + ** a COMMIT. + ** + ** The code below is still valid. All frames between (iNew+1) and iMax + ** must have been written by this transaction before the error occurred. + ** The exception is in wal2 mode - if the current wal file at the time + ** of the last COMMIT is not wal file iWal, then the error must have + ** occurred in WalLockForCommit(), before any pages were written + ** to the database file. In this case return early. */ +#ifndef SQLITE_OMIT_CONCURRENT + if( walidxGetFile(&pWal->hdr)!=iWal ){ + assert( isWalMode2(pWal) ); + return SQLITE_OK; + } +#endif + assert( walidxGetFile(&pWal->hdr)==iWal ); for(iFrame=iNew+1; ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; iFrame++){ /* This call cannot fail. Unless the page for which the page number ** is passed as the second argument is (a) in the cache and ** (b) has an outstanding reference, then xUndo is either a no-op ADDED test/concfault2.test Index: test/concfault2.test ================================================================== --- /dev/null +++ test/concfault2.test @@ -0,0 +1,65 @@ +# 2018 Dec 28 +# +# 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 contains fault injection tests designed to test the concurrent +# transactions feature. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/malloc_common.tcl +set testprefix concfault2 + +ifcapable !concurrent { + finish_test + return +} + +do_execsql_test 1.0 { + PRAGMA auto_vacuum = 0; + PRAGMA journal_mode = wal2; + CREATE TABLE t1(a PRIMARY KEY, b); + CREATE TABLE t2(a PRIMARY KEY, b); + INSERT INTO t1 VALUES(randomblob(1000), randomblob(100)); + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + INSERT INTO t1 SELECT randomblob(1000), randomblob(1000) FROM t1; + DELETE FROM t1 WHERE rowid%2; +} {wal2} + +do_test 1.1 { + list [expr [file size test.db-wal]>75000] [file size test.db-shm] +} {1 32768} + +faultsim_save_and_close + +do_faultsim_test 1 -prep { + faultsim_restore_and_reopen + execsql { + BEGIN CONCURRENT; + INSERT INTO t2 VALUES(1, 2); + } + sqlite3 db2 test.db + execsql { + PRAGMA journal_size_limit = 10000; + INSERT INTO t1 VALUES(randomblob(1000), randomblob(1000)); + } db2 + db2 close +} -body { + execsql { COMMIT } +} -test { + faultsim_test_result {0 {}} + catchsql { ROLLBACK } + faultsim_integrity_check +} +finish_test + Index: test/concurrent2.test ================================================================== --- test/concurrent2.test +++ test/concurrent2.test @@ -20,10 +20,14 @@ ifcapable !concurrent { finish_test return } + +do_test 0.1 { + llength [sqlite3_wal_info db main] +} {2} do_multiclient_test tn { do_test 1.$tn.1 { sql1 { Index: test/permutations.test ================================================================== --- test/permutations.test +++ test/permutations.test @@ -443,10 +443,11 @@ wal2concurrent.test concurrent.test concurrent2.test concurrent3.test concurrent4.test concurrent5.test concurrent6.test concurrent7.test + concfault.test concfault2.test walvfs.test walfault2.test nockpt.test snapshot2.test snapshot3.test snapshot4.test snapshot_fault.test snapshot.test snapshot_up.test walcrash2.test walcrash3.test walcrash4.test walcrash.test Index: test/wal2recover2.test ================================================================== --- test/wal2recover2.test +++ test/wal2recover2.test @@ -218,10 +218,53 @@ } db2 } $res db2 close } + +#------------------------------------------------------------------------- +reset_db +do_execsql_test 1.8.1 { + PRAGMA autovacuum = 0; + PRAGMA page_size = 4096; + CREATE TABLE t1(x); + CREATE TABLE t2(x); + WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1500 ) + INSERT INTO t1 SELECT i FROM s; + WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1500 ) + INSERT INTO t2 SELECT i FROM s; + + PRAGMA journal_mode = wal2; + PRAGMA journal_size_limit = 10000; + + WITH s(i) AS ( VALUES(1) UNION ALL SELECT i+1 FROM s WHERE i<1500 ) + INSERT INTO t2 SELECT i FROM s; +} {wal2 10000} + +do_test 1.8.2 { + list [file size test.db-wal] [file size test.db-wal2] +} {24752 0} + +do_execsql_test 1.8.3 { PRAGMA user_version = 123 } +do_test 1.8.4 { + list [file size test.db-wal] [file size test.db-wal2] +} {24752 4152} + +do_test 1.8.5 { + hexio_write test.db-wal2 [expr 56+16] 0400 + fix_wal_cksums test.db-wal2 +} {} + +do_test 1.8.6 { + forcecopy test.db test.db2 + forcecopy test.db-wal test.db2-wal + forcecopy test.db-wal2 test.db2-wal2 + sqlite3 db2 test.db2 + catchsql { SELECT * FROM sqlite_master } db2 +} {1 {database disk image is malformed}} +db2 close + #------------------------------------------------------------------------- reset_db do_execsql_test 1.0 { CREATE TABLE t1(a, b, c); CREATE INDEX t1a ON t1(a); Index: test/wal2savepoint.test ================================================================== --- test/wal2savepoint.test +++ test/wal2savepoint.test @@ -52,9 +52,22 @@ INSERT INTO t1 SELECT random(), random(), random() FROM s; COMMIT; SELECT count(*) FROM t1; PRAGMA integrity_check; } {210 ok} + +do_execsql_test 1.4 { + BEGIN; + SAVEPOINT abc; + WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s where i < 100) + INSERT INTO t1 SELECT random(), random(), random() FROM s; + ROLLBACK TO abc; + WITH s(i) AS ( SELECT 1 UNION ALL SELECT i+1 FROM s where i < 10) + INSERT INTO t1 SELECT random(), random(), random() FROM s; + COMMIT; + SELECT count(*) FROM t1; + PRAGMA integrity_check; +} {220 ok} finish_test Index: test/wal2snapshot.test ================================================================== --- test/wal2snapshot.test +++ test/wal2snapshot.test @@ -65,12 +65,22 @@ set SNAPSHOT [sqlite3_snapshot_get_blob db main] sqlite3_snapshot_open_blob db main $SNAPSHOT execsql COMMIT } {} } else { - do_test 2.6 { + + do_test 2.6.1 { + execsql BEGIN + set res [ + list [catch { sqlite3_snapshot_open_blob db main $SNAPSHOT } msg] $msg + ] + execsql COMMIT + set res + } {1 SQLITE_ERROR} + do_test 2.6.2 { execsql BEGIN + execsql {SELECT * FROM sqlite_master} set res [ list [catch { sqlite3_snapshot_open_blob db main $SNAPSHOT } msg] $msg ] execsql COMMIT set res