modernc.org/cc@v1.0.1/v2/testdata/_sqlite/test/wal2.test (about) 1 # 2010 May 5 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 # This file implements regression tests for SQLite library. The 12 # focus of this file is testing the operation of the library in 13 # "PRAGMA journal_mode=WAL" mode. 14 # 15 16 set testdir [file dirname $argv0] 17 source $testdir/tester.tcl 18 source $testdir/lock_common.tcl 19 source $testdir/malloc_common.tcl 20 source $testdir/wal_common.tcl 21 22 set testprefix wal2 23 24 ifcapable !wal {finish_test ; return } 25 26 set sqlite_sync_count 0 27 proc cond_incr_sync_count {adj} { 28 global sqlite_sync_count 29 if {$::tcl_platform(platform) == "windows"} { 30 incr sqlite_sync_count $adj 31 } { 32 ifcapable !dirsync { 33 incr sqlite_sync_count $adj 34 } 35 } 36 } 37 38 proc set_tvfs_hdr {file args} { 39 40 # Set $nHdr to the number of bytes in the wal-index header: 41 set nHdr 48 42 set nInt [expr {$nHdr/4}] 43 44 if {[llength $args]>2} { 45 error {wrong # args: should be "set_tvfs_hdr fileName ?val1? ?val2?"} 46 } 47 48 set blob [tvfs shm $file] 49 if {$::tcl_platform(byteOrder)=="bigEndian"} {set fmt I} {set fmt i} 50 51 if {[llength $args]} { 52 set ia [lindex $args 0] 53 set ib $ia 54 if {[llength $args]==2} { 55 set ib [lindex $args 1] 56 } 57 binary scan $blob a[expr $nHdr*2]a* dummy tail 58 set blob [binary format ${fmt}${nInt}${fmt}${nInt}a* $ia $ib $tail] 59 tvfs shm $file $blob 60 } 61 62 binary scan $blob ${fmt}${nInt} ints 63 return $ints 64 } 65 66 proc incr_tvfs_hdr {file idx incrval} { 67 set ints [set_tvfs_hdr $file] 68 set v [lindex $ints $idx] 69 incr v $incrval 70 lset ints $idx $v 71 set_tvfs_hdr $file $ints 72 } 73 74 75 #------------------------------------------------------------------------- 76 # Test case wal2-1.*: 77 # 78 # Set up a small database containing a single table. The database is not 79 # checkpointed during the test - all content resides in the log file. 80 # 81 # Two connections are established to the database file - a writer ([db]) 82 # and a reader ([db2]). For each of the 8 integer fields in the wal-index 83 # header (6 fields and 2 checksum values), do the following: 84 # 85 # 1. Modify the database using the writer. 86 # 87 # 2. Attempt to read the database using the reader. Before the reader 88 # has a chance to snapshot the wal-index header, increment one 89 # of the integer fields (so that the reader ends up with a corrupted 90 # header). 91 # 92 # 3. Check that the reader recovers the wal-index and reads the correct 93 # database content. 94 # 95 do_test wal2-1.0 { 96 proc tvfs_cb {method filename args} { 97 set ::filename $filename 98 return SQLITE_OK 99 } 100 101 testvfs tvfs 102 tvfs script tvfs_cb 103 tvfs filter xShmOpen 104 105 sqlite3 db test.db -vfs tvfs 106 sqlite3 db2 test.db -vfs tvfs 107 108 execsql { 109 PRAGMA journal_mode = WAL; 110 CREATE TABLE t1(a); 111 } db2 112 execsql { 113 INSERT INTO t1 VALUES(1); 114 INSERT INTO t1 VALUES(2); 115 INSERT INTO t1 VALUES(3); 116 INSERT INTO t1 VALUES(4); 117 SELECT count(a), sum(a) FROM t1; 118 } 119 } {4 10} 120 do_test wal2-1.1 { 121 execsql { SELECT count(a), sum(a) FROM t1 } db2 122 } {4 10} 123 124 set RECOVER [list \ 125 {0 1 lock exclusive} {1 7 lock exclusive} \ 126 {1 7 unlock exclusive} {0 1 unlock exclusive} \ 127 ] 128 set READ [list \ 129 {4 1 lock shared} {4 1 unlock shared} \ 130 ] 131 set INITSLOT [list \ 132 {4 1 lock exclusive} {4 1 unlock exclusive} \ 133 ] 134 135 foreach {tn iInsert res wal_index_hdr_mod wal_locks} " 136 2 5 {5 15} 0 {$RECOVER $READ} 137 3 6 {6 21} 1 {$RECOVER $READ} 138 4 7 {7 28} 2 {$RECOVER $READ} 139 5 8 {8 36} 3 {$RECOVER $READ} 140 6 9 {9 45} 4 {$RECOVER $READ} 141 7 10 {10 55} 5 {$RECOVER $READ} 142 8 11 {11 66} 6 {$RECOVER $READ} 143 9 12 {12 78} 7 {$RECOVER $READ} 144 10 13 {13 91} 8 {$RECOVER $READ} 145 11 14 {14 105} 9 {$RECOVER $READ} 146 12 15 {15 120} -1 {$INITSLOT $READ} 147 " { 148 149 do_test wal2-1.$tn.1 { 150 execsql { INSERT INTO t1 VALUES($iInsert) } 151 set ::locks [list] 152 proc tvfs_cb {method args} { 153 lappend ::locks [lindex $args 2] 154 return SQLITE_OK 155 } 156 tvfs filter xShmLock 157 if {$::wal_index_hdr_mod >= 0} { 158 incr_tvfs_hdr $::filename $::wal_index_hdr_mod 1 159 } 160 execsql { SELECT count(a), sum(a) FROM t1 } db2 161 } $res 162 163 do_test wal2-1.$tn.2 { 164 set ::locks 165 } $wal_locks 166 } 167 db close 168 db2 close 169 tvfs delete 170 forcedelete test.db test.db-wal test.db-journal 171 172 #------------------------------------------------------------------------- 173 # This test case is very similar to the previous one, except, after 174 # the reader reads the corrupt wal-index header, but before it has 175 # a chance to re-read it under the cover of the RECOVER lock, the 176 # wal-index header is replaced with a valid, but out-of-date, header. 177 # 178 # Because the header checksum looks Ok, the reader does not run recovery, 179 # it simply drops back to a READ lock and proceeds. But because the 180 # header is out-of-date, the reader reads the out-of-date snapshot. 181 # 182 # After this, the header is corrupted again and the reader is allowed 183 # to run recovery. This time, it sees an up-to-date snapshot of the 184 # database file. 185 # 186 set WRITER [list 0 1 lock exclusive] 187 set LOCKS [list \ 188 {0 1 lock exclusive} {0 1 unlock exclusive} \ 189 {4 1 lock exclusive} {4 1 unlock exclusive} \ 190 {4 1 lock shared} {4 1 unlock shared} \ 191 ] 192 do_test wal2-2.0 { 193 194 testvfs tvfs 195 tvfs script tvfs_cb 196 tvfs filter xShmOpen 197 proc tvfs_cb {method args} { 198 set ::filename [lindex $args 0] 199 return SQLITE_OK 200 } 201 202 sqlite3 db test.db -vfs tvfs 203 sqlite3 db2 test.db -vfs tvfs 204 205 execsql { 206 PRAGMA journal_mode = WAL; 207 CREATE TABLE t1(a); 208 } db2 209 execsql { 210 INSERT INTO t1 VALUES(1); 211 INSERT INTO t1 VALUES(2); 212 INSERT INTO t1 VALUES(3); 213 INSERT INTO t1 VALUES(4); 214 SELECT count(a), sum(a) FROM t1; 215 } 216 } {4 10} 217 do_test wal2-2.1 { 218 execsql { SELECT count(a), sum(a) FROM t1 } db2 219 } {4 10} 220 221 foreach {tn iInsert res0 res1 wal_index_hdr_mod} { 222 2 5 {4 10} {5 15} 0 223 3 6 {5 15} {6 21} 1 224 4 7 {6 21} {7 28} 2 225 5 8 {7 28} {8 36} 3 226 6 9 {8 36} {9 45} 4 227 7 10 {9 45} {10 55} 5 228 8 11 {10 55} {11 66} 6 229 9 12 {11 66} {12 78} 7 230 } { 231 tvfs filter xShmLock 232 233 do_test wal2-2.$tn.1 { 234 set oldhdr [set_tvfs_hdr $::filename] 235 execsql { INSERT INTO t1 VALUES($iInsert) } 236 execsql { SELECT count(a), sum(a) FROM t1 } 237 } $res1 238 239 do_test wal2-2.$tn.2 { 240 set ::locks [list] 241 proc tvfs_cb {method args} { 242 set lock [lindex $args 2] 243 lappend ::locks $lock 244 if {$lock == $::WRITER} { 245 set_tvfs_hdr $::filename $::oldhdr 246 } 247 return SQLITE_OK 248 } 249 250 if {$::wal_index_hdr_mod >= 0} { 251 incr_tvfs_hdr $::filename $::wal_index_hdr_mod 1 252 } 253 execsql { SELECT count(a), sum(a) FROM t1 } db2 254 } $res0 255 256 do_test wal2-2.$tn.3 { 257 set ::locks 258 } $LOCKS 259 260 do_test wal2-2.$tn.4 { 261 set ::locks [list] 262 proc tvfs_cb {method args} { 263 set lock [lindex $args 2] 264 lappend ::locks $lock 265 return SQLITE_OK 266 } 267 268 if {$::wal_index_hdr_mod >= 0} { 269 incr_tvfs_hdr $::filename $::wal_index_hdr_mod 1 270 } 271 execsql { SELECT count(a), sum(a) FROM t1 } db2 272 } $res1 273 } 274 db close 275 db2 close 276 tvfs delete 277 forcedelete test.db test.db-wal test.db-journal 278 279 280 if 0 { 281 #------------------------------------------------------------------------- 282 # This test case - wal2-3.* - tests the response of the library to an 283 # SQLITE_BUSY when attempting to obtain a READ or RECOVER lock. 284 # 285 # wal2-3.0 - 2: SQLITE_BUSY when obtaining a READ lock 286 # wal2-3.3 - 6: SQLITE_BUSY when obtaining a RECOVER lock 287 # 288 do_test wal2-3.0 { 289 proc tvfs_cb {method args} { 290 if {$method == "xShmLock"} { 291 if {[info exists ::locked]} { return SQLITE_BUSY } 292 } 293 return SQLITE_OK 294 } 295 296 proc busyhandler x { 297 if {$x>3} { unset -nocomplain ::locked } 298 return 0 299 } 300 301 testvfs tvfs 302 tvfs script tvfs_cb 303 sqlite3 db test.db -vfs tvfs 304 db busy busyhandler 305 306 execsql { 307 PRAGMA journal_mode = WAL; 308 CREATE TABLE t1(a); 309 INSERT INTO t1 VALUES(1); 310 INSERT INTO t1 VALUES(2); 311 INSERT INTO t1 VALUES(3); 312 INSERT INTO t1 VALUES(4); 313 } 314 315 set ::locked 1 316 info exists ::locked 317 } {1} 318 do_test wal2-3.1 { 319 execsql { SELECT count(a), sum(a) FROM t1 } 320 } {4 10} 321 do_test wal2-3.2 { 322 info exists ::locked 323 } {0} 324 325 do_test wal2-3.3 { 326 proc tvfs_cb {method args} { 327 if {$method == "xShmLock"} { 328 if {[info exists ::sabotage]} { 329 unset -nocomplain ::sabotage 330 incr_tvfs_hdr [lindex $args 0] 1 1 331 } 332 if {[info exists ::locked] && [lindex $args 2] == "RECOVER"} { 333 return SQLITE_BUSY 334 } 335 } 336 return SQLITE_OK 337 } 338 set ::sabotage 1 339 set ::locked 1 340 list [info exists ::sabotage] [info exists ::locked] 341 } {1 1} 342 do_test wal2-3.4 { 343 execsql { SELECT count(a), sum(a) FROM t1 } 344 } {4 10} 345 do_test wal2-3.5 { 346 list [info exists ::sabotage] [info exists ::locked] 347 } {0 0} 348 db close 349 tvfs delete 350 forcedelete test.db test.db-wal test.db-journal 351 352 } 353 354 #------------------------------------------------------------------------- 355 # Test that a database connection using a VFS that does not support the 356 # xShmXXX interfaces cannot open a WAL database. 357 # 358 do_test wal2-4.1 { 359 sqlite3 db test.db 360 execsql { 361 PRAGMA auto_vacuum = 0; 362 PRAGMA journal_mode = WAL; 363 CREATE TABLE data(x); 364 INSERT INTO data VALUES('need xShmOpen to see this'); 365 PRAGMA wal_checkpoint; 366 } 367 # Three pages in the WAL file at this point: One copy of page 1 and two 368 # of the root page for table "data". 369 } {wal 0 3 3} 370 do_test wal2-4.2 { 371 db close 372 testvfs tvfs -noshm 1 373 sqlite3 db test.db -vfs tvfs 374 catchsql { SELECT * FROM data } 375 } {1 {unable to open database file}} 376 do_test wal2-4.3 { 377 db close 378 testvfs tvfs 379 sqlite3 db test.db -vfs tvfs 380 catchsql { SELECT * FROM data } 381 } {0 {{need xShmOpen to see this}}} 382 db close 383 tvfs delete 384 385 #------------------------------------------------------------------------- 386 # Test that if a database connection is forced to run recovery before it 387 # can perform a checkpoint, it does not transition into RECOVER state. 388 # 389 # UPDATE: This has now changed. When running a checkpoint, if recovery is 390 # required the client grabs all exclusive locks (just as it would for a 391 # recovery performed as a pre-cursor to a normal database transaction). 392 # 393 set expected_locks [list] 394 lappend expected_locks {1 1 lock exclusive} ;# Lock checkpoint 395 lappend expected_locks {0 1 lock exclusive} ;# Lock writer 396 lappend expected_locks {2 6 lock exclusive} ;# Lock recovery & all aReadMark[] 397 lappend expected_locks {2 6 unlock exclusive} ;# Unlock recovery & aReadMark[] 398 lappend expected_locks {0 1 unlock exclusive} ;# Unlock writer 399 lappend expected_locks {3 1 lock exclusive} ;# Lock aReadMark[0] 400 lappend expected_locks {3 1 unlock exclusive} ;# Unlock aReadMark[0] 401 lappend expected_locks {1 1 unlock exclusive} ;# Unlock checkpoint 402 do_test wal2-5.1 { 403 proc tvfs_cb {method args} { 404 set ::shm_file [lindex $args 0] 405 if {$method == "xShmLock"} { lappend ::locks [lindex $args 2] } 406 return $::tvfs_cb_return 407 } 408 set tvfs_cb_return SQLITE_OK 409 410 testvfs tvfs 411 tvfs script tvfs_cb 412 413 sqlite3 db test.db -vfs tvfs 414 execsql { 415 PRAGMA journal_mode = WAL; 416 CREATE TABLE x(y); 417 INSERT INTO x VALUES(1); 418 } 419 420 incr_tvfs_hdr $::shm_file 1 1 421 set ::locks [list] 422 execsql { PRAGMA wal_checkpoint } 423 set ::locks 424 } $expected_locks 425 db close 426 tvfs delete 427 428 #------------------------------------------------------------------------- 429 # This block, test cases wal2-6.*, tests the operation of WAL with 430 # "PRAGMA locking_mode=EXCLUSIVE" set. 431 # 432 # wal2-6.1.*: Changing to WAL mode before setting locking_mode=exclusive. 433 # 434 # wal2-6.2.*: Changing to WAL mode after setting locking_mode=exclusive. 435 # 436 # wal2-6.3.*: Changing back to rollback mode from WAL mode after setting 437 # locking_mode=exclusive. 438 # 439 # wal2-6.4.*: Check that xShmLock calls are omitted in exclusive locking 440 # mode. 441 # 442 # wal2-6.5.*: 443 # 444 # wal2-6.6.*: Check that if the xShmLock() to reaquire a WAL read-lock when 445 # exiting exclusive mode fails (i.e. SQLITE_IOERR), then the 446 # connection silently remains in exclusive mode. 447 # 448 do_test wal2-6.1.1 { 449 forcedelete test.db test.db-wal test.db-journal 450 sqlite3 db test.db 451 execsql { 452 Pragma Journal_Mode = Wal; 453 } 454 } {wal} 455 do_test wal2-6.1.2 { 456 execsql { PRAGMA lock_status } 457 } {main unlocked temp closed} 458 do_test wal2-6.1.3 { 459 execsql { 460 SELECT * FROM sqlite_master; 461 Pragma Locking_Mode = Exclusive; 462 } 463 execsql { 464 BEGIN; 465 CREATE TABLE t1(a, b); 466 INSERT INTO t1 VALUES(1, 2); 467 COMMIT; 468 PRAGMA lock_status; 469 } 470 } {main exclusive temp closed} 471 do_test wal2-6.1.4 { 472 execsql { 473 PRAGMA locking_mode = normal; 474 PRAGMA lock_status; 475 } 476 } {normal main exclusive temp closed} 477 do_test wal2-6.1.5 { 478 execsql { 479 SELECT * FROM t1; 480 PRAGMA lock_status; 481 } 482 } {1 2 main shared temp closed} 483 do_test wal2-6.1.6 { 484 execsql { 485 INSERT INTO t1 VALUES(3, 4); 486 PRAGMA lock_status; 487 } 488 } {main shared temp closed} 489 db close 490 491 do_test wal2-6.2.1 { 492 forcedelete test.db test.db-wal test.db-journal 493 sqlite3 db test.db 494 execsql { 495 Pragma Locking_Mode = Exclusive; 496 Pragma Journal_Mode = Wal; 497 Pragma Lock_Status; 498 } 499 } {exclusive wal main exclusive temp closed} 500 do_test wal2-6.2.2 { 501 execsql { 502 BEGIN; 503 CREATE TABLE t1(a, b); 504 INSERT INTO t1 VALUES(1, 2); 505 COMMIT; 506 Pragma loCK_STATus; 507 } 508 } {main exclusive temp closed} 509 do_test wal2-6.2.3 { 510 db close 511 sqlite3 db test.db 512 execsql { SELECT * FROM sqlite_master } 513 execsql { PRAGMA LOCKING_MODE = EXCLUSIVE } 514 } {exclusive} 515 do_test wal2-6.2.4 { 516 execsql { 517 SELECT * FROM t1; 518 pragma lock_status; 519 } 520 } {1 2 main shared temp closed} 521 do_test wal2-6.2.5 { 522 execsql { 523 INSERT INTO t1 VALUES(3, 4); 524 pragma lock_status; 525 } 526 } {main exclusive temp closed} 527 do_test wal2-6.2.6 { 528 execsql { 529 PRAGMA locking_mode = NORMAL; 530 pragma lock_status; 531 } 532 } {normal main exclusive temp closed} 533 do_test wal2-6.2.7 { 534 execsql { 535 BEGIN IMMEDIATE; COMMIT; 536 pragma lock_status; 537 } 538 } {main shared temp closed} 539 do_test wal2-6.2.8 { 540 execsql { 541 PRAGMA locking_mode = EXCLUSIVE; 542 BEGIN IMMEDIATE; COMMIT; 543 PRAGMA locking_mode = NORMAL; 544 } 545 execsql { 546 SELECT * FROM t1; 547 pragma lock_status; 548 } 549 } {1 2 3 4 main shared temp closed} 550 do_test wal2-6.2.9 { 551 execsql { 552 INSERT INTO t1 VALUES(5, 6); 553 SELECT * FROM t1; 554 pragma lock_status; 555 } 556 } {1 2 3 4 5 6 main shared temp closed} 557 db close 558 559 do_test wal2-6.3.1 { 560 forcedelete test.db test.db-wal test.db-journal 561 sqlite3 db test.db 562 execsql { 563 PRAGMA journal_mode = WAL; 564 PRAGMA locking_mode = exclusive; 565 BEGIN; 566 CREATE TABLE t1(x); 567 INSERT INTO t1 VALUES('Chico'); 568 INSERT INTO t1 VALUES('Harpo'); 569 COMMIT; 570 } 571 list [file exists test.db-wal] [file exists test.db-journal] 572 } {1 0} 573 do_test wal2-6.3.2 { 574 execsql { PRAGMA journal_mode = DELETE } 575 file exists test.db-wal 576 } {0} 577 do_test wal2-6.3.3 { 578 execsql { PRAGMA lock_status } 579 } {main exclusive temp closed} 580 do_test wal2-6.3.4 { 581 execsql { 582 BEGIN; 583 INSERT INTO t1 VALUES('Groucho'); 584 } 585 list [file exists test.db-wal] [file exists test.db-journal] 586 } {0 1} 587 do_test wal2-6.3.5 { 588 execsql { PRAGMA lock_status } 589 } {main exclusive temp closed} 590 do_test wal2-6.3.6 { 591 execsql { COMMIT } 592 list [file exists test.db-wal] [file exists test.db-journal] 593 } {0 1} 594 do_test wal2-6.3.7 { 595 execsql { PRAGMA lock_status } 596 } {main exclusive temp closed} 597 db close 598 599 600 # This test - wal2-6.4.* - uses a single database connection and the 601 # [testvfs] instrumentation to test that xShmLock() is being called 602 # as expected when a WAL database is used with locking_mode=exclusive. 603 # 604 do_test wal2-6.4.1 { 605 forcedelete test.db test.db-wal test.db-journal 606 proc tvfs_cb {method args} { 607 set ::shm_file [lindex $args 0] 608 if {$method == "xShmLock"} { lappend ::locks [lindex $args 2] } 609 return "SQLITE_OK" 610 } 611 testvfs tvfs 612 tvfs script tvfs_cb 613 sqlite3 db test.db -vfs tvfs 614 set {} {} 615 } {} 616 617 set RECOVERY { 618 {0 1 lock exclusive} {1 7 lock exclusive} 619 {1 7 unlock exclusive} {0 1 unlock exclusive} 620 } 621 set READMARK0_READ { 622 {3 1 lock shared} {3 1 unlock shared} 623 } 624 set READMARK0_WRITE { 625 {3 1 lock shared} 626 {0 1 lock exclusive} {3 1 unlock shared} 627 {4 1 lock exclusive} {4 1 unlock exclusive} {4 1 lock shared} 628 {0 1 unlock exclusive} {4 1 unlock shared} 629 } 630 set READMARK1_SET { 631 {4 1 lock exclusive} {4 1 unlock exclusive} 632 } 633 set READMARK1_READ { 634 {4 1 lock shared} {4 1 unlock shared} 635 } 636 set READMARK1_WRITE { 637 {4 1 lock shared} 638 {0 1 lock exclusive} {0 1 unlock exclusive} 639 {4 1 unlock shared} 640 } 641 642 foreach {tn sql res expected_locks} { 643 2 { 644 PRAGMA auto_vacuum = 0; 645 PRAGMA journal_mode = WAL; 646 BEGIN; 647 CREATE TABLE t1(x); 648 INSERT INTO t1 VALUES('Leonard'); 649 INSERT INTO t1 VALUES('Arthur'); 650 COMMIT; 651 } {wal} { 652 $RECOVERY 653 $READMARK0_WRITE 654 } 655 656 3 { 657 # This test should do the READMARK1_SET locking to populate the 658 # aReadMark[1] slot with the current mxFrame value. Followed by 659 # READMARK1_READ to read the database. 660 # 661 SELECT * FROM t1 662 } {Leonard Arthur} { 663 $READMARK1_SET 664 $READMARK1_READ 665 } 666 667 4 { 668 # aReadMark[1] is already set to mxFrame. So just READMARK1_READ 669 # this time, not READMARK1_SET. 670 # 671 SELECT * FROM t1 ORDER BY x 672 } {Arthur Leonard} { 673 $READMARK1_READ 674 } 675 676 5 { 677 PRAGMA locking_mode = exclusive 678 } {exclusive} { } 679 680 6 { 681 INSERT INTO t1 VALUES('Julius Henry'); 682 SELECT * FROM t1; 683 } {Leonard Arthur {Julius Henry}} { 684 $READMARK1_READ 685 } 686 687 7 { 688 INSERT INTO t1 VALUES('Karl'); 689 SELECT * FROM t1; 690 } {Leonard Arthur {Julius Henry} Karl} { } 691 692 8 { 693 PRAGMA locking_mode = normal 694 } {normal} { } 695 696 9 { 697 SELECT * FROM t1 ORDER BY x 698 } {Arthur {Julius Henry} Karl Leonard} $READMARK1_READ 699 700 10 { DELETE FROM t1 } {} $READMARK1_WRITE 701 702 11 { 703 SELECT * FROM t1 704 } {} { 705 $READMARK1_SET 706 $READMARK1_READ 707 } 708 } { 709 710 set L [list] 711 foreach el [subst $expected_locks] { lappend L $el } 712 713 set S "" 714 foreach sq [split $sql "\n"] { 715 set sq [string trim $sq] 716 if {[string match {#*} $sq]==0} {append S "$sq\n"} 717 } 718 719 set ::locks [list] 720 do_test wal2-6.4.$tn.1 { execsql $S } $res 721 do_test wal2-6.4.$tn.2 { set ::locks } $L 722 } 723 724 db close 725 tvfs delete 726 727 do_test wal2-6.5.1 { 728 sqlite3 db test.db 729 execsql { 730 PRAGMA auto_vacuum = 0; 731 PRAGMA journal_mode = wal; 732 PRAGMA locking_mode = exclusive; 733 CREATE TABLE t2(a, b); 734 PRAGMA wal_checkpoint; 735 INSERT INTO t2 VALUES('I', 'II'); 736 PRAGMA journal_mode; 737 } 738 } {wal exclusive 0 2 2 wal} 739 do_test wal2-6.5.2 { 740 execsql { 741 PRAGMA locking_mode = normal; 742 INSERT INTO t2 VALUES('III', 'IV'); 743 PRAGMA locking_mode = exclusive; 744 SELECT * FROM t2; 745 } 746 } {normal exclusive I II III IV} 747 do_test wal2-6.5.3 { 748 execsql { PRAGMA wal_checkpoint } 749 } {0 2 2} 750 db close 751 752 proc lock_control {method filename handle spec} { 753 foreach {start n op type} $spec break 754 if {$op == "lock"} { return SQLITE_IOERR } 755 return SQLITE_OK 756 } 757 do_test wal2-6.6.1 { 758 testvfs T 759 T script lock_control 760 T filter {} 761 sqlite3 db test.db -vfs T 762 execsql { SELECT * FROM sqlite_master } 763 execsql { PRAGMA locking_mode = exclusive } 764 execsql { INSERT INTO t2 VALUES('V', 'VI') } 765 } {} 766 do_test wal2-6.6.2 { 767 execsql { PRAGMA locking_mode = normal } 768 T filter xShmLock 769 execsql { INSERT INTO t2 VALUES('VII', 'VIII') } 770 } {} 771 do_test wal2-6.6.3 { 772 # At this point the connection should still be in exclusive-mode, even 773 # though it tried to exit exclusive-mode when committing the INSERT 774 # statement above. To exit exclusive mode, SQLite has to take a read-lock 775 # on the WAL file using xShmLock(). Since that call failed, it remains 776 # in exclusive mode. 777 # 778 sqlite3 db2 test.db -vfs T 779 catchsql { SELECT * FROM t2 } db2 780 } {1 {database is locked}} 781 do_test wal2-6.6.2 { 782 db2 close 783 T filter {} 784 execsql { INSERT INTO t2 VALUES('IX', 'X') } 785 } {} 786 do_test wal2-6.6.4 { 787 # This time, we have successfully exited exclusive mode. So the second 788 # connection can read the database. 789 sqlite3 db2 test.db -vfs T 790 catchsql { SELECT * FROM t2 } db2 791 } {0 {I II III IV V VI VII VIII IX X}} 792 793 db close 794 db2 close 795 T delete 796 797 #------------------------------------------------------------------------- 798 # Test a theory about the checksum algorithm. Theory was false and this 799 # test did not provoke a bug. 800 # 801 forcedelete test.db test.db-wal test.db-journal 802 do_test wal2-7.1.1 { 803 sqlite3 db test.db 804 execsql { 805 PRAGMA page_size = 4096; 806 PRAGMA journal_mode = WAL; 807 CREATE TABLE t1(a, b); 808 } 809 file size test.db 810 } {4096} 811 do_test wal2-7.1.2 { 812 forcecopy test.db test2.db 813 forcecopy test.db-wal test2.db-wal 814 # The first 32 bytes of the WAL file contain the WAL header. Offset 48 815 # is the first byte of the checksum for the first frame in the WAL. 816 # The following three lines replaces the contents of that byte with 817 # a different value. 818 set newval FF 819 if {$newval == [hexio_read test2.db-wal 48 1]} { set newval 00 } 820 hexio_write test2.db-wal 48 $newval 821 } {1} 822 do_test wal2-7.1.3 { 823 sqlite3 db2 test2.db 824 execsql { PRAGMA wal_checkpoint } db2 825 execsql { SELECT * FROM sqlite_master } db2 826 } {} 827 db close 828 db2 close 829 forcedelete test.db test.db-wal test.db-journal 830 do_test wal2-8.1.2 { 831 sqlite3 db test.db 832 execsql { 833 PRAGMA auto_vacuum=OFF; 834 PRAGMA page_size = 1024; 835 PRAGMA journal_mode = WAL; 836 CREATE TABLE t1(x); 837 INSERT INTO t1 VALUES(zeroblob(8188*1020)); 838 CREATE TABLE t2(y); 839 PRAGMA wal_checkpoint; 840 } 841 execsql { 842 SELECT rootpage>=8192 FROM sqlite_master WHERE tbl_name = 't2'; 843 } 844 } {1} 845 do_test wal2-8.1.3 { 846 execsql { 847 PRAGMA cache_size = 10; 848 CREATE TABLE t3(z); 849 BEGIN; 850 INSERT INTO t3 VALUES(randomblob(900)); 851 INSERT INTO t3 SELECT randomblob(900) FROM t3; 852 INSERT INTO t2 VALUES('hello'); 853 INSERT INTO t3 SELECT randomblob(900) FROM t3; 854 INSERT INTO t3 SELECT randomblob(900) FROM t3; 855 INSERT INTO t3 SELECT randomblob(900) FROM t3; 856 INSERT INTO t3 SELECT randomblob(900) FROM t3; 857 INSERT INTO t3 SELECT randomblob(900) FROM t3; 858 INSERT INTO t3 SELECT randomblob(900) FROM t3; 859 ROLLBACK; 860 } 861 execsql { 862 INSERT INTO t2 VALUES('goodbye'); 863 INSERT INTO t3 SELECT randomblob(900) FROM t3; 864 INSERT INTO t3 SELECT randomblob(900) FROM t3; 865 } 866 } {} 867 do_test wal2-8.1.4 { 868 sqlite3 db2 test.db 869 execsql { SELECT * FROM t2 } 870 } {goodbye} 871 db2 close 872 db close 873 874 #------------------------------------------------------------------------- 875 # Test that even if the checksums for both are valid, if the two copies 876 # of the wal-index header in the wal-index do not match, the client 877 # runs (or at least tries to run) database recovery. 878 # 879 # 880 proc get_name {method args} { set ::filename [lindex $args 0] ; tvfs filter {} } 881 testvfs tvfs 882 tvfs script get_name 883 tvfs filter xShmOpen 884 885 forcedelete test.db test.db-wal test.db-journal 886 do_test wal2-9.1 { 887 sqlite3 db test.db -vfs tvfs 888 execsql { 889 PRAGMA journal_mode = WAL; 890 CREATE TABLE x(y); 891 INSERT INTO x VALUES('Barton'); 892 INSERT INTO x VALUES('Deakin'); 893 } 894 895 # Set $wih(1) to the contents of the wal-index header after 896 # the frames associated with the first two rows in table 'x' have 897 # been inserted. Then insert one more row and set $wih(2) 898 # to the new value of the wal-index header. 899 # 900 # If the $wih(1) is written into the wal-index before running 901 # a read operation, the client will see only the first two rows. If 902 # $wih(2) is written into the wal-index, the client will see 903 # three rows. If an invalid header is written into the wal-index, then 904 # the client will run recovery and see three rows. 905 # 906 set wih(1) [set_tvfs_hdr $::filename] 907 execsql { INSERT INTO x VALUES('Watson') } 908 set wih(2) [set_tvfs_hdr $::filename] 909 910 sqlite3 db2 test.db -vfs tvfs 911 execsql { SELECT * FROM x } db2 912 } {Barton Deakin Watson} 913 914 foreach {tn hdr1 hdr2 res} [list \ 915 3 $wih(1) $wih(1) {Barton Deakin} \ 916 4 $wih(1) $wih(2) {Barton Deakin Watson} \ 917 5 $wih(2) $wih(1) {Barton Deakin Watson} \ 918 6 $wih(2) $wih(2) {Barton Deakin Watson} \ 919 7 $wih(1) $wih(1) {Barton Deakin} \ 920 8 {0 0 0 0 0 0 0 0 0 0 0 0} {0 0 0 0 0 0 0 0 0 0 0 0} {Barton Deakin Watson} 921 ] { 922 do_test wal2-9.$tn { 923 set_tvfs_hdr $::filename $hdr1 $hdr2 924 execsql { SELECT * FROM x } db2 925 } $res 926 } 927 928 db2 close 929 db close 930 931 #------------------------------------------------------------------------- 932 # This block of tests - wal2-10.* - focus on the libraries response to 933 # new versions of the wal or wal-index formats. 934 # 935 # wal2-10.1.*: Test that the library refuses to "recover" a new WAL 936 # format. 937 # 938 # wal2-10.2.*: Test that the library refuses to read or write a database 939 # if the wal-index version is newer than it understands. 940 # 941 # At time of writing, the only versions of the wal and wal-index formats 942 # that exist are versions 3007000 (corresponding to SQLite version 3.7.0, 943 # the first version of SQLite to feature wal mode). 944 # 945 do_test wal2-10.1.1 { 946 faultsim_delete_and_reopen 947 execsql { 948 PRAGMA journal_mode = WAL; 949 CREATE TABLE t1(a, b); 950 PRAGMA wal_checkpoint; 951 INSERT INTO t1 VALUES(1, 2); 952 INSERT INTO t1 VALUES(3, 4); 953 } 954 faultsim_save_and_close 955 } {} 956 do_test wal2-10.1.2 { 957 faultsim_restore_and_reopen 958 execsql { SELECT * FROM t1 } 959 } {1 2 3 4} 960 do_test wal2-10.1.3 { 961 faultsim_restore_and_reopen 962 set hdr [wal_set_walhdr test.db-wal] 963 lindex $hdr 1 964 } {3007000} 965 do_test wal2-10.1.4 { 966 lset hdr 1 3007001 967 wal_set_walhdr test.db-wal $hdr 968 catchsql { SELECT * FROM t1 } 969 } {1 {unable to open database file}} 970 971 testvfs tvfs -default 1 972 do_test wal2-10.2.1 { 973 faultsim_restore_and_reopen 974 execsql { SELECT * FROM t1 } 975 } {1 2 3 4} 976 do_test wal2-10.2.2 { 977 set hdr [set_tvfs_hdr $::filename] 978 lindex $hdr 0 979 } {3007000} 980 do_test wal2-10.2.3 { 981 lset hdr 0 3007001 982 wal_fix_walindex_cksum hdr 983 set_tvfs_hdr $::filename $hdr 984 catchsql { SELECT * FROM t1 } 985 } {1 {unable to open database file}} 986 db close 987 tvfs delete 988 989 #------------------------------------------------------------------------- 990 # This block of tests - wal2-11.* - tests that it is not possible to put 991 # the library into an infinite loop by presenting it with a corrupt 992 # hash table (one that appears to contain a single chain of infinite 993 # length). 994 # 995 # wal2-11.1.*: While reading the hash-table. 996 # 997 # wal2-11.2.*: While writing the hash-table. 998 # 999 testvfs tvfs -default 1 1000 do_test wal2-11.0 { 1001 faultsim_delete_and_reopen 1002 execsql { 1003 PRAGMA journal_mode = WAL; 1004 CREATE TABLE t1(a, b, c); 1005 INSERT INTO t1 VALUES(1, 2, 3); 1006 INSERT INTO t1 VALUES(4, 5, 6); 1007 INSERT INTO t1 VALUES(7, 8, 9); 1008 SELECT * FROM t1; 1009 } 1010 } {wal 1 2 3 4 5 6 7 8 9} 1011 1012 do_test wal2-11.1.1 { 1013 sqlite3 db2 test.db 1014 execsql { SELECT name FROM sqlite_master } db2 1015 } {t1} 1016 1017 if {$::tcl_version>=8.5} { 1018 # Set all zeroed slots in the first hash table to invalid values. 1019 # 1020 set blob [string range [tvfs shm $::filename] 0 16383] 1021 set I [string range [tvfs shm $::filename] 16384 end] 1022 binary scan $I t* L 1023 set I [list] 1024 foreach p $L { 1025 lappend I [expr $p ? $p : 400] 1026 } 1027 append blob [binary format t* $I] 1028 tvfs shm $::filename $blob 1029 do_test wal2-11.2 { 1030 catchsql { INSERT INTO t1 VALUES(10, 11, 12) } 1031 } {1 {database disk image is malformed}} 1032 1033 # Fill up the hash table on the first page of shared memory with 0x55 bytes. 1034 # 1035 set blob [string range [tvfs shm $::filename] 0 16383] 1036 append blob [string repeat [binary format c 55] 16384] 1037 tvfs shm $::filename $blob 1038 do_test wal2-11.3 { 1039 catchsql { SELECT * FROM t1 } db2 1040 } {1 {database disk image is malformed}} 1041 } 1042 1043 db close 1044 db2 close 1045 tvfs delete 1046 1047 #------------------------------------------------------------------------- 1048 # If a connection is required to create a WAL or SHM file, it creates 1049 # the new files with the same file-system permissions as the database 1050 # file itself. Test this. 1051 # 1052 if {$::tcl_platform(platform) == "unix"} { 1053 faultsim_delete_and_reopen 1054 # Changed on 2012-02-13: umask is deliberately ignored for -wal files. 1055 #set umask [exec /bin/sh -c umask] 1056 set umask 0 1057 1058 1059 do_test wal2-12.1 { 1060 sqlite3 db test.db 1061 execsql { 1062 CREATE TABLE tx(y, z); 1063 PRAGMA journal_mode = WAL; 1064 } 1065 db close 1066 list [file exists test.db-wal] [file exists test.db-shm] 1067 } {0 0} 1068 1069 foreach {tn permissions} { 1070 1 00644 1071 2 00666 1072 3 00600 1073 4 00755 1074 } { 1075 set effective [format %.5o [expr $permissions & ~$umask]] 1076 do_test wal2-12.2.$tn.1 { 1077 file attributes test.db -permissions $permissions 1078 file attributes test.db -permissions 1079 } $permissions 1080 do_test wal2-12.2.$tn.2 { 1081 list [file exists test.db-wal] [file exists test.db-shm] 1082 } {0 0} 1083 do_test wal2-12.2.$tn.3 { 1084 sqlite3 db test.db 1085 execsql { INSERT INTO tx DEFAULT VALUES } 1086 list [file exists test.db-wal] [file exists test.db-shm] 1087 } {1 1} 1088 do_test wal2-12.2.$tn.4 { 1089 list [file attr test.db-wal -perm] [file attr test.db-shm -perm] 1090 } [list $effective $effective] 1091 do_test wal2-12.2.$tn.5 { 1092 db close 1093 list [file exists test.db-wal] [file exists test.db-shm] 1094 } {0 0} 1095 } 1096 } 1097 1098 #------------------------------------------------------------------------- 1099 # Test the libraries response to discovering that one or more of the 1100 # database, wal or shm files cannot be opened, or can only be opened 1101 # read-only. 1102 # 1103 if {$::tcl_platform(platform) == "unix"} { 1104 proc perm {} { 1105 set L [list] 1106 foreach f {test.db test.db-wal test.db-shm} { 1107 if {[file exists $f]} { 1108 lappend L [file attr $f -perm] 1109 } else { 1110 lappend L {} 1111 } 1112 } 1113 set L 1114 } 1115 1116 faultsim_delete_and_reopen 1117 execsql { 1118 PRAGMA journal_mode = WAL; 1119 CREATE TABLE t1(a, b); 1120 PRAGMA wal_checkpoint; 1121 INSERT INTO t1 VALUES('3.14', '2.72'); 1122 } 1123 do_test wal2-13.1.1 { 1124 list [file exists test.db-shm] [file exists test.db-wal] 1125 } {1 1} 1126 faultsim_save_and_close 1127 1128 foreach {tn db_perm wal_perm shm_perm can_open can_read can_write} { 1129 2 00644 00644 00644 1 1 1 1130 3 00644 00400 00644 1 1 0 1131 4 00644 00644 00400 1 0 0 1132 5 00400 00644 00644 1 1 0 1133 1134 7 00644 00000 00644 1 0 0 1135 8 00644 00644 00000 1 0 0 1136 9 00000 00644 00644 0 0 0 1137 } { 1138 faultsim_restore 1139 do_test wal2-13.$tn.1 { 1140 file attr test.db -perm $db_perm 1141 file attr test.db-wal -perm $wal_perm 1142 file attr test.db-shm -perm $shm_perm 1143 1144 set L [file attr test.db -perm] 1145 lappend L [file attr test.db-wal -perm] 1146 lappend L [file attr test.db-shm -perm] 1147 } [list $db_perm $wal_perm $shm_perm] 1148 1149 # If $can_open is true, then it should be possible to open a database 1150 # handle. Otherwise, if $can_open is 0, attempting to open the db 1151 # handle throws an "unable to open database file" exception. 1152 # 1153 set r(1) {0 ok} 1154 set r(0) {1 {unable to open database file}} 1155 do_test wal2-13.$tn.2 { 1156 list [catch {sqlite3 db test.db ; set {} ok} msg] $msg 1157 } $r($can_open) 1158 1159 if {$can_open} { 1160 1161 # If $can_read is true, then the client should be able to read from 1162 # the database file. If $can_read is false, attempting to read should 1163 # throw the "unable to open database file" exception. 1164 # 1165 set a(0) {1 {unable to open database file}} 1166 set a(1) {0 {3.14 2.72}} 1167 do_test wal2-13.$tn.3 { 1168 catchsql { SELECT * FROM t1 } 1169 } $a($can_read) 1170 1171 # Now try to write to the db file. If the client can read but not 1172 # write, then it should throw the familiar "unable to open db file" 1173 # exception. If it can read but not write, the exception should 1174 # be "attempt to write a read only database". 1175 # 1176 # If the client can read and write, the operation should succeed. 1177 # 1178 set b(0,0) {1 {unable to open database file}} 1179 set b(1,0) {1 {attempt to write a readonly database}} 1180 set b(1,1) {0 {}} 1181 do_test wal2-13.$tn.4 { 1182 catchsql { INSERT INTO t1 DEFAULT VALUES } 1183 } $b($can_read,$can_write) 1184 } 1185 catch { db close } 1186 } 1187 } 1188 1189 #------------------------------------------------------------------------- 1190 # Test that "PRAGMA checkpoint_fullsync" appears to be working. 1191 # 1192 foreach {tn sql reslist} { 1193 1 { } {10 0 4 0 6 0} 1194 2 { PRAGMA checkpoint_fullfsync = 1 } {10 6 4 3 6 3} 1195 3 { PRAGMA checkpoint_fullfsync = 0 } {10 0 4 0 6 0} 1196 } { 1197 ifcapable default_ckptfullfsync { 1198 if {[string trim $sql]==""} continue 1199 } 1200 faultsim_delete_and_reopen 1201 1202 execsql {PRAGMA auto_vacuum = 0; PRAGMA synchronous = FULL;} 1203 execsql $sql 1204 do_execsql_test wal2-14.$tn.0 { PRAGMA page_size = 4096 } {} 1205 do_execsql_test wal2-14.$tn.1 { PRAGMA journal_mode = WAL } {wal} 1206 1207 set sqlite_sync_count 0 1208 set sqlite_fullsync_count 0 1209 1210 do_execsql_test wal2-14.$tn.2 { 1211 PRAGMA wal_autocheckpoint = 10; 1212 CREATE TABLE t1(a, b); -- 2 wal syncs 1213 INSERT INTO t1 VALUES(1, 2); -- 2 wal sync 1214 PRAGMA wal_checkpoint; -- 1 wal sync, 1 db sync 1215 BEGIN; 1216 INSERT INTO t1 VALUES(3, 4); 1217 INSERT INTO t1 VALUES(5, 6); 1218 COMMIT; -- 2 wal sync 1219 PRAGMA wal_checkpoint; -- 1 wal sync, 1 db sync 1220 } {10 0 3 3 0 1 1} 1221 1222 do_test wal2-14.$tn.3 { 1223 cond_incr_sync_count 1 1224 list $sqlite_sync_count $sqlite_fullsync_count 1225 } [lrange $reslist 0 1] 1226 1227 set sqlite_sync_count 0 1228 set sqlite_fullsync_count 0 1229 1230 do_test wal2-14.$tn.4 { 1231 execsql { INSERT INTO t1 VALUES(7, zeroblob(12*4096)) } 1232 list $sqlite_sync_count $sqlite_fullsync_count 1233 } [lrange $reslist 2 3] 1234 1235 set sqlite_sync_count 0 1236 set sqlite_fullsync_count 0 1237 1238 do_test wal2-14.$tn.5 { 1239 execsql { PRAGMA wal_autocheckpoint = 1000 } 1240 execsql { INSERT INTO t1 VALUES(9, 10) } 1241 execsql { INSERT INTO t1 VALUES(11, 12) } 1242 execsql { INSERT INTO t1 VALUES(13, 14) } 1243 db close 1244 list $sqlite_sync_count $sqlite_fullsync_count 1245 } [lrange $reslist 4 5] 1246 } 1247 1248 catch { db close } 1249 1250 # PRAGMA checkpoint_fullsync 1251 # PRAGMA fullfsync 1252 # PRAGMA synchronous 1253 # 1254 foreach {tn settings restart_sync commit_sync ckpt_sync} { 1255 1 {0 0 off} {0 0} {0 0} {0 0} 1256 2 {0 0 normal} {1 0} {0 0} {2 0} 1257 3 {0 0 full} {2 0} {1 0} {2 0} 1258 1259 4 {0 1 off} {0 0} {0 0} {0 0} 1260 5 {0 1 normal} {0 1} {0 0} {0 2} 1261 6 {0 1 full} {0 2} {0 1} {0 2} 1262 1263 7 {1 0 off} {0 0} {0 0} {0 0} 1264 8 {1 0 normal} {0 1} {0 0} {0 2} 1265 9 {1 0 full} {1 1} {1 0} {0 2} 1266 1267 10 {1 1 off} {0 0} {0 0} {0 0} 1268 11 {1 1 normal} {0 1} {0 0} {0 2} 1269 12 {1 1 full} {0 2} {0 1} {0 2} 1270 } { 1271 forcedelete test.db 1272 1273 testvfs tvfs -default 1 1274 tvfs filter xSync 1275 tvfs script xSyncCb 1276 proc xSyncCb {method file fileid flags} { 1277 incr ::sync($flags) 1278 } 1279 1280 sqlite3 db test.db 1281 do_execsql_test 15.$tn.1 " 1282 PRAGMA page_size = 4096; 1283 CREATE TABLE t1(x); 1284 PRAGMA wal_autocheckpoint = OFF; 1285 PRAGMA journal_mode = WAL; 1286 PRAGMA checkpoint_fullfsync = [lindex $settings 0]; 1287 PRAGMA fullfsync = [lindex $settings 1]; 1288 PRAGMA synchronous = [lindex $settings 2]; 1289 " {0 wal} 1290 1291 do_test 15.$tn.2 { 1292 set sync(normal) 0 1293 set sync(full) 0 1294 execsql { INSERT INTO t1 VALUES('abc') } 1295 list $::sync(normal) $::sync(full) 1296 } $restart_sync 1297 1298 do_test 15.$tn.3 { 1299 set sync(normal) 0 1300 set sync(full) 0 1301 execsql { INSERT INTO t1 VALUES('abc') } 1302 list $::sync(normal) $::sync(full) 1303 } $commit_sync 1304 1305 do_test 15.$tn.4 { 1306 set sync(normal) 0 1307 set sync(full) 0 1308 execsql { INSERT INTO t1 VALUES('def') } 1309 list $::sync(normal) $::sync(full) 1310 } $commit_sync 1311 1312 do_test 15.$tn.5 { 1313 set sync(normal) 0 1314 set sync(full) 0 1315 execsql { PRAGMA wal_checkpoint } 1316 list $::sync(normal) $::sync(full) 1317 } $ckpt_sync 1318 1319 db close 1320 tvfs delete 1321 } 1322 1323 1324 1325 finish_test