# 2010 April 13
#
# 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
proc range {args} {
set ret [list]
foreach {start end} $args {
for {set i $start} {$i <= $end} {incr i} {
lappend ret $i
}
}
set ret
}
proc reopen_db {} {
catch { db close }
file delete -force test.db test.db-wal
sqlite3_wal db test.db
#register_logtest
}
proc register_logtest {{db db}} {
register_logsummary_module $db
execsql { CREATE VIRTUAL TABLE temp.logsummary USING logsummary } $db
execsql { CREATE VIRTUAL TABLE temp.logcontent USING logcontent } $db
execsql { CREATE VIRTUAL TABLE temp.loglock USING loglock } $db
}
proc sqlite3_wal {args} {
eval sqlite3 $args
[lindex $args 0] eval { PRAGMA journal_mode = wal }
}
#
# These are 'warm-body' tests used while developing the WAL code. They
# serve to prove that a few really simple cases work:
#
# wal-1.*: Read and write the database.
# wal-2.*: Test MVCC with one reader, one writer.
# wal-3.*: Test transaction rollback.
# wal-4.*: Test savepoint/statement rollback.
# wal-5.*: Test the temp database.
# wal-6.*: Test creating databases with different page sizes.
#
do_test wal-0.1 {
execsql { PRAGMA journal_mode = wal }
} {wal}
do_test wal-1.0 {
execsql {
BEGIN;
CREATE TABLE t1(a, b);
}
list [file exists test.db-journal] [file exists test.db-wal]
} {0 1}
do_test wal-1.1 {
execsql COMMIT
list [file exists test.db-journal] [file exists test.db-wal]
} {0 1}
do_test wal-1.2 {
# There are now two pages in the log.
file size test.db-wal
} [expr (20+1024)*2]
do_test wal-1.3 {
execsql { SELECT * FROM sqlite_master }
} {table t1 t1 2 {CREATE TABLE t1(a, b)}}
do_test wal-1.4 {
execsql { INSERT INTO t1 VALUES(1, 2) }
execsql { INSERT INTO t1 VALUES(3, 4) }
execsql { INSERT INTO t1 VALUES(5, 6) }
execsql { INSERT INTO t1 VALUES(7, 8) }
execsql { INSERT INTO t1 VALUES(9, 10) }
} {}
do_test wal-1.5 {
execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10}
do_test wal-2.1 {
sqlite3_wal db2 ./test.db
execsql { BEGIN; SELECT * FROM t1 } db2
} {1 2 3 4 5 6 7 8 9 10}
do_test wal-2.2 {
execsql { INSERT INTO t1 VALUES(11, 12) }
execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10 11 12}
do_test wal-2.3 {
execsql { SELECT * FROM t1 } db2
} {1 2 3 4 5 6 7 8 9 10}
do_test wal-2.4 {
execsql { INSERT INTO t1 VALUES(13, 14) }
execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
do_test wal-2.5 {
execsql { SELECT * FROM t1 } db2
} {1 2 3 4 5 6 7 8 9 10}
do_test wal-2.6 {
execsql { COMMIT; SELECT * FROM t1 } db2
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
do_test wal-3.1 {
execsql { BEGIN; DELETE FROM t1 }
execsql { SELECT * FROM t1 }
} {}
do_test wal-3.2 {
execsql { SELECT * FROM t1 } db2
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
do_test wal-3.3 {
execsql { ROLLBACK }
execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
db2 close
do_test wal-4.1 {
execsql {
DELETE FROM t1;
BEGIN;
INSERT INTO t1 VALUES('a', 'b');
SAVEPOINT sp;
INSERT INTO t1 VALUES('c', 'd');
SELECT * FROM t1;
}
} {a b c d}
do_test wal-4.2 {
execsql {
ROLLBACK TO sp;
SELECT * FROM t1;
}
} {a b}
do_test wal-4.3 {
execsql {
COMMIT;
SELECT * FROM t1;
}
} {a b}
do_test wal-5.1 {
execsql {
CREATE TEMP TABLE t2(a, b);
INSERT INTO t2 VALUES(1, 2);
}
} {}
do_test wal-5.2 {
execsql {
BEGIN;
INSERT INTO t2 VALUES(3, 4);
SELECT * FROM t2;
}
} {1 2 3 4}
do_test wal-5.3 {
execsql {
ROLLBACK;
SELECT * FROM t2;
}
} {1 2}
do_test wal-5.4 {
execsql {
CREATE TEMP TABLE t3(x UNIQUE);
BEGIN;
INSERT INTO t2 VALUES(3, 4);
INSERT INTO t3 VALUES('abc');
}
catchsql { INSERT INTO t3 VALUES('abc') }
} {1 {column x is not unique}}
do_test wal-5.5 {
execsql {
COMMIT;
SELECT * FROM t2;
}
} {1 2 3 4}
db close
foreach sector {512 4096} {
sqlite3_simulate_device -sectorsize $sector
foreach pgsz {512 1024 2048 4096} {
file delete -force test.db test.db-wal
do_test wal-6.$sector.$pgsz.1 {
sqlite3_wal db test.db -vfs devsym
execsql "
PRAGMA page_size = $pgsz ;
"
execsql "
CREATE TABLE t1(a, b);
INSERT INTO t1 VALUES(1, 2);
"
db close
file size test.db
} [expr $pgsz*2]
do_test wal-6.$sector.$pgsz.2 {
file size test.db-wal
} {0}
}
}
do_test wal-7.1 {
file delete -force test.db test.db-wal
sqlite3_wal db test.db
execsql {
PRAGMA page_size = 1024;
CREATE TABLE t1(a, b);
INSERT INTO t1 VALUES(1, 2);
}
list [file size test.db] [file size test.db-wal]
} [list 0 [expr (1024+20)*3]]
do_test wal-7.2 {
execsql { PRAGMA checkpoint }
list [file size test.db] [file size test.db-wal]
} [list 2048 [expr (1024+20)*3]]
# Execute some transactions in auto-vacuum mode to test database file
# truncation.
#
do_test wal-8.1 {
reopen_db
execsql {
PRAGMA auto_vacuum = 1;
PRAGMA auto_vacuum;
}
} {1}
do_test wal-8.2 {
execsql {
PRAGMA page_size = 1024;
CREATE TABLE t1(x);
INSERT INTO t1 VALUES(randomblob(900));
INSERT INTO t1 VALUES(randomblob(900));
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 4 */
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 8 */
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 16 */
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 32 */
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 64 */
PRAGMA checkpoint;
}
file size test.db
} [expr 68*1024]
do_test wal-8.3 {
execsql {
DELETE FROM t1 WHERE rowid<54;
PRAGMA checkpoint;
}
file size test.db
} [expr 14*1024]
# Run some "warm-body" tests to ensure that log-summary files with more
# than 256 entries (log summaries that contain index blocks) work Ok.
#
do_test wal-9.1 {
reopen_db
execsql {
PRAGMA page_size = 1024;
CREATE TABLE t1(x PRIMARY KEY);
INSERT INTO t1 VALUES(randomblob(900));
INSERT INTO t1 VALUES(randomblob(900));
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 4 */
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 8 */
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 16 */
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 32 */
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 64 */
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 128 */
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 256 */
}
file size test.db
} 0
do_test wal-9.2 {
sqlite3_wal db2 test.db
execsql {PRAGMA integrity_check } db2
} {ok}
do_test wal-9.3 {
file delete -force test2.db test2.db-wal
file copy test.db test2.db
file copy test.db-wal test2.db-wal
sqlite3_wal db3 test2.db
execsql {PRAGMA integrity_check } db3
} {ok}
db3 close
do_test wal-9.4 {
execsql { PRAGMA checkpoint }
db2 close
sqlite3_wal db2 test.db
execsql {PRAGMA integrity_check } db2
} {ok}
foreach handle {db db2 db3} { catch { $handle close } }
unset handle
#-------------------------------------------------------------------------
# The following block of tests - wal-10.* - test that the WAL locking
# scheme works for clients in a single process.
#
reopen_db
sqlite3_wal db2 test.db
sqlite3_wal db3 test.db
do_test wal-10.1 {
execsql {
CREATE TABLE t1(a, b);
INSERT INTO t1 VALUES(1, 2);
BEGIN;
INSERT INTO t1 VALUES(3, 4);
}
execsql "SELECT * FROM t1" db2
} {1 2}
do_test wal-10.2 {
execsql { COMMIT }
execsql "SELECT * FROM t1" db2
} {1 2 3 4}
do_test wal-10.3 {
execsql {
BEGIN;
SELECT * FROM t1;
} db2
} {1 2 3 4}
do_test wal-10.4 {
catchsql { PRAGMA checkpoint }
} {1 {database is locked}}
do_test wal-10.5 {
execsql { INSERT INTO t1 VALUES(5, 6) }
execsql { SELECT * FROM t1 } db2
} {1 2 3 4}
# Connection [db2] is holding a lock on a snapshot, preventing [db] from
# checkpointing the database. Add a busy-handler to [db]. If [db2] completes
# its transaction from within the busy-handler, [db] is able to complete
# the checkpoint operation.
#
proc busyhandler x {
if {$x==4} {
execsql { COMMIT } db2
}
if {$x<5} {return 0}
return 1
}
db busy busyhandler
do_test wal-10.6 {
execsql { PRAGMA checkpoint }
} {}
# Similar to the test above. Except this time, a new read transaction is
# started (db3) while the checkpointer is waiting for an old one to finish.
# The checkpointer can finish, but any subsequent write operations must
# wait until after db3 has closed the read transaction.
#
db busy {}
do_test wal-10.7 {
execsql {
BEGIN;
SELECT * FROM t1;
} db2
} {1 2 3 4 5 6}
do_test wal-10.8 {
execsql { INSERT INTO t1 VALUES(7, 8) }
catchsql { PRAGMA checkpoint }
} {1 {database is locked}}
proc busyhandler x {
if {$x==3} { execsql { BEGIN; SELECT * FROM t1 } db3 }
if {$x==4} { execsql { COMMIT } db2 }
if {$x<5} { return 0 }
return 1
}
db busy busyhandler
do_test wal-10.9 {
execsql { PRAGMA checkpoint }
} {}
do_test wal-10.10 {
execsql { SELECT * FROM t1 } db3
} {1 2 3 4 5 6 7 8}
do_test wal-10.11 {
catchsql { INSERT INTO t1 VALUES(9, 10) }
} {1 {database is locked}}
do_test wal-10.12 {
execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8}
do_test wal-10.13 {
execsql { COMMIT } db3
} {}
do_test wal-10.14 {
execsql { INSERT INTO t1 VALUES(9, 10) }
execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10}
foreach handle {db db2 db3} { catch { $handle close } }
unset handle
finish_test