1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
|
# 2008 May 12
#
# 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 tests that if sqlite3_release_memory() is called to reclaim
# memory from a pager that is in the error-state, SQLite does not
# incorrectly write dirty pages out to the database (not safe to do
# once the pager is in error state).
#
# $Id: ioerr5.test,v 1.5 2008/08/28 18:35:34 danielk1977 Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
ifcapable !memorymanage||!shared_cache {
finish_test
return
}
db close
set ::enable_shared_cache [sqlite3_enable_shared_cache 1]
set ::soft_limit [sqlite3_soft_heap_limit 1048576]
# This procedure prepares, steps and finalizes an SQL statement via the
# UTF-16 APIs. The text representation of an SQLite error code is returned
# ("SQLITE_OK", "SQLITE_IOERR" etc.). The actual results returned by the
# SQL statement, if it is a SELECT, are not available.
#
# This can be useful for testing because it forces SQLite to make an extra
# call to sqlite3_malloc() when translating from the supplied UTF-16 to
# the UTF-8 encoding used internally.
#
proc dosql16 {zSql {db db}} {
set sql [encoding convertto unicode $zSql]
append sql "\00\00"
set stmt [sqlite3_prepare16 $db $sql -1 {}]
sqlite3_step $stmt
set rc [sqlite3_finalize $stmt]
}
proc compilesql16 {zSql {db db}} {
set sql [encoding convertto unicode $zSql]
append sql "\00\00"
set stmt [sqlite3_prepare16 $db $sql -1 {}]
set rc [sqlite3_finalize $stmt]
}
# Open two database connections (handle db and db2) to database "test.db".
#
proc opendatabases {} {
catch {db close}
catch {db2 close}
sqlite3 db test.db
sqlite3 db2 test.db
db2 cache size 0
db cache size 0
execsql {
pragma page_size=512;
pragma auto_vacuum=2;
pragma cache_size=16;
}
}
# Open two database connections and create a single table in the db.
#
do_test ioerr5-1.0 {
opendatabases
execsql { CREATE TABLE A(Id INTEGER, Name TEXT) }
} {}
foreach locking_mode {normal exclusive} {
set nPage 2
for {set iFail 1} {$iFail<200} {incr iFail} {
sqlite3_soft_heap_limit 1048576
opendatabases
execsql { pragma locking_mode=exclusive }
set nRow [db one {SELECT count(*) FROM a}]
# Dirty (at least) one of the pages in the cache.
do_test ioerr5-1.$locking_mode-$iFail.1 {
execsql {
BEGIN EXCLUSIVE;
INSERT INTO a VALUES(1, 'ABCDEFGHIJKLMNOP');
}
} {}
# Open a read-only cursor on table "a". If the COMMIT below is
# interrupted by a persistent IO error, the pager will transition to
# PAGER_ERROR state. If there are no other read-only cursors open,
# from there the pager immediately discards all cached data and
# switches to PAGER_OPEN state. This read-only cursor stops that
# from happening, leaving the pager stuck in PAGER_ERROR state.
#
set channel [db incrblob -readonly a Name [db last_insert_rowid]]
# Now try to commit the transaction. Cause an IO error to occur
# within this operation, which moves the pager into the error state.
#
set ::sqlite_io_error_persist 1
set ::sqlite_io_error_pending $iFail
do_test ioerr5-1.$locking_mode-$iFail.2 {
set rc [catchsql {COMMIT}]
list
} {}
set ::sqlite_io_error_hit 0
set ::sqlite_io_error_persist 0
set ::sqlite_io_error_pending 0
# Read the contents of the database file into a Tcl variable.
#
set fd [open test.db]
fconfigure $fd -translation binary -encoding binary
set zDatabase [read $fd]
close $fd
# Set a very low soft-limit and then try to compile an SQL statement
# from UTF-16 text. To do this, SQLite will need to reclaim memory
# from the pager that is in error state. Including that associated
# with the dirty page.
#
do_test ioerr5-1.$locking_mode-$iFail.3 {
sqlite3_soft_heap_limit 1024
compilesql16 "SELECT 10"
} {SQLITE_OK}
close $channel
# Ensure that nothing was written to the database while reclaiming
# memory from the pager in error state.
#
do_test ioerr5-1.$locking_mode-$iFail.4 {
set fd [open test.db]
fconfigure $fd -translation binary -encoding binary
set zDatabase2 [read $fd]
close $fd
expr {$zDatabase eq $zDatabase2}
} {1}
if {$rc eq [list 0 {}]} {
do_test ioerr5.1-$locking_mode-$iFail.3 {
execsql { SELECT count(*) FROM a }
} [expr $nRow+1]
break
}
}
}
# Make sure this test script doesn't leave any files open.
#
do_test ioerr5-1.X {
catch { db close }
catch { db2 close }
set sqlite_open_file_count
} 0
do_test ioerr5-2.0 {
sqlite3 db test.db
execsql { CREATE INDEX i1 ON a(id, name); }
} {}
foreach locking_mode {exclusive normal} {
for {set iFail 1} {$iFail<200} {incr iFail} {
sqlite3_soft_heap_limit 1048576
opendatabases
execsql { pragma locking_mode=exclusive }
set nRow [db one {SELECT count(*) FROM a}]
do_test ioerr5-2.$locking_mode-$iFail.1 {
execsql {
BEGIN EXCLUSIVE;
INSERT INTO a VALUES(1, 'ABCDEFGHIJKLMNOP');
}
} {}
set ::sqlite_io_error_persist 1
set ::sqlite_io_error_pending $iFail
sqlite3_release_memory 10000
set error_hit $::sqlite_io_error_hit
set ::sqlite_io_error_hit 0
set ::sqlite_io_error_persist 0
set ::sqlite_io_error_pending 0
if {$error_hit} {
do_test ioerr5-2.$locking_mode-$iFail.3a {
catchsql COMMIT
} {1 {disk I/O error}}
} else {
do_test ioerr5-2.$locking_mode-$iFail.3b {
execsql COMMIT
} {}
break
}
}
}
# Make sure this test script doesn't leave any files open.
#
do_test ioerr5-2.X {
catch { db close }
catch { db2 close }
set sqlite_open_file_count
} 0
sqlite3_enable_shared_cache $::enable_shared_cache
sqlite3_soft_heap_limit $::soft_limit
finish_test
|