summaryrefslogtreecommitdiff
path: root/test/ioerr5.test
blob: a430f534074207047eaf7c8ad1af10adf1850173 (plain)
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