xref: /sqlite-3.40.0/test/walcksum.test (revision be7721d1)
1b8fd6c2fSdan# 2010 May 24
2b8fd6c2fSdan#
3b8fd6c2fSdan# The author disclaims copyright to this source code.  In place of
4b8fd6c2fSdan# a legal notice, here is a blessing:
5b8fd6c2fSdan#
6b8fd6c2fSdan#    May you do good and not evil.
7b8fd6c2fSdan#    May you find forgiveness for yourself and forgive others.
8b8fd6c2fSdan#    May you share freely, never taking more than you give.
9b8fd6c2fSdan#
10b8fd6c2fSdan#***********************************************************************
11b8fd6c2fSdan#
12b8fd6c2fSdan
13b8fd6c2fSdanset testdir [file dirname $argv0]
14b8fd6c2fSdansource $testdir/tester.tcl
15b8fd6c2fSdansource $testdir/lock_common.tcl
1610f5a50eSdansource $testdir/wal_common.tcl
17b8fd6c2fSdan
18b8fd6c2fSdanifcapable !wal {finish_test ; return }
19b8fd6c2fSdan
20b8fd6c2fSdan# Read and return the contents of file $filename. Treat the content as
21b8fd6c2fSdan# binary data.
22b8fd6c2fSdan#
23b8fd6c2fSdanproc readfile {filename} {
24b8fd6c2fSdan  set fd [open $filename]
25b8fd6c2fSdan  fconfigure $fd -encoding binary
26b8fd6c2fSdan  fconfigure $fd -translation binary
27b8fd6c2fSdan  set data [read $fd]
28b8fd6c2fSdan  close $fd
29b8fd6c2fSdan  return $data
30b8fd6c2fSdan}
31b8fd6c2fSdan
32b8fd6c2fSdan#
33b8fd6c2fSdan# File $filename must be a WAL file on disk. Check that the checksum of frame
34b8fd6c2fSdan# $iFrame in the file is correct when interpreting data as $endian-endian
35b8fd6c2fSdan# integers ($endian must be either "big" or "little"). If the checksum looks
36b8fd6c2fSdan# correct, return 1. Otherwise 0.
37b8fd6c2fSdan#
38b8fd6c2fSdanproc log_checksum_verify {filename iFrame endian} {
39b8fd6c2fSdan  set data [readfile $filename]
40b8fd6c2fSdan
4171d89919Sdan  foreach {offset c1 c2} [log_checksum_calc $data $iFrame $endian] {}
42b8fd6c2fSdan
4371d89919Sdan  binary scan [string range $data $offset [expr $offset+7]] II expect1 expect2
44b8fd6c2fSdan  set expect1 [expr $expect1&0xFFFFFFFF]
45b8fd6c2fSdan  set expect2 [expr $expect2&0xFFFFFFFF]
4671d89919Sdan
47b8fd6c2fSdan  expr {$c1==$expect1 && $c2==$expect2}
48b8fd6c2fSdan}
49b8fd6c2fSdan
50b8fd6c2fSdan# File $filename must be a WAL file on disk. Compute the checksum for frame
51b8fd6c2fSdan# $iFrame in the file by interpreting data as $endian-endian integers
52b8fd6c2fSdan# ($endian must be either "big" or "little"). Then write the computed
53b8fd6c2fSdan# checksum into the file.
54b8fd6c2fSdan#
55b8fd6c2fSdanproc log_checksum_write {filename iFrame endian} {
56b8fd6c2fSdan  set data [readfile $filename]
57b8fd6c2fSdan
5871d89919Sdan  foreach {offset c1 c2} [log_checksum_calc $data $iFrame $endian] {}
59b8fd6c2fSdan
60b8fd6c2fSdan  set bin [binary format II $c1 $c2]
61b8fd6c2fSdan  set fd [open $filename r+]
62b8fd6c2fSdan  fconfigure $fd -encoding binary
63b8fd6c2fSdan  fconfigure $fd -translation binary
6471d89919Sdan  seek $fd $offset
65b8fd6c2fSdan  puts -nonewline $fd $bin
66b8fd6c2fSdan  close $fd
67b8fd6c2fSdan}
68b8fd6c2fSdan
6910f5a50eSdan# Calculate and return the checksum for a particular frame in a WAL.
7010f5a50eSdan#
7110f5a50eSdan# Arguments are:
7210f5a50eSdan#
7310f5a50eSdan#   $data         Blob containing the entire contents of a WAL.
7410f5a50eSdan#
7510f5a50eSdan#   $iFrame       Frame number within the $data WAL. Frames are numbered
7610f5a50eSdan#                 starting at 1.
7710f5a50eSdan#
7810f5a50eSdan#   $endian       One of "big" or "little".
7910f5a50eSdan#
8010f5a50eSdan# Returns a list of three elements, as follows:
8110f5a50eSdan#
8210f5a50eSdan#   * The byte offset of the checksum belonging to frame $iFrame in the WAL.
8310f5a50eSdan#   * The first integer in the calculated version of the checksum.
8410f5a50eSdan#   * The second integer in the calculated version of the checksum.
8510f5a50eSdan#
8671d89919Sdanproc log_checksum_calc {data iFrame endian} {
8771d89919Sdan
8871d89919Sdan  binary scan [string range $data 8 11] I pgsz
8971d89919Sdan  if {$iFrame > 1} {
9010f5a50eSdan    set n [wal_file_size [expr $iFrame-2] $pgsz]
9171d89919Sdan    binary scan [string range $data [expr $n+16] [expr $n+23]] II c1 c2
9271d89919Sdan  } else {
9371d89919Sdan    set c1 0
9471d89919Sdan    set c2 0
9510f5a50eSdan    wal_cksum $endian c1 c2 [string range $data 0 23]
9671d89919Sdan  }
9771d89919Sdan
9810f5a50eSdan  set n [wal_file_size [expr $iFrame-1] $pgsz]
9910f5a50eSdan  wal_cksum $endian c1 c2 [string range $data $n [expr $n+7]]
10010f5a50eSdan  wal_cksum $endian c1 c2 [string range $data [expr $n+24] [expr $n+24+$pgsz-1]]
10171d89919Sdan
10271d89919Sdan  list [expr $n+16] $c1 $c2
10371d89919Sdan}
10471d89919Sdan
105b8fd6c2fSdan#
106b8fd6c2fSdan# File $filename must be a WAL file on disk. Set the 'magic' field of the
107b8fd6c2fSdan# WAL header to indicate that checksums are $endian-endian ($endian must be
108b8fd6c2fSdan# either "big" or "little").
109b8fd6c2fSdan#
11010f5a50eSdan# Also update the wal header checksum (since the wal header contents may
11110f5a50eSdan# have changed).
11210f5a50eSdan#
113b8fd6c2fSdanproc log_checksum_writemagic {filename endian} {
114b8fd6c2fSdan  set val [expr {0x377f0682 | ($endian == "big" ? 1 : 0)}]
115b8fd6c2fSdan  set bin [binary format I $val]
116b8fd6c2fSdan  set fd [open $filename r+]
117b8fd6c2fSdan  fconfigure $fd -encoding binary
118b8fd6c2fSdan  fconfigure $fd -translation binary
119b8fd6c2fSdan  puts -nonewline $fd $bin
12010f5a50eSdan
12110f5a50eSdan  seek $fd 0
12210f5a50eSdan  set blob [read $fd 24]
12310f5a50eSdan  set c1 0
12410f5a50eSdan  set c2 0
12510f5a50eSdan  wal_cksum $endian c1 c2 $blob
12610f5a50eSdan  seek $fd 24
12710f5a50eSdan  puts -nonewline $fd [binary format II $c1 $c2]
12810f5a50eSdan
129b8fd6c2fSdan  close $fd
130b8fd6c2fSdan}
131b8fd6c2fSdan
132b8fd6c2fSdan#-------------------------------------------------------------------------
133b8fd6c2fSdan# Test cases walcksum-1.* attempt to verify the following:
134b8fd6c2fSdan#
135b8fd6c2fSdan#   * That both native and non-native order checksum log files can
136b8fd6c2fSdan#      be recovered.
137b8fd6c2fSdan#
138b8fd6c2fSdan#   * That when appending to native or non-native checksum log files
139b8fd6c2fSdan#     SQLite continues to use the right kind of checksums.
140b8fd6c2fSdan#
141b8fd6c2fSdan#   * Test point 2 when the appending process is not one that recovered
142b8fd6c2fSdan#     the log file.
143b8fd6c2fSdan#
144b8fd6c2fSdan#   * Test that both native and non-native checksum log files can be
145b8fd6c2fSdan#     checkpointed. And that after doing so the next write to the log
146b8fd6c2fSdan#     file occurs using native byte-order checksums.
147b8fd6c2fSdan#
148b8fd6c2fSdanset native "big"
149b8fd6c2fSdanif {$::tcl_platform(byteOrder) == "littleEndian"} { set native "little" }
150b8fd6c2fSdanforeach endian {big little} {
151b8fd6c2fSdan
152b8fd6c2fSdan  # Create a database. Leave some data in the log file.
153b8fd6c2fSdan  #
154b8fd6c2fSdan  do_test walcksum-1.$endian.1 {
155b8fd6c2fSdan    catch { db close }
156*fda06befSmistachkin    forcedelete test.db test.db-wal test.db-journal
157b8fd6c2fSdan    sqlite3 db test.db
158b8fd6c2fSdan    execsql {
159b8fd6c2fSdan      PRAGMA page_size = 1024;
160b8fd6c2fSdan      PRAGMA auto_vacuum = 0;
161b8fd6c2fSdan      PRAGMA synchronous = NORMAL;
162b8fd6c2fSdan
163b8fd6c2fSdan      CREATE TABLE t1(a PRIMARY KEY, b);
164b8fd6c2fSdan      INSERT INTO t1 VALUES(1,  'one');
165b8fd6c2fSdan      INSERT INTO t1 VALUES(2,  'two');
166b8fd6c2fSdan      INSERT INTO t1 VALUES(3,  'three');
167b8fd6c2fSdan      INSERT INTO t1 VALUES(5,  'five');
168b8fd6c2fSdan
169b8fd6c2fSdan      PRAGMA journal_mode = WAL;
170b8fd6c2fSdan      INSERT INTO t1 VALUES(8,  'eight');
171b8fd6c2fSdan      INSERT INTO t1 VALUES(13, 'thirteen');
172b8fd6c2fSdan      INSERT INTO t1 VALUES(21, 'twentyone');
173b8fd6c2fSdan    }
174b8fd6c2fSdan
175*fda06befSmistachkin    forcecopy test.db test2.db
176*fda06befSmistachkin    forcecopy test.db-wal test2.db-wal
177b8fd6c2fSdan    db close
178b8fd6c2fSdan
179b8fd6c2fSdan    list [file size test2.db] [file size test2.db-wal]
18010f5a50eSdan  } [list [expr 1024*3] [wal_file_size 6 1024]]
181b8fd6c2fSdan
182b8fd6c2fSdan  # Verify that the checksums are valid for all frames and that they
183b8fd6c2fSdan  # are calculated by interpreting data in native byte-order.
184b8fd6c2fSdan  #
185b8fd6c2fSdan  for {set f 1} {$f <= 6} {incr f} {
186b8fd6c2fSdan    do_test walcksum-1.$endian.2.$f {
187b8fd6c2fSdan      log_checksum_verify test2.db-wal $f $native
188b8fd6c2fSdan    } 1
189b8fd6c2fSdan  }
190b8fd6c2fSdan
191b8fd6c2fSdan  # Replace all checksums in the current WAL file with $endian versions.
192b8fd6c2fSdan  # Then check that it is still possible to recover and read the database.
193b8fd6c2fSdan  #
19471d89919Sdan  log_checksum_writemagic test2.db-wal $endian
195b8fd6c2fSdan  for {set f 1} {$f <= 6} {incr f} {
196b8fd6c2fSdan    do_test walcksum-1.$endian.3.$f {
197b8fd6c2fSdan      log_checksum_write test2.db-wal $f $endian
198b8fd6c2fSdan      log_checksum_verify test2.db-wal $f $endian
199b8fd6c2fSdan    } {1}
200b8fd6c2fSdan  }
201b8fd6c2fSdan  do_test walcksum-1.$endian.4.1 {
202*fda06befSmistachkin    forcecopy test2.db test.db
203*fda06befSmistachkin    forcecopy test2.db-wal test.db-wal
204b8fd6c2fSdan    sqlite3 db test.db
205b8fd6c2fSdan    execsql { SELECT a FROM t1 }
206b8fd6c2fSdan  } {1 2 3 5 8 13 21}
207b8fd6c2fSdan
208b8fd6c2fSdan  # Following recovery, any frames written to the log should use the same
209b8fd6c2fSdan  # endianness as the existing frames. Check that this is the case.
210b8fd6c2fSdan  #
211b8fd6c2fSdan  do_test walcksum-1.$endian.5.0 {
212b8fd6c2fSdan    execsql {
213b8fd6c2fSdan      PRAGMA synchronous = NORMAL;
214b8fd6c2fSdan      INSERT INTO t1 VALUES(34, 'thirtyfour');
215b8fd6c2fSdan    }
216b8fd6c2fSdan    list [file size test.db] [file size test.db-wal]
21710f5a50eSdan  } [list [expr 1024*3] [wal_file_size 8 1024]]
218b8fd6c2fSdan  for {set f 1} {$f <= 8} {incr f} {
219b8fd6c2fSdan    do_test walcksum-1.$endian.5.$f {
220b8fd6c2fSdan      log_checksum_verify test.db-wal $f $endian
221b8fd6c2fSdan    } {1}
222b8fd6c2fSdan  }
223b8fd6c2fSdan
224b8fd6c2fSdan  # Now connect a second connection to the database. Check that this one
225b8fd6c2fSdan  # (not the one that did recovery) also appends frames to the log using
226b8fd6c2fSdan  # the same endianness for checksums as the existing frames.
227b8fd6c2fSdan  #
228b8fd6c2fSdan  do_test walcksum-1.$endian.6 {
229b8fd6c2fSdan    sqlite3 db2 test.db
230b8fd6c2fSdan    execsql {
231b8fd6c2fSdan      PRAGMA integrity_check;
232b8fd6c2fSdan      SELECT a FROM t1;
233b8fd6c2fSdan    } db2
234b8fd6c2fSdan  } {ok 1 2 3 5 8 13 21 34}
235b8fd6c2fSdan  do_test walcksum-1.$endian.7.0 {
236b8fd6c2fSdan    execsql {
237b8fd6c2fSdan      PRAGMA synchronous = NORMAL;
238b8fd6c2fSdan      INSERT INTO t1 VALUES(55, 'fiftyfive');
239b8fd6c2fSdan    } db2
240b8fd6c2fSdan    list [file size test.db] [file size test.db-wal]
24110f5a50eSdan  } [list [expr 1024*3] [wal_file_size 10 1024]]
242b8fd6c2fSdan  for {set f 1} {$f <= 10} {incr f} {
243b8fd6c2fSdan    do_test walcksum-1.$endian.7.$f {
244b8fd6c2fSdan      log_checksum_verify test.db-wal $f $endian
245b8fd6c2fSdan    } {1}
246b8fd6c2fSdan  }
247b8fd6c2fSdan
248b8fd6c2fSdan  # Now that both the recoverer and non-recoverer have added frames to the
249b8fd6c2fSdan  # log file, check that it can still be recovered.
250b8fd6c2fSdan  #
251*fda06befSmistachkin  forcecopy test.db test2.db
252*fda06befSmistachkin  forcecopy test.db-wal test2.db-wal
253b8fd6c2fSdan  do_test walcksum-1.$endian.7.11 {
254b8fd6c2fSdan    sqlite3 db3 test2.db
255b8fd6c2fSdan    execsql {
256b8fd6c2fSdan      PRAGMA integrity_check;
257b8fd6c2fSdan      SELECT a FROM t1;
258b8fd6c2fSdan    } db3
259b8fd6c2fSdan  } {ok 1 2 3 5 8 13 21 34 55}
260b8fd6c2fSdan  db3 close
261b8fd6c2fSdan
262b8fd6c2fSdan  # Run a checkpoint on the database file. Then, check that any frames written
263b8fd6c2fSdan  # to the start of the log use native byte-order checksums.
264b8fd6c2fSdan  #
265b8fd6c2fSdan  do_test walcksum-1.$endian.8.1 {
266b8fd6c2fSdan    execsql {
267b8fd6c2fSdan      PRAGMA wal_checkpoint;
268b8fd6c2fSdan      INSERT INTO t1 VALUES(89, 'eightynine');
269b8fd6c2fSdan    }
270b8fd6c2fSdan    log_checksum_verify test.db-wal 1 $native
271b8fd6c2fSdan  } {1}
272b8fd6c2fSdan  do_test walcksum-1.$endian.8.2 {
273b8fd6c2fSdan    log_checksum_verify test.db-wal 2 $native
274b8fd6c2fSdan  } {1}
275b8fd6c2fSdan  do_test walcksum-1.$endian.8.3 {
276b8fd6c2fSdan    log_checksum_verify test.db-wal 3 $native
27771d89919Sdan  } {0}
278b8fd6c2fSdan
279b8fd6c2fSdan  do_test walcksum-1.$endian.9 {
280b8fd6c2fSdan    execsql {
281b8fd6c2fSdan      PRAGMA integrity_check;
282b8fd6c2fSdan      SELECT a FROM t1;
283b8fd6c2fSdan    } db2
284b8fd6c2fSdan  } {ok 1 2 3 5 8 13 21 34 55 89}
285b8fd6c2fSdan
286b8fd6c2fSdan  catch { db close }
287b8fd6c2fSdan  catch { db2 close }
288b8fd6c2fSdan}
289b8fd6c2fSdan
2905f168a5dSdan#-------------------------------------------------------------------------
2915f168a5dSdan# Test case walcksum-2.* tests that if a statement transaction is rolled
2925f168a5dSdan# back after frames are written to the WAL, and then (after writing some
2935f168a5dSdan# more) the outer transaction is committed, the WAL file is still correctly
2945f168a5dSdan# formatted (and can be recovered by a second process if required).
2955f168a5dSdan#
29671d89919Sdando_test walcksum-2.1 {
297*fda06befSmistachkin  forcedelete test.db test.db-wal test.db-journal
29871d89919Sdan  sqlite3 db test.db
29971d89919Sdan  execsql {
30071d89919Sdan    PRAGMA synchronous = NORMAL;
30171d89919Sdan    PRAGMA page_size = 1024;
30271d89919Sdan    PRAGMA journal_mode = WAL;
30371d89919Sdan    PRAGMA cache_size = 10;
30471d89919Sdan    CREATE TABLE t1(x PRIMARY KEY);
30571d89919Sdan    PRAGMA wal_checkpoint;
30671d89919Sdan    INSERT INTO t1 VALUES(randomblob(800));
30771d89919Sdan    BEGIN;
30871d89919Sdan      INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*   2 */
30971d89919Sdan      INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*   4 */
31071d89919Sdan      INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*   8 */
31171d89919Sdan      INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*  16 */
31271d89919Sdan      SAVEPOINT one;
31371d89919Sdan        INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*  32 */
31471d89919Sdan        INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*  64 */
31571d89919Sdan        INSERT INTO t1 SELECT randomblob(800) FROM t1;   /* 128 */
31671d89919Sdan        INSERT INTO t1 SELECT randomblob(800) FROM t1;   /* 256 */
31771d89919Sdan      ROLLBACK TO one;
31871d89919Sdan      INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*  32 */
31971d89919Sdan      INSERT INTO t1 SELECT randomblob(800) FROM t1;   /*  64 */
32071d89919Sdan      INSERT INTO t1 SELECT randomblob(800) FROM t1;   /* 128 */
32171d89919Sdan      INSERT INTO t1 SELECT randomblob(800) FROM t1;   /* 256 */
32271d89919Sdan    COMMIT;
32371d89919Sdan  }
32471d89919Sdan
325*fda06befSmistachkin  forcecopy test.db test2.db
326*fda06befSmistachkin  forcecopy test.db-wal test2.db-wal
32771d89919Sdan
32871d89919Sdan  sqlite3 db2 test2.db
32971d89919Sdan  execsql {
33071d89919Sdan    PRAGMA integrity_check;
33171d89919Sdan    SELECT count(*) FROM t1;
33271d89919Sdan  } db2
33371d89919Sdan} {ok 256}
33471d89919Sdancatch { db close }
33571d89919Sdancatch { db2 close }
33671d89919Sdan
3375f168a5dSdan
338b8fd6c2fSdanfinish_test
339