walcksum.test 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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. set testdir [file dirname $argv0]
  13. source $testdir/tester.tcl
  14. source $testdir/lock_common.tcl
  15. source $testdir/wal_common.tcl
  16. ifcapable !wal {finish_test ; return }
  17. # Read and return the contents of file $filename. Treat the content as
  18. # binary data.
  19. #
  20. proc readfile {filename} {
  21. set fd [open $filename]
  22. fconfigure $fd -encoding binary
  23. fconfigure $fd -translation binary
  24. set data [read $fd]
  25. close $fd
  26. return $data
  27. }
  28. #
  29. # File $filename must be a WAL file on disk. Check that the checksum of frame
  30. # $iFrame in the file is correct when interpreting data as $endian-endian
  31. # integers ($endian must be either "big" or "little"). If the checksum looks
  32. # correct, return 1. Otherwise 0.
  33. #
  34. proc log_checksum_verify {filename iFrame endian} {
  35. set data [readfile $filename]
  36. foreach {offset c1 c2} [log_checksum_calc $data $iFrame $endian] {}
  37. binary scan [string range $data $offset [expr $offset+7]] II expect1 expect2
  38. set expect1 [expr $expect1&0xFFFFFFFF]
  39. set expect2 [expr $expect2&0xFFFFFFFF]
  40. expr {$c1==$expect1 && $c2==$expect2}
  41. }
  42. # File $filename must be a WAL file on disk. Compute the checksum for frame
  43. # $iFrame in the file by interpreting data as $endian-endian integers
  44. # ($endian must be either "big" or "little"). Then write the computed
  45. # checksum into the file.
  46. #
  47. proc log_checksum_write {filename iFrame endian} {
  48. set data [readfile $filename]
  49. foreach {offset c1 c2} [log_checksum_calc $data $iFrame $endian] {}
  50. set bin [binary format II $c1 $c2]
  51. set fd [open $filename r+]
  52. fconfigure $fd -encoding binary
  53. fconfigure $fd -translation binary
  54. seek $fd $offset
  55. puts -nonewline $fd $bin
  56. close $fd
  57. }
  58. # Calculate and return the checksum for a particular frame in a WAL.
  59. #
  60. # Arguments are:
  61. #
  62. # $data Blob containing the entire contents of a WAL.
  63. #
  64. # $iFrame Frame number within the $data WAL. Frames are numbered
  65. # starting at 1.
  66. #
  67. # $endian One of "big" or "little".
  68. #
  69. # Returns a list of three elements, as follows:
  70. #
  71. # * The byte offset of the checksum belonging to frame $iFrame in the WAL.
  72. # * The first integer in the calculated version of the checksum.
  73. # * The second integer in the calculated version of the checksum.
  74. #
  75. proc log_checksum_calc {data iFrame endian} {
  76. binary scan [string range $data 8 11] I pgsz
  77. if {$iFrame > 1} {
  78. set n [wal_file_size [expr $iFrame-2] $pgsz]
  79. binary scan [string range $data [expr $n+16] [expr $n+23]] II c1 c2
  80. } else {
  81. set c1 0
  82. set c2 0
  83. wal_cksum $endian c1 c2 [string range $data 0 23]
  84. }
  85. set n [wal_file_size [expr $iFrame-1] $pgsz]
  86. wal_cksum $endian c1 c2 [string range $data $n [expr $n+7]]
  87. wal_cksum $endian c1 c2 [string range $data [expr $n+24] [expr $n+24+$pgsz-1]]
  88. list [expr $n+16] $c1 $c2
  89. }
  90. #
  91. # File $filename must be a WAL file on disk. Set the 'magic' field of the
  92. # WAL header to indicate that checksums are $endian-endian ($endian must be
  93. # either "big" or "little").
  94. #
  95. # Also update the wal header checksum (since the wal header contents may
  96. # have changed).
  97. #
  98. proc log_checksum_writemagic {filename endian} {
  99. set val [expr {0x377f0682 | ($endian == "big" ? 1 : 0)}]
  100. set bin [binary format I $val]
  101. set fd [open $filename r+]
  102. fconfigure $fd -encoding binary
  103. fconfigure $fd -translation binary
  104. puts -nonewline $fd $bin
  105. seek $fd 0
  106. set blob [read $fd 24]
  107. set c1 0
  108. set c2 0
  109. wal_cksum $endian c1 c2 $blob
  110. seek $fd 24
  111. puts -nonewline $fd [binary format II $c1 $c2]
  112. close $fd
  113. }
  114. #-------------------------------------------------------------------------
  115. # Test cases walcksum-1.* attempt to verify the following:
  116. #
  117. # * That both native and non-native order checksum log files can
  118. # be recovered.
  119. #
  120. # * That when appending to native or non-native checksum log files
  121. # SQLite continues to use the right kind of checksums.
  122. #
  123. # * Test point 2 when the appending process is not one that recovered
  124. # the log file.
  125. #
  126. # * Test that both native and non-native checksum log files can be
  127. # checkpointed. And that after doing so the next write to the log
  128. # file occurs using native byte-order checksums.
  129. #
  130. set native "big"
  131. if {$::tcl_platform(byteOrder) == "littleEndian"} { set native "little" }
  132. foreach endian {big little} {
  133. # Create a database. Leave some data in the log file.
  134. #
  135. do_test walcksum-1.$endian.1 {
  136. catch { db close }
  137. forcedelete test.db test.db-wal test.db-journal
  138. sqlite3 db test.db
  139. execsql {
  140. PRAGMA page_size = 1024;
  141. PRAGMA auto_vacuum = 0;
  142. PRAGMA synchronous = NORMAL;
  143. CREATE TABLE t1(a PRIMARY KEY, b);
  144. INSERT INTO t1 VALUES(1, 'one');
  145. INSERT INTO t1 VALUES(2, 'two');
  146. INSERT INTO t1 VALUES(3, 'three');
  147. INSERT INTO t1 VALUES(5, 'five');
  148. PRAGMA journal_mode = WAL;
  149. INSERT INTO t1 VALUES(8, 'eight');
  150. INSERT INTO t1 VALUES(13, 'thirteen');
  151. INSERT INTO t1 VALUES(21, 'twentyone');
  152. }
  153. forcecopy test.db test2.db
  154. forcecopy test.db-wal test2.db-wal
  155. db close
  156. list [file size test2.db] [file size test2.db-wal]
  157. } [list [expr 1024*3] [wal_file_size 6 1024]]
  158. # Verify that the checksums are valid for all frames and that they
  159. # are calculated by interpreting data in native byte-order.
  160. #
  161. for {set f 1} {$f <= 6} {incr f} {
  162. do_test walcksum-1.$endian.2.$f {
  163. log_checksum_verify test2.db-wal $f $native
  164. } 1
  165. }
  166. # Replace all checksums in the current WAL file with $endian versions.
  167. # Then check that it is still possible to recover and read the database.
  168. #
  169. log_checksum_writemagic test2.db-wal $endian
  170. for {set f 1} {$f <= 6} {incr f} {
  171. do_test walcksum-1.$endian.3.$f {
  172. log_checksum_write test2.db-wal $f $endian
  173. log_checksum_verify test2.db-wal $f $endian
  174. } {1}
  175. }
  176. do_test walcksum-1.$endian.4.1 {
  177. forcecopy test2.db test.db
  178. forcecopy test2.db-wal test.db-wal
  179. sqlite3 db test.db
  180. execsql { SELECT a FROM t1 }
  181. } {1 2 3 5 8 13 21}
  182. # Following recovery, any frames written to the log should use the same
  183. # endianness as the existing frames. Check that this is the case.
  184. #
  185. do_test walcksum-1.$endian.5.0 {
  186. execsql {
  187. PRAGMA synchronous = NORMAL;
  188. INSERT INTO t1 VALUES(34, 'thirtyfour');
  189. }
  190. list [file size test.db] [file size test.db-wal]
  191. } [list [expr 1024*3] [wal_file_size 8 1024]]
  192. for {set f 1} {$f <= 8} {incr f} {
  193. do_test walcksum-1.$endian.5.$f {
  194. log_checksum_verify test.db-wal $f $endian
  195. } {1}
  196. }
  197. # Now connect a second connection to the database. Check that this one
  198. # (not the one that did recovery) also appends frames to the log using
  199. # the same endianness for checksums as the existing frames.
  200. #
  201. do_test walcksum-1.$endian.6 {
  202. sqlite3 db2 test.db
  203. execsql {
  204. PRAGMA integrity_check;
  205. SELECT a FROM t1;
  206. } db2
  207. } {ok 1 2 3 5 8 13 21 34}
  208. do_test walcksum-1.$endian.7.0 {
  209. execsql {
  210. PRAGMA synchronous = NORMAL;
  211. INSERT INTO t1 VALUES(55, 'fiftyfive');
  212. } db2
  213. list [file size test.db] [file size test.db-wal]
  214. } [list [expr 1024*3] [wal_file_size 10 1024]]
  215. for {set f 1} {$f <= 10} {incr f} {
  216. do_test walcksum-1.$endian.7.$f {
  217. log_checksum_verify test.db-wal $f $endian
  218. } {1}
  219. }
  220. # Now that both the recoverer and non-recoverer have added frames to the
  221. # log file, check that it can still be recovered.
  222. #
  223. forcecopy test.db test2.db
  224. forcecopy test.db-wal test2.db-wal
  225. do_test walcksum-1.$endian.7.11 {
  226. sqlite3 db3 test2.db
  227. execsql {
  228. PRAGMA integrity_check;
  229. SELECT a FROM t1;
  230. } db3
  231. } {ok 1 2 3 5 8 13 21 34 55}
  232. db3 close
  233. # Run a checkpoint on the database file. Then, check that any frames written
  234. # to the start of the log use native byte-order checksums.
  235. #
  236. do_test walcksum-1.$endian.8.1 {
  237. execsql {
  238. PRAGMA wal_checkpoint;
  239. INSERT INTO t1 VALUES(89, 'eightynine');
  240. }
  241. log_checksum_verify test.db-wal 1 $native
  242. } {1}
  243. do_test walcksum-1.$endian.8.2 {
  244. log_checksum_verify test.db-wal 2 $native
  245. } {1}
  246. do_test walcksum-1.$endian.8.3 {
  247. log_checksum_verify test.db-wal 3 $native
  248. } {0}
  249. do_test walcksum-1.$endian.9 {
  250. execsql {
  251. PRAGMA integrity_check;
  252. SELECT a FROM t1;
  253. } db2
  254. } {ok 1 2 3 5 8 13 21 34 55 89}
  255. catch { db close }
  256. catch { db2 close }
  257. }
  258. #-------------------------------------------------------------------------
  259. # Test case walcksum-2.* tests that if a statement transaction is rolled
  260. # back after frames are written to the WAL, and then (after writing some
  261. # more) the outer transaction is committed, the WAL file is still correctly
  262. # formatted (and can be recovered by a second process if required).
  263. #
  264. do_test walcksum-2.1 {
  265. forcedelete test.db test.db-wal test.db-journal
  266. sqlite3 db test.db
  267. execsql {
  268. PRAGMA synchronous = NORMAL;
  269. PRAGMA page_size = 1024;
  270. PRAGMA journal_mode = WAL;
  271. PRAGMA cache_size = 10;
  272. CREATE TABLE t1(x PRIMARY KEY);
  273. PRAGMA wal_checkpoint;
  274. INSERT INTO t1 VALUES(randomblob(800));
  275. BEGIN;
  276. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 2 */
  277. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 4 */
  278. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 8 */
  279. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 16 */
  280. SAVEPOINT one;
  281. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 32 */
  282. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 64 */
  283. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 128 */
  284. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 256 */
  285. ROLLBACK TO one;
  286. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 32 */
  287. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 64 */
  288. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 128 */
  289. INSERT INTO t1 SELECT randomblob(800) FROM t1; /* 256 */
  290. COMMIT;
  291. }
  292. forcecopy test.db test2.db
  293. forcecopy test.db-wal test2.db-wal
  294. sqlite3 db2 test2.db
  295. execsql {
  296. PRAGMA integrity_check;
  297. SELECT count(*) FROM t1;
  298. } db2
  299. } {ok 256}
  300. catch { db close }
  301. catch { db2 close }
  302. #-------------------------------------------------------------------------
  303. # Test case walcksum-3.* tests that the checksum calculation detects single
  304. # byte changes to frame or frame-header data and considers the frame
  305. # invalid as a result.
  306. #
  307. do_test walcksum-3.1 {
  308. forcedelete test.db test.db-wal test.db-journal
  309. sqlite3 db test.db
  310. execsql {
  311. PRAGMA synchronous = NORMAL;
  312. PRAGMA page_size = 1024;
  313. CREATE TABLE t1(a, b);
  314. INSERT INTO t1 VALUES(1, randomblob(300));
  315. INSERT INTO t1 VALUES(2, randomblob(300));
  316. PRAGMA journal_mode = WAL;
  317. INSERT INTO t1 VALUES(3, randomblob(300));
  318. }
  319. file size test.db-wal
  320. } [wal_file_size 1 1024]
  321. do_test walcksum-3.2 {
  322. forcecopy test.db-wal test2.db-wal
  323. forcecopy test.db test2.db
  324. sqlite3 db2 test2.db
  325. execsql { SELECT a FROM t1 } db2
  326. } {1 2 3}
  327. db2 close
  328. forcecopy test.db test2.db
  329. foreach incr {1 2 3 20 40 60 80 100 120 140 160 180 200 220 240 253 254 255} {
  330. do_test walcksum-3.3.$incr {
  331. set FAIL 0
  332. for {set iOff 0} {$iOff < [wal_file_size 1 1024]} {incr iOff} {
  333. forcecopy test.db-wal test2.db-wal
  334. set fd [open test2.db-wal r+]
  335. fconfigure $fd -encoding binary
  336. fconfigure $fd -translation binary
  337. seek $fd $iOff
  338. binary scan [read $fd 1] c x
  339. seek $fd $iOff
  340. puts -nonewline $fd [binary format c [expr {($x+$incr)&0xFF}]]
  341. close $fd
  342. sqlite3 db2 test2.db
  343. if { [execsql { SELECT a FROM t1 } db2] != "1 2" } {set FAIL 1}
  344. db2 close
  345. }
  346. set FAIL
  347. } {0}
  348. }
  349. finish_test