1: # 2009 January 8
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 test verifies a couple of specific potential data corruption
13: # scenarios involving crashes or power failures.
14: #
15: # Later: Also, some other specific scenarios required for coverage
16: # testing that do not lead to corruption.
17: #
18: # $Id: crash8.test,v 1.1.1.1 2012/02/21 17:04:16 misho Exp $
19:
20:
21: set testdir [file dirname $argv0]
22: source $testdir/tester.tcl
23:
24: ifcapable !crashtest {
25: finish_test
26: return
27: }
28:
29: do_test crash8-1.1 {
30: execsql {
31: PRAGMA auto_vacuum=OFF;
32: CREATE TABLE t1(a, b);
33: CREATE INDEX i1 ON t1(a, b);
34: INSERT INTO t1 VALUES(1, randstr(1000,1000));
35: INSERT INTO t1 VALUES(2, randstr(1000,1000));
36: INSERT INTO t1 VALUES(3, randstr(1000,1000));
37: INSERT INTO t1 VALUES(4, randstr(1000,1000));
38: INSERT INTO t1 VALUES(5, randstr(1000,1000));
39: INSERT INTO t1 VALUES(6, randstr(1000,1000));
40: CREATE TABLE t2(a, b);
41: CREATE TABLE t3(a, b);
42: CREATE TABLE t4(a, b);
43: CREATE TABLE t5(a, b);
44: CREATE TABLE t6(a, b);
45: CREATE TABLE t7(a, b);
46: CREATE TABLE t8(a, b);
47: CREATE TABLE t9(a, b);
48: CREATE TABLE t10(a, b);
49: PRAGMA integrity_check
50: }
51: } {ok}
52:
53:
54: # Potential corruption scenario 1. A second process opens the database
55: # and modifies a large portion of it. It then opens a second transaction
56: # and modifies a small part of the database, but crashes before it commits
57: # the transaction.
58: #
59: # When the first process accessed the database again, it was rolling back
60: # the aborted transaction, but was not purging its in-memory cache (which
61: # was loaded before the second process made its first, successful,
62: # modification). Producing an inconsistent cache.
63: #
64: do_test crash8-1.2 {
65: crashsql -delay 2 -file test.db {
66: PRAGMA cache_size = 10;
67: UPDATE t1 SET b = randstr(1000,1000);
68: INSERT INTO t9 VALUES(1, 2);
69: }
70: } {1 {child process exited abnormally}}
71: do_test crash8-1.3 {
72: execsql {PRAGMA integrity_check}
73: } {ok}
74:
75: # Potential corruption scenario 2. The second process, operating in
76: # persistent-journal mode, makes a large change to the database file
77: # with a small in-memory cache. Such that more than one journal-header
78: # was written to the file. It then opens a second transaction and makes
79: # a smaller change that requires only a single journal-header to be
80: # written to the journal file. The second change is such that the
81: # journal content written to the persistent journal file exactly overwrites
82: # the first journal-header and set of subsequent records written by the
83: # first, successful, change. The second process crashes before it can
84: # commit its second change.
85: #
86: # When the first process accessed the database again, it was rolling back
87: # the second aborted transaction, then continuing to rollback the second
88: # and subsequent journal-headers written by the first, successful, change.
89: # Database corruption.
90: #
91: do_test crash8.2.1 {
92: crashsql -delay 2 -file test.db {
93: PRAGMA journal_mode = persist;
94: PRAGMA cache_size = 10;
95: UPDATE t1 SET b = randstr(1000,1000);
96: PRAGMA cache_size = 100;
97: BEGIN;
98: INSERT INTO t2 VALUES('a', 'b');
99: INSERT INTO t3 VALUES('a', 'b');
100: INSERT INTO t4 VALUES('a', 'b');
101: INSERT INTO t5 VALUES('a', 'b');
102: INSERT INTO t6 VALUES('a', 'b');
103: INSERT INTO t7 VALUES('a', 'b');
104: INSERT INTO t8 VALUES('a', 'b');
105: INSERT INTO t9 VALUES('a', 'b');
106: INSERT INTO t10 VALUES('a', 'b');
107: COMMIT;
108: }
109: } {1 {child process exited abnormally}}
110:
111: do_test crash8-2.3 {
112: execsql {PRAGMA integrity_check}
113: } {ok}
114:
115: proc read_file {zFile} {
116: set fd [open $zFile]
117: fconfigure $fd -translation binary
118: set zData [read $fd]
119: close $fd
120: return $zData
121: }
122: proc write_file {zFile zData} {
123: set fd [open $zFile w]
124: fconfigure $fd -translation binary
125: puts -nonewline $fd $zData
126: close $fd
127: }
128:
129: # The following tests check that SQLite will not roll back a hot-journal
130: # file if the sector-size field in the first journal file header is
131: # suspect. Definition of suspect:
132: #
133: # a) Not a power of 2, or (crash8-3.5)
134: # b) Greater than 0x01000000 (16MB), or (crash8-3.6)
135: # c) Less than 512. (crash8-3.7)
136: #
137: # Also test that SQLite will not rollback a hot-journal file with a
138: # suspect page-size. In this case "suspect" means:
139: #
140: # a) Not a power of 2, or
141: # b) Less than 512, or
142: # c) Greater than SQLITE_MAX_PAGE_SIZE
143: #
144: do_test crash8-3.1 {
145: list [file exists test.db-joural] [file exists test.db]
146: } {0 1}
147: do_test crash8-3.2 {
148: execsql {
149: PRAGMA synchronous = off;
150: BEGIN;
151: DELETE FROM t1;
152: SELECT count(*) FROM t1;
153: }
154: } {0}
155: do_test crash8-3.3 {
156: set zJournal [read_file test.db-journal]
157: execsql {
158: COMMIT;
159: SELECT count(*) FROM t1;
160: }
161: } {0}
162: do_test crash8-3.4 {
163: binary scan [string range $zJournal 20 23] I nSector
164: set nSector
165: } {512}
166:
167: do_test crash8-3.5 {
168: set zJournal2 [string replace $zJournal 20 23 [binary format I 513]]
169: write_file test.db-journal $zJournal2
170:
171: execsql {
172: SELECT count(*) FROM t1;
173: PRAGMA integrity_check
174: }
175: } {0 ok}
176: do_test crash8-3.6 {
177: set zJournal2 [string replace $zJournal 20 23 [binary format I 0x2000000]]
178: write_file test.db-journal $zJournal2
179: execsql {
180: SELECT count(*) FROM t1;
181: PRAGMA integrity_check
182: }
183: } {0 ok}
184: do_test crash8-3.7 {
185: set zJournal2 [string replace $zJournal 20 23 [binary format I 256]]
186: write_file test.db-journal $zJournal2
187: execsql {
188: SELECT count(*) FROM t1;
189: PRAGMA integrity_check
190: }
191: } {0 ok}
192:
193: do_test crash8-3.8 {
194: set zJournal2 [string replace $zJournal 24 27 [binary format I 513]]
195: write_file test.db-journal $zJournal2
196:
197: execsql {
198: SELECT count(*) FROM t1;
199: PRAGMA integrity_check
200: }
201: } {0 ok}
202: do_test crash8-3.9 {
203: set big [expr $SQLITE_MAX_PAGE_SIZE * 2]
204: set zJournal2 [string replace $zJournal 24 27 [binary format I $big]]
205: write_file test.db-journal $zJournal2
206: execsql {
207: SELECT count(*) FROM t1;
208: PRAGMA integrity_check
209: }
210: } {0 ok}
211: do_test crash8-3.10 {
212: set zJournal2 [string replace $zJournal 24 27 [binary format I 256]]
213: write_file test.db-journal $zJournal2
214: execsql {
215: SELECT count(*) FROM t1;
216: PRAGMA integrity_check
217: }
218: } {0 ok}
219:
220: do_test crash8-3.11 {
221: set fd [open test.db-journal w]
222: fconfigure $fd -translation binary
223: puts -nonewline $fd $zJournal
224: close $fd
225: execsql {
226: SELECT count(*) FROM t1;
227: PRAGMA integrity_check
228: }
229: } {6 ok}
230:
231:
232: # If a connection running in persistent-journal mode is part of a
233: # multi-file transaction, it must ensure that the master-journal name
234: # appended to the journal file contents during the commit is located
235: # at the end of the physical journal file. If there was already a
236: # large journal file allocated at the start of the transaction, this
237: # may mean truncating the file so that the master journal name really
238: # is at the physical end of the file.
239: #
240: # This block of tests test that SQLite correctly truncates such
241: # journal files, and that the results behave correctly if a hot-journal
242: # rollback occurs.
243: #
244: ifcapable pragma {
245: reset_db
246: forcedelete test2.db
247:
248: do_test crash8-4.1 {
249: execsql {
250: PRAGMA journal_mode = persist;
251: CREATE TABLE ab(a, b);
252: INSERT INTO ab VALUES(0, 'abc');
253: INSERT INTO ab VALUES(1, NULL);
254: INSERT INTO ab VALUES(2, NULL);
255: INSERT INTO ab VALUES(3, NULL);
256: INSERT INTO ab VALUES(4, NULL);
257: INSERT INTO ab VALUES(5, NULL);
258: INSERT INTO ab VALUES(6, NULL);
259: UPDATE ab SET b = randstr(1000,1000);
260: ATTACH 'test2.db' AS aux;
261: PRAGMA aux.journal_mode = persist;
262: CREATE TABLE aux.ab(a, b);
263: INSERT INTO aux.ab SELECT * FROM main.ab;
264:
265: UPDATE aux.ab SET b = randstr(1000,1000) WHERE a>=1;
266: UPDATE ab SET b = randstr(1000,1000) WHERE a>=1;
267: }
268: list [file exists test.db-journal] [file exists test2.db-journal]
269: } {1 1}
270:
271: do_test crash8-4.2 {
272: execsql {
273: BEGIN;
274: UPDATE aux.ab SET b = 'def' WHERE a = 0;
275: UPDATE main.ab SET b = 'def' WHERE a = 0;
276: COMMIT;
277: }
278: } {}
279:
280: do_test crash8-4.3 {
281: execsql {
282: UPDATE aux.ab SET b = randstr(1000,1000) WHERE a>=1;
283: UPDATE ab SET b = randstr(1000,1000) WHERE a>=1;
284: }
285: } {}
286:
287: set contents_main [db eval {SELECT b FROM main.ab WHERE a = 1}]
288: set contents_aux [db eval {SELECT b FROM aux.ab WHERE a = 1}]
289:
290: do_test crash8-4.4 {
291: crashsql -file test2.db -delay 1 {
292: ATTACH 'test2.db' AS aux;
293: BEGIN;
294: UPDATE aux.ab SET b = 'ghi' WHERE a = 0;
295: UPDATE main.ab SET b = 'ghi' WHERE a = 0;
296: COMMIT;
297: }
298: } {1 {child process exited abnormally}}
299:
300: do_test crash8-4.5 {
301: list [file exists test.db-journal] [file exists test2.db-journal]
302: } {1 1}
303:
304: do_test crash8-4.6 {
305: execsql {
306: SELECT b FROM main.ab WHERE a = 0;
307: SELECT b FROM aux.ab WHERE a = 0;
308: }
309: } {def def}
310:
311: do_test crash8-4.7 {
312: crashsql -file test2.db -delay 1 {
313: ATTACH 'test2.db' AS aux;
314: BEGIN;
315: UPDATE aux.ab SET b = 'jkl' WHERE a = 0;
316: UPDATE main.ab SET b = 'jkl' WHERE a = 0;
317: COMMIT;
318: }
319: } {1 {child process exited abnormally}}
320:
321: do_test crash8-4.8 {
322: set fd [open test.db-journal]
323: fconfigure $fd -translation binary
324: seek $fd -16 end
325: binary scan [read $fd 4] I len
326:
327: seek $fd [expr {-1 * ($len + 16)}] end
328: set zMasterJournal [read $fd $len]
329: close $fd
330:
331: file exists $zMasterJournal
332: } {1}
333:
334: do_test crash8-4.9 {
335: execsql { SELECT b FROM aux.ab WHERE a = 0 }
336: } {def}
337:
338: do_test crash8-4.10 {
339: delete_file $zMasterJournal
340: execsql { SELECT b FROM main.ab WHERE a = 0 }
341: } {jkl}
342: }
343:
344: for {set i 1} {$i < 10} {incr i} {
345: catch { db close }
346: forcedelete test.db test.db-journal
347: sqlite3 db test.db
348: do_test crash8-5.$i.1 {
349: execsql {
350: CREATE TABLE t1(x PRIMARY KEY);
351: INSERT INTO t1 VALUES(randomblob(900));
352: INSERT INTO t1 SELECT randomblob(900) FROM t1;
353: INSERT INTO t1 SELECT randomblob(900) FROM t1;
354: INSERT INTO t1 SELECT randomblob(900) FROM t1;
355: INSERT INTO t1 SELECT randomblob(900) FROM t1;
356: INSERT INTO t1 SELECT randomblob(900) FROM t1;
357: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 64 rows */
358: }
359: crashsql -file test.db -delay [expr ($::i%2) + 1] {
360: PRAGMA cache_size = 10;
361: BEGIN;
362: UPDATE t1 SET x = randomblob(900);
363: ROLLBACK;
364: INSERT INTO t1 VALUES(randomblob(900));
365: }
366: execsql { PRAGMA integrity_check }
367: } {ok}
368:
369: catch { db close }
370: forcedelete test.db test.db-journal
371: sqlite3 db test.db
372: do_test crash8-5.$i.2 {
373: execsql {
374: PRAGMA cache_size = 10;
375: CREATE TABLE t1(x PRIMARY KEY);
376: INSERT INTO t1 VALUES(randomblob(900));
377: INSERT INTO t1 SELECT randomblob(900) FROM t1;
378: INSERT INTO t1 SELECT randomblob(900) FROM t1;
379: INSERT INTO t1 SELECT randomblob(900) FROM t1;
380: INSERT INTO t1 SELECT randomblob(900) FROM t1;
381: INSERT INTO t1 SELECT randomblob(900) FROM t1;
382: INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 64 rows */
383: BEGIN;
384: UPDATE t1 SET x = randomblob(900);
385: }
386: forcedelete testX.db testX.db-journal testX.db-wal
387: forcecopy test.db testX.db
388: forcecopy test.db-journal testX.db-journal
389: db close
390:
391: crashsql -file test.db -delay [expr ($::i%2) + 1] {
392: SELECT * FROM sqlite_master;
393: INSERT INTO t1 VALUES(randomblob(900));
394: }
395:
396: sqlite3 db2 testX.db
397: execsql { PRAGMA integrity_check } db2
398: } {ok}
399: }
400: catch {db2 close}
401:
402: finish_test
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>