xref: /sqlite-3.40.0/test/wal3.test (revision f43d7fce)
1# 2010 April 13
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/wal_common.tcl
20source $testdir/malloc_common.tcl
21ifcapable !wal {finish_test ; return }
22
23set a_string_counter 1
24proc a_string {n} {
25  global a_string_counter
26  incr a_string_counter
27  string range [string repeat "${a_string_counter}." $n] 1 $n
28}
29db func a_string a_string
30
31#-------------------------------------------------------------------------
32# When a rollback or savepoint rollback occurs, the client may remove
33# elements from one of the hash tables in the wal-index. This block
34# of test cases tests that nothing appears to go wrong when this is
35# done.
36#
37do_test wal3-1.0 {
38  execsql {
39    PRAGMA cache_size = 2000;
40    PRAGMA page_size = 1024;
41    PRAGMA auto_vacuum = off;
42    PRAGMA synchronous = normal;
43    PRAGMA journal_mode = WAL;
44    PRAGMA wal_autocheckpoint = 0;
45    BEGIN;
46      CREATE TABLE t1(x);
47      INSERT INTO t1 VALUES( a_string(800) );                  /*    1 */
48      INSERT INTO t1 SELECT a_string(800) FROM t1;             /*    2 */
49      INSERT INTO t1 SELECT a_string(800) FROM t1;             /*    4 */
50      INSERT INTO t1 SELECT a_string(800) FROM t1;             /*    8 */
51      INSERT INTO t1 SELECT a_string(800) FROM t1;             /*   16 */
52      INSERT INTO t1 SELECT a_string(800) FROM t1;             /*   32 */
53      INSERT INTO t1 SELECT a_string(800) FROM t1;             /*   64 */
54      INSERT INTO t1 SELECT a_string(800) FROM t1;             /*  128*/
55      INSERT INTO t1 SELECT a_string(800) FROM t1;             /*  256 */
56      INSERT INTO t1 SELECT a_string(800) FROM t1;             /*  512 */
57      INSERT INTO t1 SELECT a_string(800) FROM t1;             /* 1024 */
58      INSERT INTO t1 SELECT a_string(800) FROM t1;             /* 2048 */
59      INSERT INTO t1 SELECT a_string(800) FROM t1 LIMIT 1970;  /* 4018 */
60    COMMIT;
61    PRAGMA cache_size = 10;
62  }
63  wal_frame_count test.db-wal 1024
64} 4056
65
66for {set i 1} {$i < 50} {incr i} {
67
68  do_test wal3-1.$i.1 {
69    set str [a_string 800]
70    execsql { UPDATE t1 SET x = $str WHERE rowid = $i }
71    lappend L [wal_frame_count test.db-wal 1024]
72    execsql {
73      BEGIN;
74        INSERT INTO t1 SELECT a_string(800) FROM t1 LIMIT 100;
75      ROLLBACK;
76      PRAGMA integrity_check;
77    }
78  } {ok}
79
80  # Check that everything looks OK from the point of view of an
81  # external connection.
82  #
83  sqlite3 db2 test.db
84  do_test wal3-1.$i.2 {
85    execsql { SELECT count(*) FROM t1 } db2
86  } 4018
87  do_test wal3-1.$i.3 {
88    execsql { SELECT x FROM t1 WHERE rowid = $i }
89  } $str
90  do_test wal3-1.$i.4 {
91    execsql { PRAGMA integrity_check } db2
92  } {ok}
93  db2 close
94
95  # Check that the file-system in its current state can be recovered.
96  #
97  file copy -force test.db test2.db
98  file copy -force test.db-wal test2.db-wal
99  file delete -force test2.db-journal
100  sqlite3 db2 test2.db
101  do_test wal3-1.$i.5 {
102    execsql { SELECT count(*) FROM t1 } db2
103  } 4018
104  do_test wal3-1.$i.6 {
105    execsql { SELECT x FROM t1 WHERE rowid = $i }
106  } $str
107  do_test wal3-1.$i.7 {
108    execsql { PRAGMA integrity_check } db2
109  } {ok}
110  db2 close
111}
112
113do_multiclient_test i {
114
115  set testname(1) multiproc
116  set testname(2) singleproc
117  set tn $testname($i)
118
119  do_test wal3-2.$tn.1 {
120    sql1 {
121      PRAGMA page_size = 1024;
122      PRAGMA journal_mode = WAL;
123    }
124    sql1 {
125      CREATE TABLE t1(a, b);
126      INSERT INTO t1 VALUES(1, 'one');
127      BEGIN;
128        SELECT * FROM t1;
129    }
130  } {1 one}
131  do_test wal3-2.$tn.2 {
132    sql2 {
133      CREATE TABLE t2(a, b);
134      INSERT INTO t2 VALUES(2, 'two');
135      BEGIN;
136        SELECT * FROM t2;
137    }
138  } {2 two}
139  do_test wal3-2.$tn.3 {
140    sql3 {
141      CREATE TABLE t3(a, b);
142      INSERT INTO t3 VALUES(3, 'three');
143      BEGIN;
144        SELECT * FROM t3;
145    }
146  } {3 three}
147
148  # Try to checkpoint the database using [db]. It should be possible to
149  # checkpoint everything except the table added by [db3] (checkpointing
150  # these frames would clobber the snapshot currently being used by [db2]).
151  #
152  # After [db2] has committed, a checkpoint can copy the entire log to the
153  # database file. Checkpointing after [db3] has committed is therefore a
154  # no-op, as the entire log has already been backfilled.
155  #
156  do_test wal3-2.$tn.4 {
157    sql1 {
158      COMMIT;
159      PRAGMA wal_checkpoint;
160    }
161    file size test.db
162  } [expr $AUTOVACUUM ? 4*1024 : 3*1024]
163  do_test wal3-2.$tn.5 {
164    sql2 {
165      COMMIT;
166      PRAGMA wal_checkpoint;
167    }
168    file size test.db
169  } [expr $AUTOVACUUM ? 5*1024 : 4*1024]
170  do_test wal3-2.$tn.6 {
171    sql3 {
172      COMMIT;
173      PRAGMA wal_checkpoint;
174    }
175    file size test.db
176  } [expr $AUTOVACUUM ? 5*1024 : 4*1024]
177}
178catch {db close}
179
180#-------------------------------------------------------------------------
181# Test that that for the simple test:
182#
183#   CREATE TABLE x(y);
184#   INSERT INTO x VALUES('z');
185#   PRAGMA wal_checkpoint;
186#
187# in WAL mode the xSync method is invoked as expected for each of
188# synchronous=off, synchronous=normal and synchronous=full.
189#
190foreach {tn syncmode synccount} {
191  1 off
192    {}
193  2 normal
194    {test.db-wal normal test.db normal}
195  3 full
196    {test.db-wal normal test.db-wal normal test.db-wal normal test.db normal}
197} {
198
199  proc sync_counter {args} {
200    foreach {method filename id flags} $args break
201    lappend ::syncs [file tail $filename] $flags
202  }
203  do_test wal3-3.$tn {
204    file delete -force test.db test.db-wal test.db-journal
205
206    testvfs T
207    T filter {}
208    T script sync_counter
209    sqlite3 db test.db -vfs T
210
211    execsql "PRAGMA synchronous = $syncmode"
212    execsql { PRAGMA journal_mode = WAL }
213
214    set ::syncs [list]
215    T filter xSync
216    execsql {
217      CREATE TABLE x(y);
218      INSERT INTO x VALUES('z');
219      PRAGMA wal_checkpoint;
220    }
221    T filter {}
222    set ::syncs
223  } $synccount
224
225  db close
226  T delete
227}
228
229#-------------------------------------------------------------------------
230# When recovering the contents of a WAL file, a process obtains the WRITER
231# lock, then locks all other bytes before commencing recovery. If it fails
232# to lock all other bytes (because some other process is holding a read
233# lock) it should retry up to 100 times. Then return SQLITE_PROTOCOL to the
234# caller. Test this (test case wal3-4.3).
235#
236# Also test the effect of hitting an SQLITE_BUSY while attempting to obtain
237# the WRITER lock (should be the same). Test case wal3-4.4.
238#
239proc lock_callback {method filename handle lock} {
240  lappend ::locks $lock
241}
242do_test wal3-4.1 {
243  testvfs T
244  T filter xShmLock
245  T script lock_callback
246  set ::locks [list]
247  sqlite3 db test.db -vfs T
248  execsql { SELECT * FROM x }
249  lrange $::locks 0 3
250} [list {0 1 lock exclusive} {1 7 lock exclusive}      \
251        {1 7 unlock exclusive} {0 1 unlock exclusive}  \
252]
253do_test wal3-4.2 {
254  db close
255  set ::locks [list]
256  sqlite3 db test.db -vfs T
257  execsql { SELECT * FROM x }
258  lrange $::locks 0 3
259} [list {0 1 lock exclusive} {1 7 lock exclusive}      \
260        {1 7 unlock exclusive} {0 1 unlock exclusive}  \
261]
262proc lock_callback {method filename handle lock} {
263  if {$lock == "1 7 lock exclusive"} { return SQLITE_BUSY }
264  return SQLITE_OK
265}
266puts "  Warning: This next test case causes SQLite to call xSleep(1) 100 times."
267puts "  Normally this equates to a 100ms delay, but if SQLite is built on unix"
268puts "  without HAVE_USLEEP defined, it may be 100 seconds."
269do_test wal3-4.3 {
270  db close
271  set ::locks [list]
272  sqlite3 db test.db -vfs T
273  catchsql { SELECT * FROM x }
274} {1 {locking protocol}}
275
276puts "  Warning: Same again!"
277proc lock_callback {method filename handle lock} {
278  if {$lock == "0 1 lock exclusive"} { return SQLITE_BUSY }
279  return SQLITE_OK
280}
281do_test wal3-4.4 {
282  db close
283  set ::locks [list]
284  sqlite3 db test.db -vfs T
285  catchsql { SELECT * FROM x }
286} {1 {locking protocol}}
287db close
288T delete
289
290
291#-------------------------------------------------------------------------
292# Only one client may run recovery at a time. Test this mechanism.
293#
294# When client-2 tries to open a read transaction while client-1 is
295# running recovery, it fails to obtain a lock on an aReadMark[] slot
296# (because they are all locked by recovery). It then tries to obtain
297# a shared lock on the RECOVER lock to see if there really is a
298# recovery running or not.
299#
300# This block of tests checks the effect of an SQLITE_BUSY or SQLITE_IOERR
301# being returned when client-2 attempts a shared lock on the RECOVER byte.
302#
303# An SQLITE_BUSY should be converted to an SQLITE_BUSY_RECOVERY. An
304# SQLITE_IOERR should be returned to the caller.
305#
306do_test wal3-5.1 {
307  faultsim_delete_and_reopen
308  execsql {
309    PRAGMA journal_mode = WAL;
310    CREATE TABLE t1(a, b);
311    INSERT INTO t1 VALUES(1, 2);
312    INSERT INTO t1 VALUES(3, 4);
313  }
314  faultsim_save_and_close
315} {}
316
317testvfs T -default 1
318T script method_callback
319
320proc method_callback {method args} {
321  if {$method == "xShmBarrier"} {
322    incr ::barrier_count
323    if {$::barrier_count == 2} {
324      # This code is executed within the xShmBarrier() callback invoked
325      # by the client running recovery as part of writing the recovered
326      # wal-index header. If a second client attempts to access the
327      # database now, it reads a corrupt (partially written) wal-index
328      # header. But it cannot even get that far, as the first client
329      # is still holding all the locks (recovery takes an exclusive lock
330      # on *all* db locks, preventing access by any other client).
331      #
332      # If global variable ::wal3_do_lockfailure is non-zero, then set
333      # things up so that an IO error occurs within an xShmLock() callback
334      # made by the second client (aka [db2]).
335      #
336      sqlite3 db2 test.db
337      if { $::wal3_do_lockfailure } { T filter xShmLock }
338      set ::testrc [ catch { db2 eval "SELECT * FROM t1" } ::testmsg ]
339      T filter {}
340      db2 close
341    }
342  }
343
344  if {$method == "xShmLock"} {
345    foreach {file handle spec} $args break
346    if { $spec == "2 1 lock shared" } {
347      return SQLITE_IOERR
348    }
349  }
350
351  return SQLITE_OK
352}
353
354# Test a normal SQLITE_BUSY return.
355#
356T filter xShmBarrier
357set testrc ""
358set testmsg ""
359set barrier_count 0
360set wal3_do_lockfailure 0
361do_test wal3-5.2 {
362  faultsim_restore_and_reopen
363  execsql { SELECT * FROM t1 }
364} {1 2 3 4}
365do_test wal3-5.3 {
366  list $::testrc $::testmsg
367} {1 {database is locked}}
368db close
369
370# Test an SQLITE_IOERR return.
371#
372T filter xShmBarrier
373set barrier_count 0
374set wal3_do_lockfailure 1
375set testrc ""
376set testmsg ""
377do_test wal3-5.4 {
378  faultsim_restore_and_reopen
379  execsql { SELECT * FROM t1 }
380} {1 2 3 4}
381do_test wal3-5.5 {
382  list $::testrc $::testmsg
383} {1 {disk I/O error}}
384
385db close
386T delete
387
388#-------------------------------------------------------------------------
389# When opening a read-transaction on a database, if the entire log has
390# already been copied to the database file, the reader grabs a special
391# kind of read lock (on aReadMark[0]). This set of test cases tests the
392# outcome of the following:
393#
394#   + The reader discovering that between the time when it determined
395#     that the log had been completely backfilled and the lock is obtained
396#     that a writer has written to the log. In this case the reader should
397#     acquire a different read-lock (not aReadMark[0]) and read the new
398#     snapshot.
399#
400#   + The attempt to obtain the lock on aReadMark[0] fails with SQLITE_BUSY.
401#     This can happen if a checkpoint is ongoing. In this case also simply
402#     obtain a different read-lock.
403#
404catch {db close}
405testvfs T -default 1
406do_test wal3-6.1.1 {
407  file delete -force test.db test.db-journal test.db wal
408  sqlite3 db test.db
409  execsql { PRAGMA journal_mode = WAL }
410  execsql {
411    CREATE TABLE t1(a, b);
412    INSERT INTO t1 VALUES('o', 't');
413    INSERT INTO t1 VALUES('t', 'f');
414  }
415} {}
416do_test wal3-6.1.2 {
417  sqlite3 db2 test.db
418  sqlite3 db3 test.db
419  execsql { BEGIN ; SELECT * FROM t1 } db3
420} {o t t f}
421do_test wal3-6.1.3 {
422  execsql { PRAGMA wal_checkpoint } db2
423} {}
424
425# At this point the log file has been fully checkpointed. However,
426# connection [db3] holds a lock that prevents the log from being wrapped.
427# Test case 3.6.1.4 has [db] attempt a read-lock on aReadMark[0]. But
428# as it is obtaining the lock, [db2] appends to the log file.
429#
430T filter xShmLock
431T script lock_callback
432proc lock_callback {method file handle spec} {
433  if {$spec == "3 1 lock shared"} {
434    # This is the callback for [db] to obtain the read lock on aReadMark[0].
435    # Disable future callbacks using [T filter {}] and write to the log
436    # file using [db2]. [db3] is preventing [db2] from wrapping the log
437    # here, so this is an append.
438    T filter {}
439    db2 eval { INSERT INTO t1 VALUES('f', 's') }
440  }
441  return SQLITE_OK
442}
443do_test wal3-6.1.4 {
444  execsql {
445    BEGIN;
446    SELECT * FROM t1;
447  }
448} {o t t f f s}
449
450# [db] should be left holding a read-lock on some slot other than
451# aReadMark[0]. Test this by demonstrating that the read-lock is preventing
452# the log from being wrapped.
453#
454do_test wal3-6.1.5 {
455  db3 eval COMMIT
456  db2 eval { PRAGMA wal_checkpoint }
457  set sz1 [file size test.db-wal]
458  db2 eval { INSERT INTO t1 VALUES('s', 'e') }
459  set sz2 [file size test.db-wal]
460  expr {$sz2>$sz1}
461} {1}
462
463# Test that if [db2] had not interfered when [db] was trying to grab
464# aReadMark[0], it would have been possible to wrap the log in 3.6.1.5.
465#
466do_test wal3-6.1.6 {
467  execsql { COMMIT }
468  execsql { PRAGMA wal_checkpoint } db2
469  execsql {
470    BEGIN;
471    SELECT * FROM t1;
472  }
473} {o t t f f s s e}
474do_test wal3-6.1.7 {
475  db2 eval { PRAGMA wal_checkpoint }
476  set sz1 [file size test.db-wal]
477  db2 eval { INSERT INTO t1 VALUES('n', 't') }
478  set sz2 [file size test.db-wal]
479  expr {$sz2==$sz1}
480} {1}
481
482db3 close
483db2 close
484db close
485
486do_test wal3-6.2.1 {
487  file delete -force test.db test.db-journal test.db wal
488  sqlite3 db test.db
489  sqlite3 db2 test.db
490  execsql { PRAGMA journal_mode = WAL }
491  execsql {
492    CREATE TABLE t1(a, b);
493    INSERT INTO t1 VALUES('h', 'h');
494    INSERT INTO t1 VALUES('l', 'b');
495  }
496} {}
497
498T filter xShmLock
499T script lock_callback
500proc lock_callback {method file handle spec} {
501  if {$spec == "3 1 unlock exclusive"} {
502    T filter {}
503    set ::R [db2 eval {
504      BEGIN;
505      SELECT * FROM t1;
506    }]
507  }
508}
509do_test wal3-6.2.2 {
510  execsql { PRAGMA wal_checkpoint }
511} {}
512do_test wal3-6.2.3 {
513  set ::R
514} {h h l b}
515do_test wal3-6.2.4 {
516  set sz1 [file size test.db-wal]
517  execsql { INSERT INTO t1 VALUES('b', 'c'); }
518  set sz2 [file size test.db-wal]
519  expr {$sz2 > $sz1}
520} {1}
521do_test wal3-6.2.5 {
522  db2 eval { COMMIT }
523  execsql { PRAGMA wal_checkpoint }
524  set sz1 [file size test.db-wal]
525  execsql { INSERT INTO t1 VALUES('n', 'o'); }
526  set sz2 [file size test.db-wal]
527  expr {$sz2 == $sz1}
528} {1}
529
530db2 close
531db close
532T delete
533
534#-------------------------------------------------------------------------
535# When opening a read-transaction on a database, if the entire log has
536# not yet been copied to the database file, the reader grabs a read
537# lock on aReadMark[x], where x>0. The following test cases experiment
538# with the outcome of the following:
539#
540#   + The reader discovering that between the time when it read the
541#     wal-index header and the lock was obtained that a writer has
542#     written to the log. In this case the reader should re-read the
543#     wal-index header and lock a snapshot corresponding to the new
544#     header.
545#
546#   + The value in the aReadMark[x] slot has been modified since it was
547#     read.
548#
549catch {db close}
550testvfs T -default 1
551do_test wal3-7.1.1 {
552  file delete -force test.db test.db-journal test.db wal
553  sqlite3 db test.db
554  execsql {
555    PRAGMA journal_mode = WAL;
556    CREATE TABLE blue(red PRIMARY KEY, green);
557  }
558} {wal}
559
560T script method_callback
561T filter xOpen
562proc method_callback {method args} {
563  if {$method == "xOpen"} { return "reader" }
564}
565do_test wal3-7.1.2 {
566  sqlite3 db2 test.db
567  execsql { SELECT * FROM blue } db2
568} {}
569
570T filter xShmLock
571set ::locks [list]
572proc method_callback {method file handle spec} {
573  if {$handle != "reader" } { return }
574  if {$method == "xShmLock"} {
575    catch { execsql { INSERT INTO blue VALUES(1, 2) } }
576    catch { execsql { INSERT INTO blue VALUES(3, 4) } }
577  }
578  lappend ::locks $spec
579}
580do_test wal3-7.1.3 {
581  execsql { SELECT * FROM blue } db2
582} {1 2 3 4}
583do_test wal3-7.1.4 {
584  set ::locks
585} {{4 1 lock shared} {4 1 unlock shared} {5 1 lock shared} {5 1 unlock shared}}
586
587set ::locks [list]
588proc method_callback {method file handle spec} {
589  if {$handle != "reader" } { return }
590  if {$method == "xShmLock"} {
591    catch { execsql { INSERT INTO blue VALUES(5, 6) } }
592  }
593  lappend ::locks $spec
594}
595do_test wal3-7.2.1 {
596  execsql { SELECT * FROM blue } db2
597} {1 2 3 4 5 6}
598do_test wal3-7.2.2 {
599  set ::locks
600} {{5 1 lock shared} {5 1 unlock shared} {4 1 lock shared} {4 1 unlock shared}}
601
602db close
603db2 close
604T delete
605
606#-------------------------------------------------------------------------
607#
608do_test wal3-8.1 {
609  file delete -force test.db test.db-journal test.db wal
610  sqlite3 db test.db
611  sqlite3 db2 test.db
612  execsql {
613    PRAGMA journal_mode = WAL;
614    CREATE TABLE b(c);
615    INSERT INTO b VALUES('Tehran');
616    INSERT INTO b VALUES('Qom');
617    INSERT INTO b VALUES('Markazi');
618    PRAGMA wal_checkpoint;
619  }
620} {wal}
621do_test wal3-8.2 {
622  execsql { SELECT * FROM b }
623} {Tehran Qom Markazi}
624do_test wal3-8.3 {
625  db eval { SELECT * FROM b } {
626    db eval { INSERT INTO b VALUES('Qazvin') }
627    set r [db2 eval { SELECT * FROM b }]
628    break
629  }
630  set r
631} {Tehran Qom Markazi Qazvin}
632do_test wal3-8.4 {
633  execsql {
634    INSERT INTO b VALUES('Gilan');
635    INSERT INTO b VALUES('Ardabil');
636  }
637} {}
638db2 close
639
640faultsim_save_and_close
641testvfs T -default 1
642faultsim_restore_and_reopen
643T filter xShmLock
644T script lock_callback
645
646proc lock_callback {method file handle spec} {
647  if {$spec == "4 1 unlock exclusive"} {
648    T filter {}
649    set ::r [catchsql { SELECT * FROM b } db2]
650  }
651}
652sqlite3 db test.db
653sqlite3 db2 test.db
654do_test wal3-8.5 {
655  execsql { SELECT * FROM b }
656} {Tehran Qom Markazi Qazvin Gilan Ardabil}
657do_test wal3-8.6 {
658  set ::r
659} {1 {locking protocol}}
660
661db close
662db2 close
663
664faultsim_restore_and_reopen
665sqlite3 db2 test.db
666T filter xShmLock
667T script lock_callback
668proc lock_callback {method file handle spec} {
669  if {$spec == "1 7 unlock exclusive"} {
670    T filter {}
671    set ::r [catchsql { SELECT * FROM b } db2]
672  }
673}
674unset ::r
675do_test wal3-8.5 {
676  execsql { SELECT * FROM b }
677} {Tehran Qom Markazi Qazvin Gilan Ardabil}
678do_test wal3-8.6 {
679  set ::r
680} {1 {locking protocol}}
681
682db close
683db2 close
684T delete
685
686#-------------------------------------------------------------------------
687# When a connection opens a read-lock on the database, it searches for
688# an aReadMark[] slot that is already set to the mxFrame value for the
689# new transaction. If it cannot find one, it attempts to obtain an
690# exclusive lock on an aReadMark[] slot for the purposes of modifying
691# the value, then drops back to a shared-lock for the duration of the
692# transaction.
693#
694# This test case verifies that if an exclusive lock cannot be obtained
695# on any aReadMark[] slot (because there are already several readers),
696# the client takes a shared-lock on a slot without modifying the value
697# and continues.
698#
699do_test wal3-9.0 {
700  file delete -force test.db test.db-journal test.db wal
701  sqlite3 db test.db
702  execsql {
703    PRAGMA journal_mode = WAL;
704    CREATE TABLE whoami(x);
705    INSERT INTO whoami VALUES('nobody');
706  }
707} {wal}
708for {set i 0} {$i < 50} {incr i} {
709  set c db$i
710  do_test wal3-9.1.$i {
711    sqlite3 $c test.db
712    execsql { UPDATE whoami SET x = $c }
713    execsql {
714      BEGIN;
715      SELECT * FROM whoami
716    } $c
717  } $c
718}
719for {set i 0} {$i < 50} {incr i} {
720  set c db$i
721  do_test wal3-9.2.$i {
722    execsql { SELECT * FROM whoami } $c
723  } $c
724}
725do_test wal3-9.3 {
726  for {set i 0} {$i < 49} {incr i} { db$i close }
727  execsql { PRAGMA wal_checkpoint }
728  set sz1 [file size test.db]
729  db49 close
730  execsql { PRAGMA wal_checkpoint }
731  set sz2 [file size test.db]
732  expr {$sz2 > $sz1}
733} {1}
734
735db close
736
737finish_test
738
739