|
1 # 2001 October 12 |
|
2 # |
|
3 # Portions Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiaries. All rights reserved. |
|
4 # |
|
5 # The author disclaims copyright to this source code. In place of |
|
6 # a legal notice, here is a blessing: |
|
7 # |
|
8 # May you do good and not evil. |
|
9 # May you find forgiveness for yourself and forgive others. |
|
10 # May you share freely, never taking more than you give. |
|
11 # |
|
12 #*********************************************************************** |
|
13 # This file implements regression tests for SQLite library. The |
|
14 # focus of this file is testing for correct handling of I/O errors |
|
15 # such as writes failing because the disk is full. |
|
16 # |
|
17 # The tests in this file use special facilities that are only |
|
18 # available in the SQLite test fixture. |
|
19 # |
|
20 # $Id: ioerr.test,v 1.41 2008/07/12 14:52:20 drh Exp $ |
|
21 |
|
22 set testdir [file dirname $argv0] |
|
23 source $testdir/tester.tcl |
|
24 |
|
25 # If SQLITE_DEFAULT_AUTOVACUUM is set to true, then a simulated IO error |
|
26 # on the 8th IO operation in the SQL script below doesn't report an error. |
|
27 # |
|
28 # This is because the 8th IO call attempts to read page 2 of the database |
|
29 # file when the file on disk is only 1 page. The pager layer detects that |
|
30 # this has happened and suppresses the error returned by the OS layer. |
|
31 # |
|
32 do_ioerr_test ioerr-1 -erc 1 -ckrefcount 1 -sqlprep { |
|
33 SELECT * FROM sqlite_master; |
|
34 } -sqlbody { |
|
35 CREATE TABLE t1(a,b,c); |
|
36 SELECT * FROM sqlite_master; |
|
37 BEGIN TRANSACTION; |
|
38 INSERT INTO t1 VALUES(1,2,3); |
|
39 INSERT INTO t1 VALUES(4,5,6); |
|
40 ROLLBACK; |
|
41 SELECT * FROM t1; |
|
42 BEGIN TRANSACTION; |
|
43 INSERT INTO t1 VALUES(1,2,3); |
|
44 INSERT INTO t1 VALUES(4,5,6); |
|
45 COMMIT; |
|
46 SELECT * FROM t1; |
|
47 DELETE FROM t1 WHERE a<100; |
|
48 } -exclude [expr [string match [execsql {pragma auto_vacuum}] 1] ? 4 : 0] |
|
49 |
|
50 # Test for IO errors during a VACUUM. |
|
51 # |
|
52 # The first IO call is excluded from the test. This call attempts to read |
|
53 # the file-header of the temporary database used by VACUUM. Since the |
|
54 # database doesn't exist at that point, the IO error is not detected. |
|
55 # |
|
56 # Additionally, if auto-vacuum is enabled, the 12th IO error is not |
|
57 # detected. Same reason as the 8th in the test case above. |
|
58 # |
|
59 ifcapable vacuum { |
|
60 do_ioerr_test ioerr-2 -cksum true -ckrefcount true -sqlprep { |
|
61 BEGIN; |
|
62 CREATE TABLE t1(a, b, c); |
|
63 INSERT INTO t1 VALUES(1, randstr(50,50), randstr(50,50)); |
|
64 INSERT INTO t1 SELECT a+2, b||'-'||rowid, c||'-'||rowid FROM t1; |
|
65 INSERT INTO t1 SELECT a+4, b||'-'||rowid, c||'-'||rowid FROM t1; |
|
66 INSERT INTO t1 SELECT a+8, b||'-'||rowid, c||'-'||rowid FROM t1; |
|
67 INSERT INTO t1 SELECT a+16, b||'-'||rowid, c||'-'||rowid FROM t1; |
|
68 INSERT INTO t1 SELECT a+32, b||'-'||rowid, c||'-'||rowid FROM t1; |
|
69 INSERT INTO t1 SELECT a+64, b||'-'||rowid, c||'-'||rowid FROM t1; |
|
70 INSERT INTO t1 SELECT a+128, b||'-'||rowid, c||'-'||rowid FROM t1; |
|
71 INSERT INTO t1 VALUES(1, randstr(600,600), randstr(600,600)); |
|
72 CREATE TABLE t2 AS SELECT * FROM t1; |
|
73 CREATE TABLE t3 AS SELECT * FROM t1; |
|
74 COMMIT; |
|
75 DROP TABLE t2; |
|
76 } -sqlbody { |
|
77 VACUUM; |
|
78 } -exclude [list \ |
|
79 1 [expr [string match [execsql {pragma auto_vacuum}] 1]?9:-1]] |
|
80 } |
|
81 |
|
82 do_ioerr_test ioerr-3 -ckrefcount true -tclprep { |
|
83 execsql { |
|
84 PRAGMA cache_size = 10; |
|
85 BEGIN; |
|
86 CREATE TABLE abc(a); |
|
87 INSERT INTO abc VALUES(randstr(1500,1500)); -- Page 4 is overflow |
|
88 } |
|
89 for {set i 0} {$i<150} {incr i} { |
|
90 execsql { |
|
91 INSERT INTO abc VALUES(randstr(100,100)); |
|
92 } |
|
93 } |
|
94 execsql COMMIT |
|
95 } -sqlbody { |
|
96 CREATE TABLE abc2(a); |
|
97 BEGIN; |
|
98 DELETE FROM abc WHERE length(a)>100; |
|
99 UPDATE abc SET a = randstr(90,90); |
|
100 COMMIT; |
|
101 CREATE TABLE abc3(a); |
|
102 } |
|
103 |
|
104 # Test IO errors that can occur retrieving a record header that flows over |
|
105 # onto an overflow page. |
|
106 do_ioerr_test ioerr-4 -ckrefcount true -tclprep { |
|
107 set sql "CREATE TABLE abc(a1" |
|
108 for {set i 2} {$i<1300} {incr i} { |
|
109 append sql ", a$i" |
|
110 } |
|
111 append sql ");" |
|
112 execsql $sql |
|
113 execsql {INSERT INTO abc (a1) VALUES(NULL)} |
|
114 } -sqlbody { |
|
115 SELECT * FROM abc; |
|
116 } |
|
117 |
|
118 |
|
119 # Test IO errors that may occur during a multi-file commit. |
|
120 # |
|
121 # Tests 8 and 17 are excluded when auto-vacuum is enabled for the same |
|
122 # reason as in test cases ioerr-1.XXX |
|
123 ifcapable attach { |
|
124 set ex "" |
|
125 if {[string match [execsql {pragma auto_vacuum}] 1]} { |
|
126 set ex [list 4 17] |
|
127 } |
|
128 do_ioerr_test ioerr-5 -restoreprng 0 -ckrefcount true -sqlprep { |
|
129 ATTACH 'test2.db' AS test2; |
|
130 } -sqlbody { |
|
131 BEGIN; |
|
132 CREATE TABLE t1(a,b,c); |
|
133 CREATE TABLE test2.t2(a,b,c); |
|
134 COMMIT; |
|
135 } -exclude $ex |
|
136 } |
|
137 |
|
138 # Test IO errors when replaying two hot journals from a 2-file |
|
139 # transaction. This test only runs on UNIX. |
|
140 ifcapable crashtest&&attach { |
|
141 if {![catch {sqlite3 -has_codec} r] && !$r} { |
|
142 do_ioerr_test ioerr-6 -ckrefcount true -tclprep { |
|
143 execsql { |
|
144 ATTACH 'test2.db' as aux; |
|
145 CREATE TABLE tx(a, b); |
|
146 CREATE TABLE aux.ty(a, b); |
|
147 } |
|
148 set rc [crashsql -delay 2 -file test2.db-journal { |
|
149 ATTACH 'test2.db' as aux; |
|
150 PRAGMA cache_size = 10; |
|
151 BEGIN; |
|
152 CREATE TABLE aux.t2(a, b, c); |
|
153 CREATE TABLE t1(a, b, c); |
|
154 COMMIT; |
|
155 }] |
|
156 if {$rc!="1 {child process exited abnormally}"} { |
|
157 error "Wrong error message: $rc" |
|
158 } |
|
159 } -sqlbody { |
|
160 SELECT * FROM sqlite_master; |
|
161 SELECT * FROM aux.sqlite_master; |
|
162 } |
|
163 } |
|
164 } |
|
165 |
|
166 # Test handling of IO errors that occur while rolling back hot journal |
|
167 # files. |
|
168 # |
|
169 # These tests can't be run on windows because the windows version of |
|
170 # SQLite holds a mandatory exclusive lock on journal files it has open. |
|
171 # |
|
172 if {$tcl_platform(platform)!="windows" && $tcl_platform(platform)!="symbian"} { |
|
173 do_ioerr_test ioerr-7 -tclprep { |
|
174 db close |
|
175 sqlite3 db2 test2.db |
|
176 db2 eval { |
|
177 PRAGMA synchronous = 0; |
|
178 CREATE TABLE t1(a, b); |
|
179 INSERT INTO t1 VALUES(1, 2); |
|
180 BEGIN; |
|
181 INSERT INTO t1 VALUES(3, 4); |
|
182 } |
|
183 copy_file test2.db test.db |
|
184 copy_file test2.db-journal test.db-journal |
|
185 db2 close |
|
186 } -tclbody { |
|
187 sqlite3 db test.db |
|
188 db eval { |
|
189 SELECT * FROM t1; |
|
190 } |
|
191 } -exclude 1 |
|
192 } |
|
193 |
|
194 # For test coverage: Cause an I/O failure while trying to read a |
|
195 # short field (one that fits into a Mem buffer without mallocing |
|
196 # for space). |
|
197 # |
|
198 do_ioerr_test ioerr-8 -ckrefcount true -tclprep { |
|
199 execsql { |
|
200 CREATE TABLE t1(a,b,c); |
|
201 INSERT INTO t1 VALUES(randstr(200,200), randstr(1000,1000), 2); |
|
202 } |
|
203 db close |
|
204 sqlite3 db test.db |
|
205 } -sqlbody { |
|
206 SELECT c FROM t1; |
|
207 } |
|
208 |
|
209 # For test coverage: Cause an IO error whilst reading the master-journal |
|
210 # name from a journal file. |
|
211 if {$tcl_platform(platform)=="unix"} { |
|
212 do_ioerr_test ioerr-9 -ckrefcount true -tclprep { |
|
213 execsql { |
|
214 CREATE TABLE t1(a,b,c); |
|
215 INSERT INTO t1 VALUES(randstr(200,200), randstr(1000,1000), 2); |
|
216 BEGIN; |
|
217 INSERT INTO t1 VALUES(randstr(200,200), randstr(1000,1000), 2); |
|
218 } |
|
219 copy_file test.db-journal test2.db-journal |
|
220 execsql { |
|
221 COMMIT; |
|
222 } |
|
223 copy_file test2.db-journal test.db-journal |
|
224 set f [open test.db-journal a] |
|
225 fconfigure $f -encoding binary |
|
226 puts -nonewline $f "hello" |
|
227 puts -nonewline $f "\x00\x00\x00\x05\x01\x02\x03\x04" |
|
228 puts -nonewline $f "\xd9\xd5\x05\xf9\x20\xa1\x63\xd7" |
|
229 close $f |
|
230 } -sqlbody { |
|
231 SELECT a FROM t1; |
|
232 } |
|
233 } |
|
234 |
|
235 # For test coverage: Cause an IO error during statement playback (i.e. |
|
236 # a constraint). |
|
237 do_ioerr_test ioerr-10 -ckrefcount true -tclprep { |
|
238 execsql { |
|
239 BEGIN; |
|
240 CREATE TABLE t1(a PRIMARY KEY, b); |
|
241 } |
|
242 for {set i 0} {$i < 500} {incr i} { |
|
243 execsql {INSERT INTO t1 VALUES(:i, 'hello world');} |
|
244 } |
|
245 execsql { |
|
246 COMMIT; |
|
247 } |
|
248 } -tclbody { |
|
249 |
|
250 catch {execsql { |
|
251 BEGIN; |
|
252 INSERT INTO t1 VALUES('abc', 123); |
|
253 INSERT INTO t1 VALUES('def', 123); |
|
254 INSERT INTO t1 VALUES('ghi', 123); |
|
255 INSERT INTO t1 SELECT (a+500)%900, 'good string' FROM t1; |
|
256 }} msg |
|
257 |
|
258 if {$msg != "column a is not unique"} { |
|
259 error $msg |
|
260 } |
|
261 } |
|
262 |
|
263 # Assertion fault bug reported by alex dimitrov. |
|
264 # |
|
265 do_ioerr_test ioerr-11 -ckrefcount true -erc 1 -sqlprep { |
|
266 CREATE TABLE A(Id INTEGER, Name TEXT); |
|
267 INSERT INTO A(Id, Name) VALUES(1, 'Name'); |
|
268 } -sqlbody { |
|
269 UPDATE A SET Id = 2, Name = 'Name2' WHERE Id = 1; |
|
270 } |
|
271 |
|
272 # Test that an io error encountered in a sync() caused by a call to |
|
273 # sqlite3_release_memory() is handled Ok. Only try this if |
|
274 # memory-management is enabled. |
|
275 # |
|
276 ifcapable memorymanage { |
|
277 do_ioerr_test memmanage-ioerr1 -ckrefcount true -sqlprep { |
|
278 BEGIN; |
|
279 CREATE TABLE t1(a, b, c); |
|
280 INSERT INTO t1 VALUES(randstr(50,50), randstr(100,100), randstr(10,10)); |
|
281 INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1; |
|
282 INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1; |
|
283 INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1; |
|
284 INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1; |
|
285 INSERT INTO t1 SELECT randstr(50,50), randstr(9,9), randstr(90,90) FROM t1; |
|
286 } -tclbody { |
|
287 sqlite3_release_memory |
|
288 } -sqlbody { |
|
289 COMMIT; |
|
290 } |
|
291 } |
|
292 |
|
293 ifcapable pager_pragmas&&autovacuum { |
|
294 do_ioerr_test ioerr-12 -ckrefcount true -erc 1 -sqlprep { |
|
295 PRAGMA page_size = 512; |
|
296 PRAGMA auto_vacuum = incremental; |
|
297 CREATE TABLE t1(x); |
|
298 INSERT INTO t1 VALUES( randomblob(1 * (512-4)) ); |
|
299 INSERT INTO t1 VALUES( randomblob(110 * (512-4)) ); |
|
300 INSERT INTO t1 VALUES( randomblob(2 * (512-4)) ); |
|
301 INSERT INTO t1 VALUES( randomblob(110 * (512-4)) ); |
|
302 INSERT INTO t1 VALUES( randomblob(3 * (512-4)) ); |
|
303 DELETE FROM t1 WHERE rowid = 3; |
|
304 PRAGMA incremental_vacuum = 2; |
|
305 DELETE FROM t1 WHERE rowid = 1; |
|
306 } -sqlbody { |
|
307 PRAGMA incremental_vacuum = 1; |
|
308 } |
|
309 } |
|
310 |
|
311 # Usually, after a new page is allocated from the end of the file, it does |
|
312 # not need to be written to the journal. The exception is when the new page |
|
313 # shares its sector with an existing page that does need to be journalled. |
|
314 # This test case provokes this condition to test for the sake of coverage |
|
315 # that an IO error while journalling the coresident page is handled correctly. |
|
316 # |
|
317 sqlite3_simulate_device -char {} -sectorsize 2048 |
|
318 do_ioerr_test ioerr-12 -ckrefcount true -erc 1 -tclprep { |
|
319 db close |
|
320 sqlite3 db test.db -vfs devsym |
|
321 |
|
322 # Create a test database. Page 2 is the root page of table t1. The only |
|
323 # row inserted into t1 has an overflow page - page 3. Page 3 will be |
|
324 # coresident on the 2048 byte sector with the next page to be allocated. |
|
325 # |
|
326 db eval { PRAGMA page_size = 1024 } |
|
327 db eval { CREATE TABLE t1(x) } |
|
328 db eval { INSERT INTO t1 VALUES(randomblob(1100)); } |
|
329 } -tclbody { |
|
330 db eval { INSERT INTO t1 VALUES(randomblob(2000)); } |
|
331 } |
|
332 sqlite3_simulate_device -char {} -sectorsize 0 |
|
333 catch {db close} |
|
334 |
|
335 do_ioerr_test ioerr-13 -ckrefcount true -erc 1 -sqlprep { |
|
336 PRAGMA auto_vacuum = incremental; |
|
337 CREATE TABLE t1(x); |
|
338 CREATE TABLE t2(x); |
|
339 INSERT INTO t2 VALUES(randomblob(1500)); |
|
340 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
341 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
342 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
343 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
344 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
345 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
346 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
347 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
348 INSERT INTO t1 VALUES(randomblob(20)); |
|
349 INSERT INTO t1 SELECT x FROM t1; |
|
350 INSERT INTO t1 SELECT x FROM t1; |
|
351 INSERT INTO t1 SELECT x FROM t1; |
|
352 INSERT INTO t1 SELECT x FROM t1; |
|
353 INSERT INTO t1 SELECT x FROM t1; |
|
354 INSERT INTO t1 SELECT x FROM t1; /* 64 entries in t1 */ |
|
355 INSERT INTO t1 SELECT x FROM t1 LIMIT 14; /* 78 entries in t1 */ |
|
356 DELETE FROM t2 WHERE rowid = 3; |
|
357 } -sqlbody { |
|
358 -- This statement uses the balance_quick() optimization. The new page |
|
359 -- is appended to the database file. But the overflow page used by |
|
360 -- the new record will be positioned near the start of the database |
|
361 -- file, in the gap left by the "DELETE FROM t2 WHERE rowid=3" statement |
|
362 -- above. |
|
363 -- |
|
364 -- The point of this is that the statement wil need to update two pointer |
|
365 -- map pages. Which introduces another opportunity for an IO error. |
|
366 -- |
|
367 INSERT INTO t1 VALUES(randomblob(2000)); |
|
368 } |
|
369 |
|
370 do_ioerr_test ioerr-14 -ckrefcount true -erc 1 -sqlprep { |
|
371 PRAGMA auto_vacuum = incremental; |
|
372 CREATE TABLE t1(x); |
|
373 CREATE TABLE t2(x); |
|
374 INSERT INTO t2 VALUES(randomblob(1500)); |
|
375 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
376 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
377 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
378 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
379 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
380 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
381 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
382 INSERT INTO t2 SELECT randomblob(1500) FROM t2; |
|
383 |
|
384 -- This statement inserts a row into t1 with an overflow page at the |
|
385 -- end of the file. A long way from its parent (the root of t1). |
|
386 INSERT INTO t1 VALUES(randomblob(1500)); |
|
387 DELETE FROM t2 WHERE rowid<10; |
|
388 } -sqlbody { |
|
389 -- This transaction will cause the root-page of table t1 to divide |
|
390 -- (by calling balance_deeper()). When it does, the "parent" page of the |
|
391 -- overflow page inserted in the -sqlprep block above will change and |
|
392 -- the corresponding pointer map page be updated. This test case attempts |
|
393 -- to cause an IO error during the pointer map page update. |
|
394 -- |
|
395 BEGIN; |
|
396 INSERT INTO t1 VALUES(randomblob(100)); |
|
397 INSERT INTO t1 VALUES(randomblob(100)); |
|
398 INSERT INTO t1 VALUES(randomblob(100)); |
|
399 INSERT INTO t1 VALUES(randomblob(100)); |
|
400 INSERT INTO t1 VALUES(randomblob(100)); |
|
401 INSERT INTO t1 VALUES(randomblob(100)); |
|
402 INSERT INTO t1 VALUES(randomblob(100)); |
|
403 INSERT INTO t1 VALUES(randomblob(100)); |
|
404 INSERT INTO t1 VALUES(randomblob(100)); |
|
405 INSERT INTO t1 VALUES(randomblob(100)); |
|
406 COMMIT; |
|
407 } |
|
408 |
|
409 finish_test |