aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authordrh <drh@noemail.net>2010-06-21 12:47:41 +0000
committerdrh <drh@noemail.net>2010-06-21 12:47:41 +0000
commit24f0f7716a8963454d77e0224c98dc76f1eebfd2 (patch)
treef76f3b8d9477bb70be62d337bd579479b4d23078 /test
parent19515c8da1df2330c7689315dcd70fc02a4b4e28 (diff)
parente08341c664e41bb084a7a95bfb47b90b13868694 (diff)
downloadsqlite-24f0f7716a8963454d77e0224c98dc76f1eebfd2.tar.gz
sqlite-24f0f7716a8963454d77e0224c98dc76f1eebfd2.zip
Merge the experimental UNDELETABLE_WHEN_OPEN optimization into the trunk.
FossilOrigin-Name: ee0acef1faffd480fd2136f81fb2b6f6a17b5388
Diffstat (limited to 'test')
-rw-r--r--test/journal2.test228
-rw-r--r--test/jrnlmode2.test34
-rw-r--r--test/malloc_common.tcl33
-rw-r--r--test/pager1.test448
-rw-r--r--test/pager2.test117
-rw-r--r--test/pagerfault.test197
-rw-r--r--test/permutations.test2
-rw-r--r--test/tester.tcl10
-rw-r--r--test/walmode.test31
9 files changed, 1067 insertions, 33 deletions
diff --git a/test/journal2.test b/test/journal2.test
new file mode 100644
index 000000000..f7145ebdf
--- /dev/null
+++ b/test/journal2.test
@@ -0,0 +1,228 @@
+# 2010 June 16
+#
+# 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.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/lock_common.tcl
+source $testdir/malloc_common.tcl
+db close
+
+set a_string_counter 1
+proc a_string {n} {
+ global a_string_counter
+ incr a_string_counter
+ string range [string repeat "${a_string_counter}." $n] 1 $n
+}
+
+# Create a [testvfs] and install it as the default VFS. Set the device
+# characteristics flags to "SAFE_DELETE".
+#
+testvfs tvfs -default 1
+tvfs devchar undeletable_when_open
+
+# Set up a hook so that each time a journal file is opened, closed or
+# deleted, the method name ("xOpen", "xClose" or "xDelete") and the final
+# segment of the journal file-name (i.e. "test.db-journal") are appended to
+# global list variable $::oplog.
+#
+tvfs filter {xOpen xClose xDelete}
+tvfs script journal_op_catcher
+proc journal_op_catcher {method filename args} {
+
+ # If global variable ::tvfs_error_on_write is defined, then return an
+ # IO error to every attempt to modify the file-system. Otherwise, return
+ # SQLITE_OK.
+ #
+ if {[info exists ::tvfs_error_on_write]} {
+ if {[lsearch {xDelete xWrite xTruncate} $method]>=0} {
+ return SQLITE_IOERR
+ }
+ }
+
+ # The rest of this command only deals with xOpen(), xClose() and xDelete()
+ # operations on journal files. If this invocation does not represent such
+ # an operation, return with no further ado.
+ #
+ set f [file tail $filename]
+ if {[string match *journal $f]==0} return
+ if {[lsearch {xOpen xDelete xClose} $method]<0} return
+
+ # Append a record of this operation to global list variable $::oplog.
+ #
+ lappend ::oplog $method $f
+
+ # If this is an attempt to delete a journal file for which there exists
+ # one ore more open handles, return an error. The code in test_vfs.c
+ # will not invoke the xDelete method of the "real" VFS in this case.
+ #
+ if {[info exists ::open_journals($f)]==0} { set ::open_journals($f) 0 }
+ switch -- $method {
+ xOpen { incr ::open_journals($f) +1 }
+ xClose { incr ::open_journals($f) -1 }
+ xDelete { if {$::open_journals($f)>0} { return SQLITE_IOERR } }
+ }
+
+ return ""
+}
+
+
+do_test journal2-1.1 {
+ set ::oplog [list]
+ sqlite3 db test.db
+ execsql { CREATE TABLE t1(a, b) }
+ set ::oplog
+} {xOpen test.db-journal xClose test.db-journal xDelete test.db-journal}
+do_test journal2-1.2 {
+ set ::oplog [list]
+ execsql {
+ PRAGMA journal_mode = truncate;
+ INSERT INTO t1 VALUES(1, 2);
+ }
+ set ::oplog
+} {xOpen test.db-journal}
+do_test journal2-1.3 {
+ set ::oplog [list]
+ execsql { INSERT INTO t1 VALUES(3, 4) }
+ set ::oplog
+} {}
+do_test journal2-1.4 { execsql { SELECT * FROM t1 } } {1 2 3 4}
+
+# Add a second connection. This connection attempts to commit data in
+# journal_mode=DELETE mode. When it tries to delete the journal file,
+# the VFS layer returns an IO error.
+#
+do_test journal2-1.5 {
+ set ::oplog [list]
+ sqlite3 db2 test.db
+ execsql { PRAGMA journal_mode = delete } db2
+ catchsql { INSERT INTO t1 VALUES(5, 6) } db2
+} {1 {disk I/O error}}
+do_test journal2-1.6 { file exists test.db-journal } 1
+do_test journal2-1.7 { execsql { SELECT * FROM t1 } } {1 2 3 4}
+do_test journal2-1.8 {
+ execsql { PRAGMA journal_mode = truncate } db2
+ execsql { INSERT INTO t1 VALUES(5, 6) } db2
+} {}
+do_test journal2-1.9 { execsql { SELECT * FROM t1 } } {1 2 3 4 5 6}
+
+# Grow the database until it is reasonably large.
+#
+do_test journal2-1.10 {
+ db2 close
+ db func a_string a_string
+ execsql {
+ CREATE TABLE t2(a UNIQUE, b UNIQUE);
+ INSERT INTO t2 VALUES(a_string(200), a_string(300));
+ INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 2
+ INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 4
+ INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 8
+ INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 16
+ INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 32
+ INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 64
+ }
+ file size test.db-journal
+} {0}
+do_test journal2-1.11 {
+ set sz [expr [file size test.db] / 1024]
+ expr {$sz>120 && $sz<200}
+} 1
+
+# Using new connection [db2] (with journal_mode=DELETE), write a lot of
+# data to the database. So that many pages within the database file are
+# modified before the transaction is committed.
+#
+# Then, enable simulated IO errors in all calls to xDelete, xWrite
+# and xTruncate before committing the transaction and closing the
+# database file. From the point of view of other file-system users, it
+# appears as if the process hosting [db2] unexpectedly exited.
+#
+do_test journal2-1.12 {
+ sqlite3 db2 test.db
+ execsql {
+ PRAGMA cache_size = 10;
+ BEGIN;
+ INSERT INTO t2 SELECT randomblob(200), randomblob(300) FROM t2; -- 128
+ } db2
+} {}
+do_test journal2-1.13 {
+ tvfs filter {xOpen xClose xDelete xWrite xTruncate}
+ set ::tvfs_error_on_write 1
+ catchsql { COMMIT } db2
+} {1 {disk I/O error}}
+db2 close
+unset ::tvfs_error_on_write
+file copy -force test.db testX.db
+
+do_test journal2-1.14 { file exists test.db-journal } 1
+do_test journal2-1.15 {
+ execsql {
+ SELECT count(*) FROM t2;
+ PRAGMA integrity_check;
+ }
+} {64 ok}
+
+# This block checks that in the test case above, connection [db2] really
+# did begin writing to the database file before it hit IO errors. If
+# this is true, then the copy of the database file made before [db]
+# rolled back the hot journal should fail the integrity-check.
+#
+do_test journal2-1.16 {
+ set sz [expr [file size testX.db] / 1024]
+ expr {$sz>240 && $sz<400}
+} 1
+do_test journal2-1.17 {
+ expr {[catchsql { PRAGMA integrity_check } db] == "0 ok"}
+} {1}
+do_test journal2-1.20 {
+ sqlite3 db2 testX.db
+ expr {[catchsql { PRAGMA integrity_check } db2] == "0 ok"}
+} {0}
+do_test journal2-1.21 {
+ db2 close
+} {}
+db close
+
+#-------------------------------------------------------------------------
+# Test that it is possible to switch from journal_mode=truncate to
+# journal_mode=WAL on a SAFE_DELETE file-system. SQLite should close and
+# delete the journal file when committing the transaction that switches
+# the system to WAL mode.
+#
+ifcapable wal {
+ do_test journal2-2.1 {
+ faultsim_delete_and_reopen
+ set ::oplog [list]
+ execsql { PRAGMA journal_mode = persist }
+ set ::oplog
+ } {}
+ do_test journal2-2.2 {
+ execsql {
+ CREATE TABLE t1(x);
+ INSERT INTO t1 VALUES(3.14159);
+ }
+ set ::oplog
+ } {xOpen test.db-journal}
+ do_test journal2-2.3 {
+ expr {[file size test.db-journal] > 512}
+ } {1}
+ do_test journal2-2.4 {
+ set ::oplog [list]
+ execsql { PRAGMA journal_mode = WAL }
+ set ::oplog
+ } {xClose test.db-journal xDelete test.db-journal}
+ db close
+}
+
+tvfs delete
+finish_test
+
diff --git a/test/jrnlmode2.test b/test/jrnlmode2.test
index 9a1fe37c3..dc3bc270b 100644
--- a/test/jrnlmode2.test
+++ b/test/jrnlmode2.test
@@ -9,7 +9,6 @@
#
#***********************************************************************
#
-# $Id: jrnlmode2.test,v 1.6 2009/06/05 17:09:12 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
@@ -20,10 +19,33 @@ ifcapable {!pager_pragmas} {
}
#-------------------------------------------------------------------------
-# Test overview:
+# The tests in this file check that the following two bugs (both now fixed)
+# do not reappear.
#
-# jrnlmode2-1.*: Demonstrate bug #3745
-# jrnlmode2-2.*: Demonstrate bug #3751
+# jrnlmode2-1.*: Demonstrate bug #3745:
+#
+# In persistent journal mode, if:
+#
+# * There is a persistent journal in the file-system, AND
+# * there exists a connection with a shared lock on the db file,
+#
+# then a second connection cannot open a read-transaction on the database.
+# The reason is because while determining that the persistent-journal is
+# not a hot-journal, SQLite currently grabs an exclusive lock on the
+# database file. If this fails because another connection has a shared
+# lock, then SQLITE_BUSY is returned to the user.
+#
+# jrnlmode2-2.*: Demonstrate bug #3751:
+#
+# If a connection is opened in SQLITE_OPEN_READONLY mode, the underlying
+# unix file descriptor on the database file is opened in O_RDONLY mode.
+#
+# When SQLite queries the database file for the schema in order to compile
+# the SELECT statement, it sees the empty journal in the file system, it
+# attempts to obtain an exclusive lock on the database file (this is a
+# bug). The attempt to obtain an exclusive (write) lock on a read-only file
+# fails at the OS level. Under unix, fcntl() reports an EBADF - "Bad file
+# descriptor" - error.
#
do_test jrnlmode2-1.1 {
@@ -46,6 +68,8 @@ do_test jrnlmode2-1.3 {
do_test jrnlmode2-1.4 {
execsql {
INSERT INTO t1 VALUES(3, 4);
+ }
+ execsql {
BEGIN;
SELECT * FROM t1;
}
@@ -87,9 +111,9 @@ do_test jrnlmode2-2.4 {
} {0 {1 2 3 4 5 6}}
do_test jrnlmode2-2.5 {
+ db close
file delete test.db-journal
} {}
-
do_test jrnlmode2-2.6 {
sqlite3 db2 test.db -readonly 1
catchsql { SELECT * FROM t1 } db2
diff --git a/test/malloc_common.tcl b/test/malloc_common.tcl
index a24716cd4..8202b19af 100644
--- a/test/malloc_common.tcl
+++ b/test/malloc_common.tcl
@@ -49,6 +49,16 @@ set FAULTSIM(ioerr-persistent) [list \
-injecterrlist {{1 {disk I/O error}}} \
]
+# SQLITE_FULL errors (always persistent):
+#
+set FAULTSIM(full) [list \
+ -injectinstall fullerr_injectinstall \
+ -injectstart fullerr_injectstart \
+ -injectstop fullerr_injectstop \
+ -injecterrlist {{1 {database or disk is full}}} \
+ -injectuninstall fullerr_injectuninstall \
+]
+
# Transient and persistent SHM errors:
#
set FAULTSIM(shmerr-transient) [list \
@@ -126,14 +136,14 @@ proc faultsim_save_and_close {} {
catch { db close }
return ""
}
-proc faultsim_restore_and_reopen {} {
+proc faultsim_restore_and_reopen {{dbfile test.db}} {
catch { db close }
foreach f [glob -nocomplain test.db*] { file delete -force $f }
foreach f2 [glob -nocomplain sv_test.db*] {
set f [string range $f2 3 end]
file copy -force $f2 $f
}
- sqlite3 db test.db
+ sqlite3 db $dbfile
sqlite3_extended_result_codes db 1
sqlite3_db_config_lookaside db 0 0 0
}
@@ -146,7 +156,7 @@ proc faultsim_integrity_check {{db db}} {
proc faultsim_delete_and_reopen {{file test.db}} {
catch { db close }
foreach f [glob -nocomplain test.db*] { file delete -force $f }
- sqlite3 db test.db
+ sqlite3 db $file
}
@@ -177,6 +187,7 @@ proc ioerr_injectstop {} {
return $sv
}
+
# The following procs are used as [do_one_faultsim_test] callbacks when
# injecting shared-memory related error faults into test cases.
#
@@ -195,6 +206,22 @@ proc shmerr_injectstop {} {
shmfault ioerr 0 0
}
+proc fullerr_injectinstall {} {
+ testvfs shmfault -default true
+}
+proc fullerr_injectuninstall {} {
+ catch {db close}
+ catch {db2 close}
+ shmfault delete
+}
+proc fullerr_injectstart {iFail} {
+ shmfault full $iFail
+}
+proc fullerr_injectstop {} {
+ shmfault full 0
+}
+
+
# This command is not called directly. It is used by the
# [faultsim_test_result] command created by [do_faultsim_test] and used
# by -test scripts.
diff --git a/test/pager1.test b/test/pager1.test
index d0caaa9d4..f7a821b6d 100644
--- a/test/pager1.test
+++ b/test/pager1.test
@@ -14,6 +14,7 @@ set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
source $testdir/malloc_common.tcl
+source $testdir/wal_common.tcl
#
# pager1-1.*: Test inter-process locking (clients in multiple processes).
@@ -22,13 +23,14 @@ source $testdir/malloc_common.tcl
#
# pager1-3.*: Savepoint related tests.
#
-
-proc do_execsql_test {testname sql result} {
- uplevel do_test $testname [list "execsql {$sql}"] [list $result]
-}
-proc do_catchsql_test {testname sql result} {
- uplevel do_test $testname [list "catchsql {$sql}"] [list $result]
-}
+# pager1-4.*: Hot-journal related tests.
+#
+# pager1-5.*: Cases related to multi-file commits.
+#
+# pager1-6.*: Cases related to "PRAGMA max_page_count"
+#
+# pager1-7.*: Cases specific to "PRAGMA journal_mode=TRUNCATE"
+#
set a_string_counter 1
proc a_string {n} {
@@ -221,6 +223,17 @@ do_execsql_test pager1-3.6 { COMMIT } {}
# compute) the associated journal is rolled back (and no
# xAccess() call to check for the presence of any master
# journal file is made).
+#
+# pager1.4.3.*: Test that the contents of a hot-journal are ignored if the
+# page-size or sector-size in the journal header appear to
+# be invalid (too large, too small or not a power of 2).
+#
+# pager1.4.4.*: Test hot-journal rollback of journal file with a master
+# journal pointer generated in various "PRAGMA synchronous"
+# modes.
+#
+# pager1.4.5.*: Test that hot-journal rollback stops if it encounters a
+# journal-record for which the checksum fails.
#
do_test pager1.4.1.1 {
faultsim_delete_and_reopen
@@ -308,5 +321,426 @@ do_test pager1.4.2.5 {
}
} {4 ok}
+do_test pager1.4.3.1 {
+ testvfs tstvfs -default 1
+ tstvfs filter xSync
+ tstvfs script xSyncCallback
+ proc xSyncCallback {method file args} {
+ set file [file tail $file]
+ if { 0==[string match *journal $file] } { faultsim_save }
+ }
+ faultsim_delete_and_reopen
+ execsql {
+ PRAGMA journal_mode = DELETE;
+ CREATE TABLE t1(a, b);
+ INSERT INTO t1 VALUES(1, 2);
+ INSERT INTO t1 VALUES(3, 4);
+ }
+ db close
+ tstvfs delete
+} {}
+
+foreach {tn ofst value result} {
+ 2 20 31 {1 2 3 4}
+ 3 20 32 {1 2 3 4}
+ 4 20 33 {1 2 3 4}
+ 5 20 65536 {1 2 3 4}
+ 6 20 131072 {1 2 3 4}
+
+ 7 24 511 {1 2 3 4}
+ 8 24 513 {1 2 3 4}
+ 9 24 65536 {1 2 3 4}
+
+ 10 32 65536 {1 2}
+} {
+ do_test pager1.4.3.$tn {
+ faultsim_restore_and_reopen
+ hexio_write test.db-journal $ofst [format %.8x $value]
+ execsql { SELECT * FROM t1 }
+ } $result
+}
+db close
+
+# Set up a VFS that snapshots the file-system just before a master journal
+# file is deleted to commit a multi-file transaction. Specifically, the
+# file-system is saved just before the xDelete() call to remove the
+# master journal file from the file-system.
+#
+testvfs tv -default 1
+tv script copy_on_mj_delete
+set ::mj_filename_length 0
+proc copy_on_mj_delete {method filename args} {
+ if {[string match *mj* [file tail $filename]]} {
+ set ::mj_filename_length [string length $filename]
+ faultsim_save
+ }
+ return SQLITE_OK
+}
+
+set pwd [pwd]
+foreach {tn1 tcl} {
+ 1 { set prefix "test.db" }
+ 2 {
+ # This test depends on the underlying VFS being able to open paths
+ # 512 bytes in length. The idea is to create a hot-journal file that
+ # contains a master-journal pointer so large that it could contain
+ # a valid page record (if the file page-size is 512 bytes). So as to
+ # make sure SQLite doesn't get confused by this.
+ #
+ set nPadding [expr 511 - $::mj_filename_length]
+
+ # We cannot just create a really long database file name to open, as
+ # Linux limits a single component of a path to 255 bytes by default
+ # (and presumably other systems have limits too). So create a directory
+ # hierarchy to work in.
+ #
+ set dirname "d123456789012345678901234567890/"
+ set nDir [expr $nPadding / 32]
+ if { $nDir } {
+ set p [string repeat $dirname $nDir]
+ file mkdir $p
+ cd $p
+ }
+
+ set padding [string repeat x [expr $nPadding %32]]
+ set prefix "test.db${padding}"
+ }
+} {
+ eval $tcl
+ foreach {tn2 sql} {
+ o {
+ PRAGMA main.synchronous=OFF;
+ PRAGMA aux.synchronous=OFF;
+ }
+ o512 {
+ PRAGMA main.synchronous=OFF;
+ PRAGMA aux.synchronous=OFF;
+ PRAGMA main.page_size = 512;
+ PRAGMA aux.page_size = 512;
+ }
+ n {
+ PRAGMA main.synchronous=NORMAL;
+ PRAGMA aux.synchronous=NORMAL;
+ }
+ f {
+ PRAGMA main.synchronous=FULL;
+ PRAGMA aux.synchronous=FULL;
+ }
+ } {
+
+ set tn "${tn1}.${tn2}"
+
+ # Set up a connection to have two databases, test.db (main) and
+ # test.db2 (aux). Then run a multi-file transaction on them. The
+ # VFS will snapshot the file-system just before the master-journal
+ # file is deleted to commit the transaction.
+ #
+ tv filter xDelete
+ do_test pager1-4.4.$tn.1 {
+ faultsim_delete_and_reopen $prefix
+ execsql "
+ ATTACH '${prefix}2' AS aux;
+ $sql
+ CREATE TABLE a(x);
+ CREATE TABLE aux.b(x);
+ INSERT INTO a VALUES('double-you');
+ INSERT INTO a VALUES('why');
+ INSERT INTO a VALUES('zed');
+ INSERT INTO b VALUES('won');
+ INSERT INTO b VALUES('too');
+ INSERT INTO b VALUES('free');
+ "
+ execsql {
+ BEGIN;
+ INSERT INTO a SELECT * FROM b WHERE rowid<=3;
+ INSERT INTO b SELECT * FROM a WHERE rowid<=3;
+ COMMIT;
+ }
+ } {}
+ tv filter {}
+
+ # Check that the transaction was committed successfully.
+ #
+ do_execsql_test pager1-4.4.$tn.2 {
+ SELECT * FROM a
+ } {double-you why zed won too free}
+ do_execsql_test pager1-4.4.$tn.3 {
+ SELECT * FROM b
+ } {won too free double-you why zed}
+
+ # Restore the file-system and reopen the databases. Check that it now
+ # appears that the transaction was not committed (because the file-system
+ # was restored to the state where it had not been).
+ #
+ do_test pager1-4.4.$tn.4 {
+ faultsim_restore_and_reopen $prefix
+ execsql "ATTACH '${prefix}2' AS aux"
+ } {}
+ do_execsql_test pager1-4.4.$tn.5 {SELECT * FROM a} {double-you why zed}
+ do_execsql_test pager1-4.4.$tn.6 {SELECT * FROM b} {won too free}
+
+ # Restore the file-system again. This time, before reopening the databases,
+ # delete the master-journal file from the file-system. It now appears that
+ # the transaction was committed (no master-journal file == no rollback).
+ #
+ do_test pager1-4.4.$tn.7 {
+ faultsim_restore_and_reopen $prefix
+ foreach f [glob ${prefix}-mj*] { file delete -force $f }
+ execsql "ATTACH '${prefix}2' AS aux"
+ } {}
+ do_execsql_test pager1-4.4.$tn.8 {
+ SELECT * FROM a
+ } {double-you why zed won too free}
+ do_execsql_test pager1-4.4.$tn.9 {
+ SELECT * FROM b
+ } {won too free double-you why zed}
+ }
+
+ cd $pwd
+}
+db close
+tv delete
+
+#-------------------------------------------------------------------------
+# The following tests deal with multi-file commits.
+#
+# pager1-5.1.*: The case where a multi-file cannot be committed because
+# another connection is holding a SHARED lock on one of the
+# files. After the SHARED lock is removed, the COMMIT succeeds.
+#
+# pager1-5.2.*: Multi-file commits with journal_mode=memory.
+#
+# pager1-5.3.*: Multi-file commits with journal_mode=memory.
+#
+# pager1-5.4.*: Check that with synchronous=normal, the master-journal file
+# name is added to a journal file immediately after the last
+# journal record. But with synchronous=full, extra unused space
+# is allocated between the last journal record and the
+# master-journal file name so that the master-journal file
+# name does not lie on the same sector as the last journal file
+# record.
+#
+# pager1-5.5.*: Check that in journal_mode=PERSIST mode, a journal file is
+# truncated to zero bytes when a multi-file transaction is
+# committed (instead of the first couple of bytes being zeroed).
+#
+#
+do_test pager1-5.1.1 {
+ faultsim_delete_and_reopen
+ execsql {
+ ATTACH 'test.db2' AS aux;
+ CREATE TABLE t1(a, b);
+ CREATE TABLE aux.t2(a, b);
+ INSERT INTO t1 VALUES(17, 'Lenin');
+ INSERT INTO t1 VALUES(22, 'Stalin');
+ INSERT INTO t1 VALUES(53, 'Khrushchev');
+ }
+} {}
+do_test pager1-5.1.2 {
+ execsql {
+ BEGIN;
+ INSERT INTO t1 VALUES(64, 'Brezhnev');
+ INSERT INTO t2 SELECT * FROM t1;
+ }
+ sqlite3 db2 test.db2
+ execsql {
+ BEGIN;
+ SELECT * FROM t2;
+ } db2
+} {}
+do_test pager1-5.1.3 {
+ catchsql COMMIT
+} {1 {database is locked}}
+do_test pager1-5.1.4 {
+ execsql COMMIT db2
+ execsql COMMIT
+ execsql { SELECT * FROM t2 } db2
+} {17 Lenin 22 Stalin 53 Khrushchev 64 Brezhnev}
+do_test pager1-5.1.5 {
+ db2 close
+} {}
+
+do_test pager1-5.2.1 {
+ execsql {
+ PRAGMA journal_mode = memory;
+ BEGIN;
+ INSERT INTO t1 VALUES(84, 'Andropov');
+ INSERT INTO t2 VALUES(84, 'Andropov');
+ COMMIT;
+ }
+} {memory}
+do_test pager1-5.3.1 {
+ execsql {
+ PRAGMA journal_mode = off;
+ BEGIN;
+ INSERT INTO t1 VALUES(85, 'Gorbachev');
+ INSERT INTO t2 VALUES(85, 'Gorbachev');
+ COMMIT;
+ }
+} {off}
+
+do_test pager1-5.4.1 {
+ db close
+ testvfs tv
+ sqlite3 db test.db -vfs tv
+ execsql { ATTACH 'test.db2' AS aux }
+
+ tv filter xDelete
+ tv script max_journal_size
+ tv sectorsize 512
+ set ::max_journal 0
+ proc max_journal_size {method args} {
+ set sz 0
+ catch { set sz [file size test.db-journal] }
+ if {$sz > $::max_journal} {
+ set ::max_journal $sz
+ }
+ return SQLITE_OK
+ }
+ execsql {
+ PRAGMA journal_mode = DELETE;
+ PRAGMA synchronous = NORMAL;
+ BEGIN;
+ INSERT INTO t1 VALUES(85, 'Gorbachev');
+ INSERT INTO t2 VALUES(85, 'Gorbachev');
+ COMMIT;
+ }
+ set ::max_journal
+} [expr 2615+[string length [pwd]]]
+do_test pager1-5.4.2 {
+ set ::max_journal 0
+ execsql {
+ PRAGMA synchronous = full;
+ BEGIN;
+ DELETE FROM t1 WHERE b = 'Lenin';
+ DELETE FROM t2 WHERE b = 'Lenin';
+ COMMIT;
+ }
+ set ::max_journal
+} [expr 3111+[string length [pwd]]]
+db close
+tv delete
+
+do_test pager1-5.5.1 {
+ sqlite3 db test.db
+ execsql {
+ ATTACH 'test.db2' AS aux;
+ PRAGMA journal_mode = PERSIST;
+ CREATE TABLE t3(a, b);
+ INSERT INTO t3 SELECT randomblob(1500), randomblob(1500) FROM t1;
+ UPDATE t3 SET b = randomblob(1500);
+ }
+ expr [file size test.db-journal] > 15000
+} {1}
+do_test pager1-5.5.2 {
+ execsql {
+ PRAGMA synchronous = full;
+ BEGIN;
+ DELETE FROM t1 WHERE b = 'Stalin';
+ DELETE FROM t2 WHERE b = 'Stalin';
+ COMMIT;
+ }
+ file size test.db-journal
+} {0}
+
+
+#-------------------------------------------------------------------------
+# The following tests work with "PRAGMA max_page_count"
+#
+do_test pager1-6.1 {
+ faultsim_delete_and_reopen
+ execsql {
+ PRAGMA max_page_count = 10;
+ CREATE TABLE t2(a, b);
+ CREATE TABLE t3(a, b);
+ CREATE TABLE t4(a, b);
+ CREATE TABLE t5(a, b);
+ CREATE TABLE t6(a, b);
+ CREATE TABLE t7(a, b);
+ CREATE TABLE t8(a, b);
+ CREATE TABLE t9(a, b);
+ CREATE TABLE t10(a, b);
+ }
+} {10}
+do_test pager1-6.2 {
+ catchsql {
+ CREATE TABLE t11(a, b);
+ }
+} {1 {database or disk is full}}
+
+
+#-------------------------------------------------------------------------
+# The following tests work with "PRAGMA journal_mode=TRUNCATE" and
+# "PRAGMA locking_mode=EXCLUSIVE".
+#
+# Each test is specified with 5 variables. As follows:
+#
+# $tn: Test Number. Used as part of the [do_test] test names.
+# $sql: SQL to execute.
+# $res: Expected result of executing $sql.
+# $js: The expected size of the journal file, in bytes, after executing
+# the SQL script. Or -1 if the journal is not expected to exist.
+# $ws: The expected size of the WAL file, in bytes, after executing
+# the SQL script. Or -1 if the WAL is not expected to exist.
+#
+faultsim_delete_and_reopen
+foreach {tn sql res js ws} [subst {
+
+ 1 {
+ CREATE TABLE t1(a, b);
+ PRAGMA auto_vacuum=OFF;
+ PRAGMA synchronous=NORMAL;
+ PRAGMA page_size=1024;
+ PRAGMA locking_mode=EXCLUSIVE;
+ PRAGMA journal_mode=TRUNCATE;
+ INSERT INTO t1 VALUES(1, 2);
+ } {exclusive truncate} 0 -1
+
+ 2 {
+ BEGIN IMMEDIATE;
+ SELECT * FROM t1;
+ COMMIT;
+ } {1 2} 0 -1
+
+ 3 {
+ BEGIN;
+ SELECT * FROM t1;
+ COMMIT;
+ } {1 2} 0 -1
+
+ 4 { PRAGMA journal_mode = WAL } wal -1 -1
+ 5 { INSERT INTO t1 VALUES(3, 4) } {} -1 [wal_file_size 1 1024]
+ 6 { PRAGMA locking_mode = NORMAL } normal -1 [wal_file_size 1 1024]
+ 7 { INSERT INTO t1 VALUES(5, 6); } {} -1 [wal_file_size 2 1024]
+
+ 8 { PRAGMA journal_mode = TRUNCATE } truncate 0 -1
+ 9 { INSERT INTO t1 VALUES(7, 8) } {} 0 -1
+ 10 { SELECT * FROM t1 } {1 2 3 4 5 6 7 8} 0 -1
+
+}] {
+ do_execsql_test pager1-7.1.$tn.1 $sql $res
+ catch { set J -1 ; set J [file size test.db-journal] }
+ catch { set W -1 ; set W [file size test.db-wal] }
+ do_test pager1-7.1.$tn.2 { list $J $W } [list $js $ws]
+}
+
+do_test pager1-8.1 {
+ faultsim_delete_and_reopen
+ db close
+ sqlite3 db :memory:
+ execsql {
+ CREATE TABLE x1(x);
+ INSERT INTO x1 VALUES('Charles');
+ INSERT INTO x1 VALUES('James');
+ INSERT INTO x1 VALUES('Mary');
+ SELECT * FROM x1;
+ }
+} {Charles James Mary}
+do_test pager1-8.2 {
+ db close
+ sqlite3 db :memory:
+ catchsql { SELECT * FROM x1 }
+} {1 {no such table: x1}}
+
finish_test
diff --git a/test/pager2.test b/test/pager2.test
new file mode 100644
index 000000000..855850f05
--- /dev/null
+++ b/test/pager2.test
@@ -0,0 +1,117 @@
+# 2010 June 15
+#
+# 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.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/lock_common.tcl
+source $testdir/malloc_common.tcl
+
+set otn 0
+testvfs tv -default 1
+foreach code [list {
+ set s 512
+} {
+ set s 1024
+ set sql { PRAGMA journal_mode = memory }
+} {
+ set s 1024
+ set sql {
+ PRAGMA journal_mode = memory;
+ PRAGMA locking_mode = exclusive;
+ }
+} {
+ set s 2048
+ tv devchar safe_append
+} {
+ set s 4096
+} {
+ set s 4096
+ set sql { PRAGMA journal_mode = WAL }
+} {
+ set s 8192
+ set sql { PRAGMA synchronous = off }
+}] {
+
+ incr otn
+ set sql ""
+ tv devchar {}
+ eval $code
+ tv sectorsize $s
+
+ do_test pager2-1.$otn.0 {
+ faultsim_delete_and_reopen
+ execsql $sql
+ execsql {
+ PRAGMA cache_size = 10;
+ CREATE TABLE t1(i INTEGER PRIMARY KEY, j blob);
+ }
+ } {}
+
+ set tn 0
+ set lowpoint 0
+ foreach x {
+ 100 x 0 100
+ x
+ 70 22 96 59 96 50 22 56 21 16 37 64 43 40 0 38 22 38 55 0 6
+ 43 62 32 93 54 18 13 29 45 66 29 25 61 31 53 82 75 25 96 86 10 69
+ 2 29 6 60 80 95 42 82 85 50 68 96 90 39 78 69 87 97 48 74 65 43
+ x
+ 86 34 26 50 41 85 58 44 89 22 6 51 45 46 58 32 97 6 1 12 32 2
+ 69 39 48 71 33 31 5 58 90 43 24 54 12 9 18 57 4 38 91 42 27 45
+ 50 38 56 29 10 0 26 37 83 1 78 15 47 30 75 62 46 29 68 5 30 4
+ 27 96 33 95 79 75 56 10 29 70 32 75 52 88 5 36 50 57 46 63 88 65
+ x
+ 44 95 64 20 24 35 69 61 61 2 35 92 42 46 23 98 78 1 38 72 79 35
+ 94 37 13 59 5 93 27 58 80 75 58 7 67 13 10 76 84 4 8 70 81 45
+ 8 41 98 5 60 26 92 29 91 90 2 62 40 4 5 22 80 15 83 76 52 88
+ 29 5 68 73 72 7 54 17 89 32 81 94 51 28 53 71 8 42 54 59 70 79
+ x
+ } {
+ incr tn
+ set now [db one {SELECT count(i) FROM t1}]
+ if {$x == "x"} {
+ execsql { COMMIT ; BEGIN }
+ set lowpoint $now
+ do_test pager2.1.$otn.$tn {
+ sqlite3 db2 test.db
+ execsql {
+ SELECT COALESCE(max(i), 0) FROM t1;
+ PRAGMA integrity_check;
+ }
+ } [list $lowpoint ok]
+ db2 close
+ } else {
+ if {$now > $x } {
+ if { $x>=$lowpoint } {
+ execsql "ROLLBACK TO sp_$x"
+ } else {
+ execsql "DELETE FROM t1 WHERE i>$x"
+ set lowpoint $x
+ }
+ } elseif {$now < $x} {
+ for {set k $now} {$k < $x} {incr k} {
+ execsql "SAVEPOINT sp_$k"
+ execsql { INSERT INTO t1(j) VALUES(randomblob(1500)) }
+ }
+ }
+ do_execsql_test pager2.1.$otn.$tn {
+ SELECT COALESCE(max(i), 0) FROM t1;
+ PRAGMA integrity_check;
+ } [list $x ok]
+ }
+ }
+}
+db close
+tv delete
+
+
+finish_test
diff --git a/test/pagerfault.test b/test/pagerfault.test
index 8dc99e0bc..0af8d5180 100644
--- a/test/pagerfault.test
+++ b/test/pagerfault.test
@@ -55,10 +55,55 @@ do_faultsim_test pagerfault-1 -prep {
}
#-------------------------------------------------------------------------
+# Test fault-injection while rolling back a hot-journal file with a
+# page-size different from the current value stored on page 1 of the
+# database file.
+#
+do_test pagerfault-2-pre1 {
+ testvfs tv -default 1
+ tv filter xSync
+ tv script xSyncCb
+ proc xSyncCb {filename args} {
+ if {[string match *journal filename]==0} faultsim_save
+ }
+ faultsim_delete_and_reopen
+ execsql {
+ PRAGMA page_size = 4096;
+ BEGIN;
+ CREATE TABLE abc(a, b, c);
+ INSERT INTO abc VALUES('o', 't', 't');
+ INSERT INTO abc VALUES('f', 'f', 's');
+ INSERT INTO abc SELECT * FROM abc; -- 4
+ INSERT INTO abc SELECT * FROM abc; -- 8
+ INSERT INTO abc SELECT * FROM abc; -- 16
+ INSERT INTO abc SELECT * FROM abc; -- 32
+ INSERT INTO abc SELECT * FROM abc; -- 64
+ INSERT INTO abc SELECT * FROM abc; -- 128
+ INSERT INTO abc SELECT * FROM abc; -- 256
+ COMMIT;
+ PRAGMA page_size = 1024;
+ VACUUM;
+ }
+ db close
+ tv delete
+} {}
+do_faultsim_test pagerfault-2 -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { SELECT * FROM abc }
+} -test {
+ set answer [split [string repeat "ottffs" 128] ""]
+ faultsim_test_result [list 0 $answer]
+ faultsim_integrity_check
+ set res [db eval { SELECT * FROM abc }]
+ if {$res != $answer} { error "Database content appears incorrect ($res)" }
+} -faults oom-transient
+
+#-------------------------------------------------------------------------
# Test fault-injection while rolling back hot-journals that were created
# as part of a multi-file transaction.
#
-do_test pagerfault-2-pre1 {
+do_test pagerfault-3-pre1 {
testvfs tstvfs -default 1
tstvfs filter xDelete
tstvfs script xDeleteCallback
@@ -96,7 +141,7 @@ do_test pagerfault-2-pre1 {
db close
tstvfs delete
} {}
-do_faultsim_test pagerfault-2 -faults ioerr-persistent -prep {
+do_faultsim_test pagerfault-3 -faults ioerr-persistent -prep {
faultsim_restore_and_reopen
} -body {
execsql {
@@ -107,7 +152,6 @@ do_faultsim_test pagerfault-2 -faults ioerr-persistent -prep {
} -test {
faultsim_test_result {0 {4 4}} {1 {unable to open database: test.db2}}
faultsim_integrity_check
-
catchsql { ATTACH 'test.db2' AS aux }
if {[db one { SELECT count(*) FROM t1 }] != 4
|| [db one { SELECT count(*) FROM t2 }] != 4
@@ -116,4 +160,151 @@ do_faultsim_test pagerfault-2 -faults ioerr-persistent -prep {
}
}
+#-------------------------------------------------------------------------
+# Test fault-injection as part of a vanilla, no-transaction, INSERT
+# statement.
+#
+do_faultsim_test pagerfault-4 -prep {
+ faultsim_delete_and_reopen
+} -body {
+ execsql {
+ CREATE TABLE x(y);
+ INSERT INTO x VALUES('z');
+ SELECT * FROM x;
+ }
+} -test {
+ faultsim_test_result {0 z}
+ faultsim_integrity_check
+}
+
+#-------------------------------------------------------------------------
+# Test fault-injection as part of a commit when using journal_mode=PERSIST.
+# Three different cases:
+#
+# pagerfault-5.1: With no journal_size_limit configured.
+# pagerfault-5.2: With a journal_size_limit configured.
+# pagerfault-5.4: Multi-file transaction. One connection has a
+# journal_size_limit of 0, the other has no limit.
+#
+do_test pagerfault-5-pre1 {
+ faultsim_delete_and_reopen
+ db func a_string a_string
+ execsql {
+ CREATE TABLE t1(a UNIQUE, b UNIQUE);
+ INSERT INTO t1 VALUES(a_string(200), a_string(300));
+ INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1;
+ INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1;
+ }
+ faultsim_save_and_close
+} {}
+do_faultsim_test pagerfault-5.1 -prep {
+ faultsim_restore_and_reopen
+ db func a_string a_string
+ execsql { PRAGMA journal_mode = PERSIST }
+} -body {
+ execsql { INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1 }
+} -test {
+ faultsim_test_result {0 {}}
+ faultsim_integrity_check
+}
+do_faultsim_test pagerfault-5.2 -prep {
+ faultsim_restore_and_reopen
+ db func a_string a_string
+ execsql {
+ PRAGMA journal_mode = PERSIST;
+ PRAGMA journal_size_limit = 2048;
+ }
+} -body {
+ execsql { INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1 }
+} -test {
+ faultsim_test_result {0 {}}
+ faultsim_integrity_check
+}
+do_faultsim_test pagerfault-5.3 -prep {
+ faultsim_restore_and_reopen
+ db func a_string a_string
+ file delete -force test2.db test2.db-journal test2.db-wal
+ execsql {
+ PRAGMA journal_mode = PERSIST;
+ ATTACH 'test2.db' AS aux;
+ PRAGMA aux.journal_mode = PERSIST;
+ PRAGMA aux.journal_size_limit = 0;
+ }
+} -body {
+ execsql {
+ BEGIN;
+ INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1;
+ CREATE TABLE aux.t2 AS SELECT * FROM t1;
+ COMMIT;
+ }
+} -test {
+ faultsim_test_result {0 {}}
+}
+
+#-------------------------------------------------------------------------
+# Test fault-injection as part of a commit when using
+# journal_mode=TRUNCATE.
+#
+do_test pagerfault-6-pre1 {
+ faultsim_delete_and_reopen
+ db func a_string a_string
+ execsql {
+ CREATE TABLE t1(a UNIQUE, b UNIQUE);
+ INSERT INTO t1 VALUES(a_string(200), a_string(300));
+ }
+ faultsim_save_and_close
+} {}
+do_faultsim_test pagerfault-6.1 -prep {
+ faultsim_restore_and_reopen
+ db func a_string a_string
+ execsql { PRAGMA journal_mode = TRUNCATE }
+} -body {
+ execsql { INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1 }
+} -test {
+ faultsim_test_result {0 {}}
+ faultsim_integrity_check
+}
+
+# The following was an attempt to get a bitvec malloc to fail. Didn't work.
+#
+# do_test pagerfault-6-pre1 {
+# faultsim_delete_and_reopen
+# execsql {
+# CREATE TABLE t1(x, y, UNIQUE(x, y));
+# INSERT INTO t1 VALUES(1, randomblob(1501));
+# INSERT INTO t1 VALUES(2, randomblob(1502));
+# INSERT INTO t1 VALUES(3, randomblob(1503));
+# INSERT INTO t1 VALUES(4, randomblob(1504));
+# INSERT INTO t1
+# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1;
+# INSERT INTO t1
+# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1;
+# INSERT INTO t1
+# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1;
+# INSERT INTO t1
+# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1;
+# }
+# faultsim_save_and_close
+# } {}
+# do_faultsim_test pagerfault-6 -prep {
+# faultsim_restore_and_reopen
+# } -body {
+# execsql {
+# BEGIN;
+# UPDATE t1 SET x=x+4 WHERE x=1;
+# SAVEPOINT one;
+# UPDATE t1 SET x=x+4 WHERE x=2;
+# SAVEPOINT three;
+# UPDATE t1 SET x=x+4 WHERE x=3;
+# SAVEPOINT four;
+# UPDATE t1 SET x=x+4 WHERE x=4;
+# RELEASE three;
+# COMMIT;
+# SELECT DISTINCT x FROM t1;
+# }
+# } -test {
+# faultsim_test_result {0 {5 6 7 8}}
+# faultsim_integrity_check
+# }
+
finish_test
diff --git a/test/permutations.test b/test/permutations.test
index 921df33a4..e1846c13a 100644
--- a/test/permutations.test
+++ b/test/permutations.test
@@ -169,7 +169,9 @@ test_suite "coverage-pager" -description {
Coverage tests for file pager.c.
} -files {
pager1.test
+ pager2.test
pagerfault.test
+ journal2.test
}
diff --git a/test/tester.tcl b/test/tester.tcl
index 625bdcc3e..d6ce7eac2 100644
--- a/test/tester.tcl
+++ b/test/tester.tcl
@@ -49,6 +49,8 @@
# crashsql ARGS...
# integrity_check TESTNAME ?DB?
# do_test TESTNAME SCRIPT EXPECTED
+# do_execsql_test TESTNAME SQL EXPECTED
+# do_catchsql_test TESTNAME SQL EXPECTED
#
# Commands providing a lower level interface to the global test counters:
#
@@ -316,6 +318,14 @@ proc do_test {name cmd expected} {
}
flush stdout
}
+
+proc do_execsql_test {testname sql result} {
+ uplevel do_test $testname [list "execsql {$sql}"] [list $result]
+}
+proc do_catchsql_test {testname sql result} {
+ uplevel do_test $testname [list "catchsql {$sql}"] [list $result]
+}
+
# Run an SQL script.
# Return the number of microseconds per statement.
diff --git a/test/walmode.test b/test/walmode.test
index 714e0d818..48979b19c 100644
--- a/test/walmode.test
+++ b/test/walmode.test
@@ -124,46 +124,48 @@ do_test walmode-4.5 {
# from WAL to rollback mode because a second connection has the database
# open. Or from rollback to WAL.
#
-do_test walmode-4.1 {
+do_test walmode-4.6 {
sqlite3 db2 test.db
execsql { PRAGMA main.journal_mode } db2
} {delete}
-do_test walmode-4.2 {
+do_test walmode-4.7 {
execsql { PRAGMA main.journal_mode = wal } db
} {wal}
-do_test walmode-4.3 {
+do_test walmode-4.8 {
execsql { SELECT * FROM t1 } db2
} {1 2}
-do_test walmode-4.4 {
+do_test walmode-4.9 {
catchsql { PRAGMA journal_mode = delete } db
} {1 {database is locked}}
-do_test walmode-4.5 {
+do_test walmode-4.10 {
execsql { PRAGMA main.journal_mode } db
} {wal}
-do_test walmode-4.6 {
+
+do_test walmode-4.11 {
db2 close
execsql { PRAGMA journal_mode = delete } db
} {delete}
-do_test walmode-4.7 {
+do_test walmode-4.12 {
execsql { PRAGMA main.journal_mode } db
} {delete}
-do_test walmode-4.8 {
+do_test walmode-4.13 {
list [file exists test.db-journal] [file exists test.db-wal]
} {0 0}
-do_test walmode-4.9 {
+do_test walmode-4.14 {
sqlite3 db2 test.db
execsql {
BEGIN;
SELECT * FROM t1;
} db2
} {1 2}
-do_test walmode-4.11 {
- execsql { PRAGMA main.journal_mode } db
-} {delete}
-do_test walmode-4.10 {
+
+do_test walmode-4.16 { execsql { PRAGMA main.journal_mode } db } {delete}
+do_test walmode-4.17 { execsql { PRAGMA main.journal_mode } db2 } {delete}
+
+do_test walmode-4.17 {
catchsql { PRAGMA main.journal_mode = wal } db
} {1 {database is locked}}
-do_test walmode-4.11 {
+do_test walmode-4.18 {
execsql { PRAGMA main.journal_mode } db
} {delete}
catch { db close }
@@ -180,7 +182,6 @@ do_test walmode-5.1.1 {
sqlite3 db :memory:
execsql { PRAGMA main.journal_mode }
} {memory}
-breakpoint
do_test walmode-5.1.2 {
execsql { PRAGMA main.journal_mode = wal }
} {memory}