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