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