xref: /sqlite-3.40.0/test/walcksum.test (revision be7721d1)
1# 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
16source $testdir/wal_common.tcl
17
18ifcapable !wal {finish_test ; return }
19
20# 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]
40
41  foreach {offset c1 c2} [log_checksum_calc $data $iFrame $endian] {}
42
43  binary scan [string range $data $offset [expr $offset+7]] II expect1 expect2
44  set expect1 [expr $expect1&0xFFFFFFFF]
45  set expect2 [expr $expect2&0xFFFFFFFF]
46
47  expr {$c1==$expect1 && $c2==$expect2}
48}
49
50# 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]
57
58  foreach {offset c1 c2} [log_checksum_calc $data $iFrame $endian] {}
59
60  set bin [binary format II $c1 $c2]
61  set fd [open $filename r+]
62  fconfigure $fd -encoding binary
63  fconfigure $fd -translation binary
64  seek $fd $offset
65  puts -nonewline $fd $bin
66  close $fd
67}
68
69# 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#
86proc log_checksum_calc {data iFrame endian} {
87
88  binary scan [string range $data 8 11] I pgsz
89  if {$iFrame > 1} {
90    set n [wal_file_size [expr $iFrame-2] $pgsz]
91    binary scan [string range $data [expr $n+16] [expr $n+23]] II c1 c2
92  } else {
93    set c1 0
94    set c2 0
95    wal_cksum $endian c1 c2 [string range $data 0 23]
96  }
97
98  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]]
101
102  list [expr $n+16] $c1 $c2
103}
104
105#
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#
110# Also update the wal header checksum (since the wal header contents may
111# have changed).
112#
113proc 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
120
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
129  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 }
156    forcedelete test.db test.db-wal test.db-journal
157    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
175    forcecopy test.db test2.db
176    forcecopy test.db-wal test2.db-wal
177    db close
178
179    list [file size test2.db] [file size test2.db-wal]
180  } [list [expr 1024*3] [wal_file_size 6 1024]]
181
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  #
194  log_checksum_writemagic test2.db-wal $endian
195  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 {
202    forcecopy test2.db test.db
203    forcecopy test2.db-wal test.db-wal
204    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]
217  } [list [expr 1024*3] [wal_file_size 8 1024]]
218  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]
241  } [list [expr 1024*3] [wal_file_size 10 1024]]
242  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  #
251  forcecopy test.db test2.db
252  forcecopy test.db-wal test2.db-wal
253  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
277  } {0}
278
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
290#-------------------------------------------------------------------------
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#
296do_test walcksum-2.1 {
297  forcedelete test.db test.db-wal test.db-journal
298  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
325  forcecopy test.db test2.db
326  forcecopy test.db-wal test2.db-wal
327
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
337
338finish_test
339