Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -1061,10 +1061,11 @@ case SQLITE_PERM: zName = "SQLITE_PERM"; break; case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; case SQLITE_ABORT_ROLLBACK: zName = "SQLITE_ABORT_ROLLBACK"; break; case SQLITE_BUSY: zName = "SQLITE_BUSY"; break; case SQLITE_BUSY_RECOVERY: zName = "SQLITE_BUSY_RECOVERY"; break; + case SQLITE_BUSY_SNAPSHOT: zName = "SQLITE_BUSY_SNAPSHOT"; break; case SQLITE_LOCKED: zName = "SQLITE_LOCKED"; break; case SQLITE_LOCKED_SHAREDCACHE: zName = "SQLITE_LOCKED_SHAREDCACHE";break; case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break; case SQLITE_READONLY: zName = "SQLITE_READONLY"; break; case SQLITE_READONLY_RECOVERY: zName = "SQLITE_READONLY_RECOVERY"; break; Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -473,10 +473,11 @@ #define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22<<8)) #define SQLITE_IOERR_DELETE_NOENT (SQLITE_IOERR | (23<<8)) #define SQLITE_IOERR_MMAP (SQLITE_IOERR | (24<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) +#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) #define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8)) #define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8)) #define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8)) #define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) #define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) Index: src/wal.c ================================================================== --- src/wal.c +++ src/wal.c @@ -2461,11 +2461,11 @@ ** the write is disallowed. */ if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){ walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); pWal->writeLock = 0; - rc = SQLITE_BUSY; + rc = SQLITE_BUSY_SNAPSHOT; } return rc; } Index: test/wal6.test ================================================================== --- test/wal6.test +++ test/wal6.test @@ -12,10 +12,11 @@ # focus of this file is testing the operation of the library in # "PRAGMA journal_mode=WAL" mode. # set testdir [file dirname $argv0] +set testprefix wal6 source $testdir/tester.tcl source $testdir/lock_common.tcl source $testdir/wal_common.tcl source $testdir/malloc_common.tcl ifcapable !wal {finish_test ; return } @@ -27,22 +28,22 @@ forcedelete test.db set all_journal_modes {delete persist truncate memory off} foreach jmode $all_journal_modes { - do_test wal6-1.0.$jmode { + do_test wal6-1.0.$jmode { sqlite3 db test.db execsql "PRAGMA journal_mode = $jmode;" - } $jmode - - do_test wal6-1.1.$jmode { - execsql { - CREATE TABLE t1(a INTEGER PRIMARY KEY, b); - INSERT INTO t1 VALUES(1,2); - SELECT * FROM t1; - } - } {1 2} + } $jmode + + do_test wal6-1.1.$jmode { + execsql { + CREATE TABLE t1(a INTEGER PRIMARY KEY, b); + INSERT INTO t1 VALUES(1,2); + SELECT * FROM t1; + } + } {1 2} # Under Windows, you'll get an error trying to delete # a file this is already opened. Close the first connection # so the other tests work. if {$tcl_platform(platform)=="windows"} { @@ -49,33 +50,98 @@ if {$jmode=="persist" || $jmode=="truncate"} { db close } } - do_test wal6-1.2.$jmode { - sqlite3 db2 test.db - execsql { - PRAGMA journal_mode=WAL; - INSERT INTO t1 VALUES(3,4); - SELECT * FROM t1 ORDER BY a; - } db2 - } {wal 1 2 3 4} + do_test wal6-1.2.$jmode { + sqlite3 db2 test.db + execsql { + PRAGMA journal_mode=WAL; + INSERT INTO t1 VALUES(3,4); + SELECT * FROM t1 ORDER BY a; + } db2 + } {wal 1 2 3 4} if {$tcl_platform(platform)=="windows"} { if {$jmode=="persist" || $jmode=="truncate"} { - sqlite3 db test.db + sqlite3 db test.db } } - do_test wal6-1.3.$jmode { - execsql { - SELECT * FROM t1 ORDER BY a; - } - } {1 2 3 4} - - db close - db2 close + do_test wal6-1.3.$jmode { + execsql { + SELECT * FROM t1 ORDER BY a; + } + } {1 2 3 4} + + db close + db2 close forcedelete test.db } + +#------------------------------------------------------------------------- +# Test that SQLITE_BUSY_SNAPSHOT is returned as expected. +# +reset_db +sqlite3 db2 test.db + +do_execsql_test 2.1 { + PRAGMA journal_mode = WAL; + CREATE TABLE t1(a PRIMARY KEY, b TEXT); + INSERT INTO t1 VALUES(1, 'one'); + INSERT INTO t1 VALUES(2, 'two'); + BEGIN; + SELECT * FROM t1; +} {wal 1 one 2 two} + +do_test 2.2 { + execsql { + SELECT * FROM t1; + INSERT INTO t1 VALUES(3, 'three'); + } db2 +} {1 one 2 two} + +do_catchsql_test 2.3 { + INSERT INTO t1 VALUES('x', 'x') +} {1 {database is locked}} + +do_test 2.4 { + list [sqlite3_errcode db] [sqlite3_extended_errcode db] +} {SQLITE_BUSY SQLITE_BUSY_SNAPSHOT} + +do_execsql_test 2.5 { + SELECT * FROM t1; + COMMIT; + INSERT INTO t1 VALUES('x', 'x') +} {1 one 2 two} + +if 0 { +proc test3 {prefix} { + do_test $prefix.1 { + execsql { SELECT count(*) FROM t1 } + } {0} + do_test $prefix.2 { + execsql { INSERT INTO t1 VALUES('x', 'x') } db2 + } {} + do_test $prefix.3 { + execsql { INSERT INTO t1 VALUES('y', 'y') } + } {} + do_test $prefix.4 { + execsql { SELECT count(*) FROM t1 } + } {2} +} + +do_execsql_test 2.6.1 { DELETE FROM t1 } +test3 2.6.2 + +db func test3 test3 +do_execsql_test 2.6.3 { DELETE FROM t1 } +db eval {SELECT test3('2.6.4')} +} + +do_test 2.x { + db2 close +} {} finish_test +