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