blob: 3005a758acd1af0fbcf505991409524f2edf4ce6 [file] [log] [blame]
danb8fd6c22010-05-24 10:39:36 +00001# 2010 May 24
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
13set testdir [file dirname $argv0]
14source $testdir/tester.tcl
15source $testdir/lock_common.tcl
dan10f5a502010-06-23 15:55:43 +000016source $testdir/wal_common.tcl
danb8fd6c22010-05-24 10:39:36 +000017
18ifcapable !wal {finish_test ; return }
19
danb8fd6c22010-05-24 10:39:36 +000020# Read and return the contents of file $filename. Treat the content as
21# binary data.
22#
23proc readfile {filename} {
24 set fd [open $filename]
25 fconfigure $fd -encoding binary
26 fconfigure $fd -translation binary
27 set data [read $fd]
28 close $fd
29 return $data
30}
31
32#
33# File $filename must be a WAL file on disk. Check that the checksum of frame
34# $iFrame in the file is correct when interpreting data as $endian-endian
35# integers ($endian must be either "big" or "little"). If the checksum looks
36# correct, return 1. Otherwise 0.
37#
38proc log_checksum_verify {filename iFrame endian} {
39 set data [readfile $filename]
danb8fd6c22010-05-24 10:39:36 +000040
dan71d89912010-05-24 13:57:42 +000041 foreach {offset c1 c2} [log_checksum_calc $data $iFrame $endian] {}
danb8fd6c22010-05-24 10:39:36 +000042
dan71d89912010-05-24 13:57:42 +000043 binary scan [string range $data $offset [expr $offset+7]] II expect1 expect2
danb8fd6c22010-05-24 10:39:36 +000044 set expect1 [expr $expect1&0xFFFFFFFF]
45 set expect2 [expr $expect2&0xFFFFFFFF]
dan71d89912010-05-24 13:57:42 +000046
danb8fd6c22010-05-24 10:39:36 +000047 expr {$c1==$expect1 && $c2==$expect2}
48}
49
danb8fd6c22010-05-24 10:39:36 +000050# File $filename must be a WAL file on disk. Compute the checksum for frame
51# $iFrame in the file by interpreting data as $endian-endian integers
52# ($endian must be either "big" or "little"). Then write the computed
53# checksum into the file.
54#
55proc log_checksum_write {filename iFrame endian} {
56 set data [readfile $filename]
danb8fd6c22010-05-24 10:39:36 +000057
dan71d89912010-05-24 13:57:42 +000058 foreach {offset c1 c2} [log_checksum_calc $data $iFrame $endian] {}
danb8fd6c22010-05-24 10:39:36 +000059
60 set bin [binary format II $c1 $c2]
61 set fd [open $filename r+]
62 fconfigure $fd -encoding binary
63 fconfigure $fd -translation binary
dan71d89912010-05-24 13:57:42 +000064 seek $fd $offset
danb8fd6c22010-05-24 10:39:36 +000065 puts -nonewline $fd $bin
66 close $fd
67}
68
dan10f5a502010-06-23 15:55:43 +000069# Calculate and return the checksum for a particular frame in a WAL.
70#
71# Arguments are:
72#
73# $data Blob containing the entire contents of a WAL.
74#
75# $iFrame Frame number within the $data WAL. Frames are numbered
76# starting at 1.
77#
78# $endian One of "big" or "little".
79#
80# Returns a list of three elements, as follows:
81#
82# * The byte offset of the checksum belonging to frame $iFrame in the WAL.
83# * The first integer in the calculated version of the checksum.
84# * The second integer in the calculated version of the checksum.
85#
dan71d89912010-05-24 13:57:42 +000086proc log_checksum_calc {data iFrame endian} {
87
88 binary scan [string range $data 8 11] I pgsz
89 if {$iFrame > 1} {
dan10f5a502010-06-23 15:55:43 +000090 set n [wal_file_size [expr $iFrame-2] $pgsz]
dan71d89912010-05-24 13:57:42 +000091 binary scan [string range $data [expr $n+16] [expr $n+23]] II c1 c2
92 } else {
93 set c1 0
94 set c2 0
dan10f5a502010-06-23 15:55:43 +000095 wal_cksum $endian c1 c2 [string range $data 0 23]
dan71d89912010-05-24 13:57:42 +000096 }
97
dan10f5a502010-06-23 15:55:43 +000098 set n [wal_file_size [expr $iFrame-1] $pgsz]
99 wal_cksum $endian c1 c2 [string range $data $n [expr $n+7]]
100 wal_cksum $endian c1 c2 [string range $data [expr $n+24] [expr $n+24+$pgsz-1]]
dan71d89912010-05-24 13:57:42 +0000101
102 list [expr $n+16] $c1 $c2
103}
104
danb8fd6c22010-05-24 10:39:36 +0000105#
106# File $filename must be a WAL file on disk. Set the 'magic' field of the
107# WAL header to indicate that checksums are $endian-endian ($endian must be
108# either "big" or "little").
109#
dan10f5a502010-06-23 15:55:43 +0000110# Also update the wal header checksum (since the wal header contents may
111# have changed).
112#
danb8fd6c22010-05-24 10:39:36 +0000113proc log_checksum_writemagic {filename endian} {
114 set val [expr {0x377f0682 | ($endian == "big" ? 1 : 0)}]
115 set bin [binary format I $val]
116 set fd [open $filename r+]
117 fconfigure $fd -encoding binary
118 fconfigure $fd -translation binary
119 puts -nonewline $fd $bin
dan10f5a502010-06-23 15:55:43 +0000120
121 seek $fd 0
122 set blob [read $fd 24]
123 set c1 0
124 set c2 0
125 wal_cksum $endian c1 c2 $blob
126 seek $fd 24
127 puts -nonewline $fd [binary format II $c1 $c2]
128
danb8fd6c22010-05-24 10:39:36 +0000129 close $fd
130}
131
132#-------------------------------------------------------------------------
133# Test cases walcksum-1.* attempt to verify the following:
134#
135# * That both native and non-native order checksum log files can
136# be recovered.
137#
138# * That when appending to native or non-native checksum log files
139# SQLite continues to use the right kind of checksums.
140#
141# * Test point 2 when the appending process is not one that recovered
142# the log file.
143#
144# * Test that both native and non-native checksum log files can be
145# checkpointed. And that after doing so the next write to the log
146# file occurs using native byte-order checksums.
147#
148set native "big"
149if {$::tcl_platform(byteOrder) == "littleEndian"} { set native "little" }
150foreach endian {big little} {
151
152 # Create a database. Leave some data in the log file.
153 #
154 do_test walcksum-1.$endian.1 {
155 catch { db close }
mistachkinfda06be2011-08-02 00:57:34 +0000156 forcedelete test.db test.db-wal test.db-journal
danb8fd6c22010-05-24 10:39:36 +0000157 sqlite3 db test.db
158 execsql {
159 PRAGMA page_size = 1024;
160 PRAGMA auto_vacuum = 0;
161 PRAGMA synchronous = NORMAL;
162
163 CREATE TABLE t1(a PRIMARY KEY, b);
164 INSERT INTO t1 VALUES(1, 'one');
165 INSERT INTO t1 VALUES(2, 'two');
166 INSERT INTO t1 VALUES(3, 'three');
167 INSERT INTO t1 VALUES(5, 'five');
168
169 PRAGMA journal_mode = WAL;
170 INSERT INTO t1 VALUES(8, 'eight');
171 INSERT INTO t1 VALUES(13, 'thirteen');
172 INSERT INTO t1 VALUES(21, 'twentyone');
173 }
174
mistachkinfda06be2011-08-02 00:57:34 +0000175 forcecopy test.db test2.db
176 forcecopy test.db-wal test2.db-wal
danb8fd6c22010-05-24 10:39:36 +0000177 db close
178
179 list [file size test2.db] [file size test2.db-wal]
dan10f5a502010-06-23 15:55:43 +0000180 } [list [expr 1024*3] [wal_file_size 6 1024]]
danb8fd6c22010-05-24 10:39:36 +0000181
182 # Verify that the checksums are valid for all frames and that they
183 # are calculated by interpreting data in native byte-order.
184 #
185 for {set f 1} {$f <= 6} {incr f} {
186 do_test walcksum-1.$endian.2.$f {
187 log_checksum_verify test2.db-wal $f $native
188 } 1
189 }
190
191 # Replace all checksums in the current WAL file with $endian versions.
192 # Then check that it is still possible to recover and read the database.
193 #
dan71d89912010-05-24 13:57:42 +0000194 log_checksum_writemagic test2.db-wal $endian
danb8fd6c22010-05-24 10:39:36 +0000195 for {set f 1} {$f <= 6} {incr f} {
196 do_test walcksum-1.$endian.3.$f {
197 log_checksum_write test2.db-wal $f $endian
198 log_checksum_verify test2.db-wal $f $endian
199 } {1}
200 }
201 do_test walcksum-1.$endian.4.1 {
mistachkinfda06be2011-08-02 00:57:34 +0000202 forcecopy test2.db test.db
203 forcecopy test2.db-wal test.db-wal
danb8fd6c22010-05-24 10:39:36 +0000204 sqlite3 db test.db
205 execsql { SELECT a FROM t1 }
206 } {1 2 3 5 8 13 21}
207
208 # Following recovery, any frames written to the log should use the same
209 # endianness as the existing frames. Check that this is the case.
210 #
211 do_test walcksum-1.$endian.5.0 {
212 execsql {
213 PRAGMA synchronous = NORMAL;
214 INSERT INTO t1 VALUES(34, 'thirtyfour');
215 }
216 list [file size test.db] [file size test.db-wal]
dan10f5a502010-06-23 15:55:43 +0000217 } [list [expr 1024*3] [wal_file_size 8 1024]]
danb8fd6c22010-05-24 10:39:36 +0000218 for {set f 1} {$f <= 8} {incr f} {
219 do_test walcksum-1.$endian.5.$f {
220 log_checksum_verify test.db-wal $f $endian
221 } {1}
222 }
223
224 # Now connect a second connection to the database. Check that this one
225 # (not the one that did recovery) also appends frames to the log using
226 # the same endianness for checksums as the existing frames.
227 #
228 do_test walcksum-1.$endian.6 {
229 sqlite3 db2 test.db
230 execsql {
231 PRAGMA integrity_check;
232 SELECT a FROM t1;
233 } db2
234 } {ok 1 2 3 5 8 13 21 34}
235 do_test walcksum-1.$endian.7.0 {
236 execsql {
237 PRAGMA synchronous = NORMAL;
238 INSERT INTO t1 VALUES(55, 'fiftyfive');
239 } db2
240 list [file size test.db] [file size test.db-wal]
dan10f5a502010-06-23 15:55:43 +0000241 } [list [expr 1024*3] [wal_file_size 10 1024]]
danb8fd6c22010-05-24 10:39:36 +0000242 for {set f 1} {$f <= 10} {incr f} {
243 do_test walcksum-1.$endian.7.$f {
244 log_checksum_verify test.db-wal $f $endian
245 } {1}
246 }
247
248 # Now that both the recoverer and non-recoverer have added frames to the
249 # log file, check that it can still be recovered.
250 #
mistachkinfda06be2011-08-02 00:57:34 +0000251 forcecopy test.db test2.db
252 forcecopy test.db-wal test2.db-wal
danb8fd6c22010-05-24 10:39:36 +0000253 do_test walcksum-1.$endian.7.11 {
254 sqlite3 db3 test2.db
255 execsql {
256 PRAGMA integrity_check;
257 SELECT a FROM t1;
258 } db3
259 } {ok 1 2 3 5 8 13 21 34 55}
260 db3 close
261
262 # Run a checkpoint on the database file. Then, check that any frames written
263 # to the start of the log use native byte-order checksums.
264 #
265 do_test walcksum-1.$endian.8.1 {
266 execsql {
267 PRAGMA wal_checkpoint;
268 INSERT INTO t1 VALUES(89, 'eightynine');
269 }
270 log_checksum_verify test.db-wal 1 $native
271 } {1}
272 do_test walcksum-1.$endian.8.2 {
273 log_checksum_verify test.db-wal 2 $native
274 } {1}
275 do_test walcksum-1.$endian.8.3 {
276 log_checksum_verify test.db-wal 3 $native
dan71d89912010-05-24 13:57:42 +0000277 } {0}
danb8fd6c22010-05-24 10:39:36 +0000278
279 do_test walcksum-1.$endian.9 {
280 execsql {
281 PRAGMA integrity_check;
282 SELECT a FROM t1;
283 } db2
284 } {ok 1 2 3 5 8 13 21 34 55 89}
285
286 catch { db close }
287 catch { db2 close }
288}
289
dan5f168a52010-05-28 04:16:28 +0000290#-------------------------------------------------------------------------
291# Test case walcksum-2.* tests that if a statement transaction is rolled
292# back after frames are written to the WAL, and then (after writing some
293# more) the outer transaction is committed, the WAL file is still correctly
294# formatted (and can be recovered by a second process if required).
295#
dan71d89912010-05-24 13:57:42 +0000296do_test walcksum-2.1 {
mistachkinfda06be2011-08-02 00:57:34 +0000297 forcedelete test.db test.db-wal test.db-journal
dan71d89912010-05-24 13:57:42 +0000298 sqlite3 db test.db
299 execsql {
300 PRAGMA synchronous = NORMAL;
301 PRAGMA page_size = 1024;
302 PRAGMA journal_mode = WAL;
303 PRAGMA cache_size = 10;
304 CREATE TABLE t1(x PRIMARY KEY);
305 PRAGMA wal_checkpoint;
306 INSERT INTO t1 VALUES(randomblob(800));
307 BEGIN;
308 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 2 */
309 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 4 */
310 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 8 */
311 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 16 */
312 SAVEPOINT one;
313 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 32 */
314 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 64 */
315 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 128 */
316 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 256 */
317 ROLLBACK TO one;
318 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 32 */
319 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 64 */
320 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 128 */
321 INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 256 */
322 COMMIT;
323 }
324
mistachkinfda06be2011-08-02 00:57:34 +0000325 forcecopy test.db test2.db
326 forcecopy test.db-wal test2.db-wal
dan71d89912010-05-24 13:57:42 +0000327
328 sqlite3 db2 test2.db
329 execsql {
330 PRAGMA integrity_check;
331 SELECT count(*) FROM t1;
332 } db2
333} {ok 256}
334catch { db close }
335catch { db2 close }
336
dan5f168a52010-05-28 04:16:28 +0000337#-------------------------------------------------------------------------
338# Test case walcksum-3.* tests that the checksum calculation detects single
339# byte changes to frame or frame-header data and considers the frame
340# invalid as a result.
341#
342do_test walcksum-3.1 {
mistachkinfda06be2011-08-02 00:57:34 +0000343 forcedelete test.db test.db-wal test.db-journal
dan5f168a52010-05-28 04:16:28 +0000344 sqlite3 db test.db
345
346 execsql {
347 PRAGMA synchronous = NORMAL;
348 PRAGMA page_size = 1024;
349 CREATE TABLE t1(a, b);
350 INSERT INTO t1 VALUES(1, randomblob(300));
351 INSERT INTO t1 VALUES(2, randomblob(300));
352 PRAGMA journal_mode = WAL;
353 INSERT INTO t1 VALUES(3, randomblob(300));
354 }
355
356 file size test.db-wal
dan10f5a502010-06-23 15:55:43 +0000357} [wal_file_size 1 1024]
dan5f168a52010-05-28 04:16:28 +0000358do_test walcksum-3.2 {
mistachkinfda06be2011-08-02 00:57:34 +0000359 forcecopy test.db-wal test2.db-wal
360 forcecopy test.db test2.db
dan5f168a52010-05-28 04:16:28 +0000361 sqlite3 db2 test2.db
362 execsql { SELECT a FROM t1 } db2
363} {1 2 3}
364db2 close
mistachkinfda06be2011-08-02 00:57:34 +0000365forcecopy test.db test2.db
dan94b7f762010-05-29 06:18:54 +0000366
367
368foreach incr {1 2 3 20 40 60 80 100 120 140 160 180 200 220 240 253 254 255} {
dan5f168a52010-05-28 04:16:28 +0000369 do_test walcksum-3.3.$incr {
370 set FAIL 0
dan10f5a502010-06-23 15:55:43 +0000371 for {set iOff 0} {$iOff < [wal_file_size 1 1024]} {incr iOff} {
dan5f168a52010-05-28 04:16:28 +0000372
mistachkinfda06be2011-08-02 00:57:34 +0000373 forcecopy test.db-wal test2.db-wal
dan5f168a52010-05-28 04:16:28 +0000374 set fd [open test2.db-wal r+]
375 fconfigure $fd -encoding binary
376 fconfigure $fd -translation binary
377
378 seek $fd $iOff
379 binary scan [read $fd 1] c x
380 seek $fd $iOff
dan94b7f762010-05-29 06:18:54 +0000381 puts -nonewline $fd [binary format c [expr {($x+$incr)&0xFF}]]
dan5f168a52010-05-28 04:16:28 +0000382 close $fd
383
384 sqlite3 db2 test2.db
385 if { [execsql { SELECT a FROM t1 } db2] != "1 2" } {set FAIL 1}
386 db2 close
387 }
388 set FAIL
389 } {0}
390}
391
danb8fd6c22010-05-24 10:39:36 +0000392finish_test