ed36020daf
FossilOrigin-Name: c501b2ede6aad123bef0aa7ce8b356a134eb6d26
595 lines
15 KiB
Plaintext
595 lines
15 KiB
Plaintext
# 2010 May 5
|
|
#
|
|
# 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. The
|
|
# focus of this file is testing the operation of the library in
|
|
# "PRAGMA journal_mode=WAL" mode.
|
|
#
|
|
|
|
set testdir [file dirname $argv0]
|
|
source $testdir/tester.tcl
|
|
source $testdir/lock_common.tcl
|
|
ifcapable !wal {finish_test ; return }
|
|
|
|
proc set_tvfs_hdr {file args} {
|
|
if {[llength $args]>1} {
|
|
return -code error {wrong # args: should be "set_tvfs_hdr fileName ?val?"}
|
|
}
|
|
|
|
set blob [tvfs shm $file]
|
|
if {[llength $args]} {
|
|
set blob [binary format i8a* [lindex $args 0] [string range $blob 32 end]]
|
|
tvfs shm $file $blob
|
|
}
|
|
|
|
binary scan $blob i8 ints
|
|
return $ints
|
|
}
|
|
|
|
proc incr_tvfs_hdr {file idx incrval} {
|
|
set ints [set_tvfs_hdr $file]
|
|
set v [lindex $ints $idx]
|
|
incr v $incrval
|
|
lset ints $idx $v
|
|
set_tvfs_hdr $file $ints
|
|
}
|
|
|
|
|
|
#-------------------------------------------------------------------------
|
|
# Test case wal2-1.*:
|
|
#
|
|
# Set up a small database containing a single table. The database is not
|
|
# checkpointed during the test - all content resides in the log file.
|
|
#
|
|
# Two connections are established to the database file - a writer ([db])
|
|
# and a reader ([db2]). For each of the 8 integer fields in the wal-index
|
|
# header (6 fields and 2 checksum values), do the following:
|
|
#
|
|
# 1. Modify the database using the writer.
|
|
#
|
|
# 2. Attempt to read the database using the reader. Before the reader
|
|
# has a chance to snapshot the wal-index header, increment one
|
|
# of the the integer fields (so that the reader ends up with a corrupted
|
|
# header).
|
|
#
|
|
# 3. Check that the reader recovers the wal-index and reads the correct
|
|
# database content.
|
|
#
|
|
do_test wal2-1.0 {
|
|
proc tvfs_cb {method args} { return SQLITE_OK }
|
|
testvfs tvfs tvfs_cb
|
|
|
|
sqlite3 db test.db -vfs tvfs
|
|
sqlite3 db2 test.db -vfs tvfs
|
|
|
|
execsql {
|
|
PRAGMA journal_mode = WAL;
|
|
CREATE TABLE t1(a);
|
|
} db2
|
|
execsql {
|
|
INSERT INTO t1 VALUES(1);
|
|
INSERT INTO t1 VALUES(2);
|
|
INSERT INTO t1 VALUES(3);
|
|
INSERT INTO t1 VALUES(4);
|
|
SELECT count(a), sum(a) FROM t1;
|
|
}
|
|
} {4 10}
|
|
do_test wal2-1.1 {
|
|
execsql { SELECT count(a), sum(a) FROM t1 } db2
|
|
} {4 10}
|
|
|
|
foreach {tn iInsert res wal_index_hdr_mod wal_locks} {
|
|
2 5 {5 15} 0 {READ RECOVER READ UNLOCK}
|
|
3 6 {6 21} 1 {READ RECOVER READ UNLOCK}
|
|
4 7 {7 28} 2 {READ RECOVER READ UNLOCK}
|
|
5 8 {8 36} 3 {READ RECOVER READ UNLOCK}
|
|
6 9 {9 45} 4 {READ RECOVER READ UNLOCK}
|
|
7 10 {10 55} 5 {READ RECOVER READ UNLOCK}
|
|
8 11 {11 66} 6 {READ RECOVER READ UNLOCK}
|
|
9 12 {12 78} 7 {READ RECOVER READ UNLOCK}
|
|
10 13 {13 91} -1 {READ UNLOCK}
|
|
} {
|
|
|
|
do_test wal2-1.$tn.1 {
|
|
execsql { INSERT INTO t1 VALUES($iInsert) }
|
|
|
|
set ::locks [list]
|
|
set ::cb_done 0
|
|
|
|
proc tvfs_cb {method args} {
|
|
if {$::cb_done == 0 && $method == "xShmGet"} {
|
|
set ::cb_done 1
|
|
if {$::wal_index_hdr_mod >= 0} {
|
|
incr_tvfs_hdr [lindex $args 0] $::wal_index_hdr_mod 1
|
|
}
|
|
}
|
|
|
|
if {$method == "xShmLock"} { lappend ::locks [lindex $args 2] }
|
|
return SQLITE_OK
|
|
}
|
|
|
|
execsql { SELECT count(a), sum(a) FROM t1 } db2
|
|
} $res
|
|
|
|
do_test wal2-1.$tn.2 {
|
|
set ::locks
|
|
} $wal_locks
|
|
}
|
|
db close
|
|
db2 close
|
|
tvfs delete
|
|
file delete -force test.db test.db-wal test.db-journal
|
|
|
|
#-------------------------------------------------------------------------
|
|
# This test case is very similar to the previous one, except, after
|
|
# the reader reads the corrupt wal-index header, but before it has
|
|
# a chance to re-read it under the cover of the RECOVER lock, the
|
|
# wal-index header is replaced with a valid, but out-of-date, header.
|
|
#
|
|
# Because the header checksum looks Ok, the reader does not run recovery,
|
|
# it simply drops back to a READ lock and proceeds. But because the
|
|
# header is out-of-date, the reader reads the out-of-date snapshot.
|
|
#
|
|
# After this, the header is corrupted again and the reader is allowed
|
|
# to run recovery. This time, it sees an up-to-date snapshot of the
|
|
# database file.
|
|
#
|
|
do_test wal2-2.0 {
|
|
|
|
testvfs tvfs tvfs_cb
|
|
proc tvfs_cb {method args} {
|
|
if {$method == "xShmOpen"} { set ::shm_file [lindex $args 0] }
|
|
return SQLITE_OK
|
|
}
|
|
|
|
sqlite3 db test.db -vfs tvfs
|
|
sqlite3 db2 test.db -vfs tvfs
|
|
|
|
execsql {
|
|
PRAGMA journal_mode = WAL;
|
|
CREATE TABLE t1(a);
|
|
} db2
|
|
execsql {
|
|
INSERT INTO t1 VALUES(1);
|
|
INSERT INTO t1 VALUES(2);
|
|
INSERT INTO t1 VALUES(3);
|
|
INSERT INTO t1 VALUES(4);
|
|
SELECT count(a), sum(a) FROM t1;
|
|
}
|
|
} {4 10}
|
|
do_test wal2-2.1 {
|
|
execsql { SELECT count(a), sum(a) FROM t1 } db2
|
|
} {4 10}
|
|
|
|
foreach {tn iInsert res0 res1 wal_index_hdr_mod} {
|
|
2 5 {4 10} {5 15} 0
|
|
3 6 {5 15} {6 21} 1
|
|
4 7 {6 21} {7 28} 2
|
|
5 8 {7 28} {8 36} 3
|
|
6 9 {8 36} {9 45} 4
|
|
7 10 {9 45} {10 55} 5
|
|
8 11 {10 55} {11 66} 6
|
|
9 12 {11 66} {12 78} 7
|
|
} {
|
|
do_test wal2-1.$tn.1 {
|
|
set oldhdr [set_tvfs_hdr $::shm_file]
|
|
execsql { INSERT INTO t1 VALUES($iInsert) }
|
|
execsql { SELECT count(a), sum(a) FROM t1 }
|
|
} $res1
|
|
|
|
do_test wal2-2.$tn.2 {
|
|
set ::locks [list]
|
|
set ::cb_done 0
|
|
proc tvfs_cb {method args} {
|
|
if {$::cb_done == 0 && $method == "xShmGet"} {
|
|
set ::cb_done 1
|
|
if {$::wal_index_hdr_mod >= 0} {
|
|
incr_tvfs_hdr $::shm_file $::wal_index_hdr_mod 1
|
|
}
|
|
}
|
|
if {$method == "xShmLock"} {
|
|
set lock [lindex $args 2]
|
|
lappend ::locks $lock
|
|
if {$lock == "RECOVER"} {
|
|
set_tvfs_hdr $::shm_file $::oldhdr
|
|
}
|
|
}
|
|
return SQLITE_OK
|
|
}
|
|
|
|
execsql { SELECT count(a), sum(a) FROM t1 } db2
|
|
} $res0
|
|
|
|
do_test wal2-2.$tn.3 {
|
|
set ::locks
|
|
} {READ RECOVER READ UNLOCK}
|
|
|
|
do_test wal2-2.$tn.4 {
|
|
set ::locks [list]
|
|
set ::cb_done 0
|
|
proc tvfs_cb {method args} {
|
|
if {$::cb_done == 0 && $method == "xShmGet"} {
|
|
set ::cb_done 1
|
|
if {$::wal_index_hdr_mod >= 0} {
|
|
incr_tvfs_hdr $::shm_file $::wal_index_hdr_mod 1
|
|
}
|
|
}
|
|
if {$method == "xShmLock"} {
|
|
set lock [lindex $args 2]
|
|
lappend ::locks $lock
|
|
}
|
|
return SQLITE_OK
|
|
}
|
|
|
|
execsql { SELECT count(a), sum(a) FROM t1 } db2
|
|
} $res1
|
|
}
|
|
db close
|
|
db2 close
|
|
tvfs delete
|
|
file delete -force test.db test.db-wal test.db-journal
|
|
|
|
#-------------------------------------------------------------------------
|
|
# This test case - wal2-3.* - tests the response of the library to an
|
|
# SQLITE_BUSY when attempting to obtain a READ or RECOVER lock.
|
|
#
|
|
# wal2-3.0 - 2: SQLITE_BUSY when obtaining a READ lock
|
|
# wal2-3.3 - 6: SQLITE_BUSY when obtaining a RECOVER lock
|
|
#
|
|
do_test wal2-3.0 {
|
|
proc tvfs_cb {method args} {
|
|
if {$method == "xShmLock"} {
|
|
if {[info exists ::locked]} { return SQLITE_BUSY }
|
|
}
|
|
return SQLITE_OK
|
|
}
|
|
|
|
proc busyhandler x {
|
|
if {$x>3} { unset -nocomplain ::locked }
|
|
return 0
|
|
}
|
|
|
|
testvfs tvfs tvfs_cb
|
|
sqlite3 db test.db -vfs tvfs
|
|
db busy busyhandler
|
|
|
|
execsql {
|
|
PRAGMA journal_mode = WAL;
|
|
CREATE TABLE t1(a);
|
|
INSERT INTO t1 VALUES(1);
|
|
INSERT INTO t1 VALUES(2);
|
|
INSERT INTO t1 VALUES(3);
|
|
INSERT INTO t1 VALUES(4);
|
|
}
|
|
|
|
set ::locked 1
|
|
info exists ::locked
|
|
} {1}
|
|
do_test wal2-3.1 {
|
|
execsql { SELECT count(a), sum(a) FROM t1 }
|
|
} {4 10}
|
|
do_test wal2-3.2 {
|
|
info exists ::locked
|
|
} {0}
|
|
|
|
do_test wal2-3.3 {
|
|
proc tvfs_cb {method args} {
|
|
if {$method == "xShmLock"} {
|
|
if {[info exists ::sabotage]} {
|
|
unset -nocomplain ::sabotage
|
|
incr_tvfs_hdr [lindex $args 0] 1 1
|
|
}
|
|
if {[info exists ::locked] && [lindex $args 2] == "RECOVER"} {
|
|
return SQLITE_BUSY
|
|
}
|
|
}
|
|
return SQLITE_OK
|
|
}
|
|
set ::sabotage 1
|
|
set ::locked 1
|
|
list [info exists ::sabotage] [info exists ::locked]
|
|
} {1 1}
|
|
do_test wal2-3.4 {
|
|
execsql { SELECT count(a), sum(a) FROM t1 }
|
|
} {4 10}
|
|
do_test wal2-3.5 {
|
|
list [info exists ::sabotage] [info exists ::locked]
|
|
} {0 0}
|
|
db close
|
|
tvfs delete
|
|
file delete -force test.db test.db-wal test.db-journal
|
|
|
|
#-------------------------------------------------------------------------
|
|
# Test that a database connection using a VFS that does not support the
|
|
# xShmXXX interfaces cannot open a WAL database.
|
|
#
|
|
do_test wal2-4.1 {
|
|
sqlite3 db test.db
|
|
execsql {
|
|
PRAGMA journal_mode = WAL;
|
|
CREATE TABLE data(x);
|
|
INSERT INTO data VALUES('need xShmOpen to see this');
|
|
PRAGMA wal_checkpoint;
|
|
}
|
|
} {wal}
|
|
do_test wal2-4.2 {
|
|
db close
|
|
proc ok {args} {return SQLITE_OK}
|
|
testvfs -noshm tvfs ok
|
|
sqlite3 db test.db -vfs tvfs
|
|
catchsql { SELECT * FROM data }
|
|
} {1 {unable to open database file}}
|
|
do_test wal2-4.3 {
|
|
db close
|
|
proc ok {args} {return SQLITE_OK}
|
|
testvfs tvfs ok
|
|
sqlite3 db test.db -vfs tvfs
|
|
catchsql { SELECT * FROM data }
|
|
} {0 {{need xShmOpen to see this}}}
|
|
db close
|
|
tvfs delete
|
|
|
|
#-------------------------------------------------------------------------
|
|
# Test that if a database connection is forced to run recovery before it
|
|
# can perform a checkpoint, it does not transition into RECOVER state.
|
|
#
|
|
do_test wal2-5.1 {
|
|
proc tvfs_cb {method args} {
|
|
set ::shm_file [lindex $args 0]
|
|
if {$method == "xShmLock"} { lappend ::locks [lindex $args 2] }
|
|
return $::tvfs_cb_return
|
|
}
|
|
set tvfs_cb_return SQLITE_OK
|
|
|
|
testvfs tvfs tvfs_cb
|
|
|
|
sqlite3 db test.db -vfs tvfs
|
|
execsql {
|
|
PRAGMA journal_mode = WAL;
|
|
CREATE TABLE x(y);
|
|
INSERT INTO x VALUES(1);
|
|
}
|
|
|
|
incr_tvfs_hdr $::shm_file 1 1
|
|
set ::locks [list]
|
|
execsql { PRAGMA wal_checkpoint }
|
|
set ::locks
|
|
} {CHECKPOINT UNLOCK}
|
|
db close
|
|
tvfs delete
|
|
|
|
#-------------------------------------------------------------------------
|
|
# This block, test cases wal2-6.*, tests the operation of WAL with
|
|
# "PRAGMA locking_mode=EXCLUSIVE" set.
|
|
#
|
|
# wal2-6.1.*: Changing to WAL mode before setting locking_mode=exclusive.
|
|
#
|
|
# wal2-6.2.*: Changing to WAL mode after setting locking_mode=exclusive.
|
|
#
|
|
# wal2-6.3.*: Changing back to rollback mode from WAL mode after setting
|
|
# locking_mode=exclusive.
|
|
#
|
|
# wal2-6.4.*: Check that xShmLock calls are omitted in exclusive locking
|
|
# mode.
|
|
#
|
|
do_test wal2-6.1.1 {
|
|
file delete -force test.db test.db-wal test.db-journal
|
|
sqlite3 db test.db
|
|
execsql {
|
|
Pragma Journal_Mode = Wal;
|
|
Pragma Locking_Mode = Exclusive;
|
|
}
|
|
} {wal exclusive}
|
|
do_test wal2-6.1.2 {
|
|
execsql { PRAGMA lock_status }
|
|
} {main unlocked temp closed}
|
|
do_test wal2-6.1.3 {
|
|
execsql {
|
|
BEGIN;
|
|
CREATE TABLE t1(a, b);
|
|
INSERT INTO t1 VALUES(1, 2);
|
|
COMMIT;
|
|
PRAGMA lock_status;
|
|
}
|
|
} {main exclusive temp closed}
|
|
do_test wal2-6.1.4 {
|
|
execsql {
|
|
PRAGMA locking_mode = normal;
|
|
PRAGMA lock_status;
|
|
}
|
|
} {normal main exclusive temp closed}
|
|
do_test wal2-6.1.5 {
|
|
execsql {
|
|
SELECT * FROM t1;
|
|
PRAGMA lock_status;
|
|
}
|
|
} {1 2 main exclusive temp closed}
|
|
do_test wal2-6.1.6 {
|
|
execsql {
|
|
INSERT INTO t1 VALUES(3, 4);
|
|
PRAGMA lock_status;
|
|
}
|
|
} {main shared temp closed}
|
|
db close
|
|
|
|
do_test wal2-6.2.1 {
|
|
file delete -force test.db test.db-wal test.db-journal
|
|
sqlite3 db test.db
|
|
execsql {
|
|
Pragma Locking_Mode = Exclusive;
|
|
Pragma Journal_Mode = Wal;
|
|
Pragma Lock_Status;
|
|
}
|
|
} {exclusive wal main exclusive temp closed}
|
|
do_test wal2-6.2.2 {
|
|
execsql {
|
|
BEGIN;
|
|
CREATE TABLE t1(a, b);
|
|
INSERT INTO t1 VALUES(1, 2);
|
|
COMMIT;
|
|
Pragma loCK_STATus;
|
|
}
|
|
} {main exclusive temp closed}
|
|
do_test wal2-6.2.3 {
|
|
db close
|
|
sqlite3 db test.db
|
|
execsql { PRAGMA LOCKING_MODE = EXCLUSIVE }
|
|
} {exclusive}
|
|
do_test wal2-6.2.4 {
|
|
execsql {
|
|
SELECT * FROM t1;
|
|
pragma lock_status;
|
|
}
|
|
} {1 2 main shared temp closed}
|
|
do_test wal2-6.2.5 {
|
|
execsql {
|
|
INSERT INTO t1 VALUES(3, 4);
|
|
pragma lock_status;
|
|
}
|
|
} {main exclusive temp closed}
|
|
do_test wal2-6.2.6 {
|
|
execsql {
|
|
PRAGMA locking_mode = NORMAL;
|
|
pragma lock_status;
|
|
}
|
|
} {normal main exclusive temp closed}
|
|
do_test wal2-6.2.7 {
|
|
execsql {
|
|
BEGIN IMMEDIATE; COMMIT;
|
|
pragma lock_status;
|
|
}
|
|
} {main shared temp closed}
|
|
do_test wal2-6.2.8 {
|
|
execsql {
|
|
PRAGMA locking_mode = EXCLUSIVE;
|
|
BEGIN IMMEDIATE; COMMIT;
|
|
PRAGMA locking_mode = NORMAL;
|
|
}
|
|
execsql {
|
|
SELECT * FROM t1;
|
|
pragma lock_status;
|
|
}
|
|
} {1 2 3 4 main exclusive temp closed}
|
|
do_test wal2-6.2.9 {
|
|
execsql {
|
|
INSERT INTO t1 VALUES(5, 6);
|
|
SELECT * FROM t1;
|
|
pragma lock_status;
|
|
}
|
|
} {1 2 3 4 5 6 main shared temp closed}
|
|
db close
|
|
|
|
do_test wal2-6.3.1 {
|
|
file delete -force test.db test.db-wal test.db-journal
|
|
sqlite3 db test.db
|
|
execsql {
|
|
PRAGMA journal_mode = WAL;
|
|
PRAGMA locking_mode = exclusive;
|
|
BEGIN;
|
|
CREATE TABLE t1(x);
|
|
INSERT INTO t1 VALUES('Chico');
|
|
INSERT INTO t1 VALUES('Harpo');
|
|
COMMIT;
|
|
}
|
|
list [file exists test.db-wal] [file exists test.db-journal]
|
|
} {1 0}
|
|
do_test wal2-6.3.2 {
|
|
execsql { PRAGMA journal_mode = DELETE }
|
|
file exists test.db-wal
|
|
} {0}
|
|
do_test wal2-6.3.3 {
|
|
execsql { PRAGMA lock_status }
|
|
} {main exclusive temp closed}
|
|
do_test wal2-6.3.4 {
|
|
execsql {
|
|
BEGIN;
|
|
INSERT INTO t1 VALUES('Groucho');
|
|
}
|
|
list [file exists test.db-wal] [file exists test.db-journal]
|
|
} {0 1}
|
|
do_test wal2-6.3.5 {
|
|
execsql { PRAGMA lock_status }
|
|
} {main exclusive temp closed}
|
|
do_test wal2-6.3.6 {
|
|
execsql { COMMIT }
|
|
list [file exists test.db-wal] [file exists test.db-journal]
|
|
} {0 1}
|
|
do_test wal2-6.3.7 {
|
|
execsql { PRAGMA lock_status }
|
|
} {main exclusive temp closed}
|
|
db close
|
|
|
|
do_test wal2-6.4.1 {
|
|
file delete -force test.db test.db-wal test.db-journal
|
|
proc tvfs_cb {method args} {
|
|
set ::shm_file [lindex $args 0]
|
|
if {$method == "xShmLock"} { lappend ::locks [lindex $args 2] }
|
|
return "SQLITE_OK"
|
|
}
|
|
testvfs tvfs tvfs_cb
|
|
sqlite3 db test.db -vfs tvfs
|
|
|
|
execsql {
|
|
PRAGMA journal_mode = WAL;
|
|
CREATE TABLE t1(x);
|
|
INSERT INTO t1 VALUES('Leonard');
|
|
INSERT INTO t1 VALUES('Arthur');
|
|
}
|
|
|
|
set ::locks [list]
|
|
execsql { PRAGMA locking_mode = exclusive }
|
|
set ::locks
|
|
} {}
|
|
do_test wal2-6.4.2 {
|
|
execsql { SELECT * FROM t1 }
|
|
} {Leonard Arthur}
|
|
do_test wal2-6.4.3 {
|
|
set ::locks
|
|
} {READ}
|
|
do_test wal2-6.4.4 {
|
|
execsql {
|
|
INSERT INTO t1 VALUES('Julius Henry');
|
|
SELECT * FROM t1;
|
|
}
|
|
} {Leonard Arthur {Julius Henry}}
|
|
do_test wal2-6.4.5 {
|
|
set ::locks
|
|
} {READ}
|
|
do_test wal2-6.4.6 {
|
|
execsql {
|
|
PRAGMA locking_mode = NORMAL;
|
|
DELETE FROM t1;
|
|
}
|
|
set ::locks
|
|
} {READ UNLOCK}
|
|
do_test wal2-6.4.7 {
|
|
set ::locks [list]
|
|
execsql { INSERT INTO t1 VALUES('Karl') }
|
|
set ::locks
|
|
} {READ WRITE READ UNLOCK}
|
|
db close
|
|
tvfs delete
|
|
|
|
do_test wal2-6.5.1 {
|
|
sqlite3 db test.db
|
|
execsql {
|
|
PRAGMA journal_mode = wal;
|
|
PRAGMA locking_mode = exclusive;
|
|
CREATE TABLE t2(a, b);
|
|
PRAGMA wal_checkpoint;
|
|
INSERT INTO t2 VALUES('I', 'II');
|
|
PRAGMA journal_mode;
|
|
}
|
|
} {wal exclusive wal}
|
|
db close
|
|
|
|
finish_test
|
|
|