000001  # 2014 December 04
000002  #
000003  # The author disclaims copyright to this source code.  In place of
000004  # a legal notice, here is a blessing:
000005  #
000006  #    May you do good and not evil.
000007  #    May you find forgiveness for yourself and forgive others.
000008  #    May you share freely, never taking more than you give.
000009  #
000010  #***********************************************************************
000011  #
000012  
000013  set testdir [file dirname $argv0]
000014  source $testdir/tester.tcl
000015  source $testdir/lock_common.tcl
000016  source $testdir/wal_common.tcl
000017  set testprefix e_walckpt
000018  
000019  # The following two commands are used to determine if any of the files
000020  # "test.db", "test.db2" and "test.db3" are modified by a test case.
000021  #
000022  # The [save_db_hashes] command saves a hash of the current contents of
000023  # all three files in global variables. The [compare_db_hashes] compares
000024  # the current contents with the saved hashes and returns a list of the
000025  # files that have changed.
000026  #
000027  proc save_db_hashes {} {
000028    global H
000029    foreach f {test.db test.db2 test.db3} {
000030      set H($f) 0
000031      catch { set H($f) [md5file $f] }
000032    }
000033  }
000034  proc compare_db_hashes {} {
000035    global H
000036    set ret [list]
000037    foreach f {test.db test.db2 test.db3} {
000038      set expect 0
000039      catch { set expect [md5file $f] }
000040      if {$H($f) != $expect} { lappend ret $f }
000041    }
000042    set ret
000043  }
000044  
000045  #-------------------------------------------------------------------------
000046  # All calls to the [sqlite3_wal_checkpoint_v2] command made within this
000047  # file use this wrapper. It's sole purpose is to throw an error if the
000048  # following requirement is violated:
000049  #
000050  # EVIDENCE-OF: R-60567-47780 Unless it returns SQLITE_MISUSE, the
000051  # sqlite3_wal_checkpoint_v2() interface sets the error information that
000052  # is queried by sqlite3_errcode() and sqlite3_errmsg().
000053  #
000054  proc wal_checkpoint_v2 {db args} {
000055    set rc [catch {
000056      uplevel sqlite3_wal_checkpoint_v2 $db $args
000057    } msg]
000058  
000059    set errcode "SQLITE_OK"
000060    if {$rc} {
000061      set errcode [lindex [split $msg " "] 0]
000062    } elseif { [lindex $msg 0] } {
000063      set errcode "SQLITE_BUSY"
000064    }
000065  
000066    if {$errcode != "SQLITE_MISUSE" && [sqlite3_errcode $db] != $errcode} {
000067      error "sqlite3_errcode mismatch! (1) $errcode!=[sqlite3_errcode $db]"
000068    }
000069  
000070    if {$rc==0} {
000071      return $msg
000072    } else {
000073      error $msg
000074    }
000075  }
000076  
000077  
000078  # The following tests are run 3 times, each using a different method of 
000079  # invoking a checkpoint:
000080  #
000081  #   1) Using sqlite3_wal_checkpoint_v2()
000082  #   2) Using "PRAGMA wal_checkpoint"
000083  #   3) Using sqlite3_wal_checkpoint() in place of checkpoint_v2(PASSIVE)
000084  #
000085  # Cases (2) and (3) are to show that the following statements are 
000086  # correct, respectively:
000087  #
000088  # EVIDENCE-OF: R-36706-10507 The PRAGMA wal_checkpoint command can be
000089  # used to invoke this interface from SQL.
000090  #
000091  # EVIDENCE-OF: R-41613-20553 The sqlite3_wal_checkpoint(D,X) is
000092  # equivalent to
000093  # sqlite3_wal_checkpoint_v2(D,X,SQLITE_CHECKPOINT_PASSIVE,0,0).
000094  # 
000095  foreach {tn script} {
000096    1 {
000097      proc checkpoint {db mode args} {
000098        eval wal_checkpoint_v2 [list $db] [list $mode] $args
000099      }
000100    }
000101  
000102    2 {
000103      proc checkpoint {db mode args} {
000104        set sql "PRAGMA wal_checkpoint = $mode"
000105        if {[llength $args] && [lindex $args 0]!=""} {
000106          set sql "PRAGMA [lindex $args 0].wal_checkpoint = $mode"
000107        }
000108        set rc [catch { $db eval $sql } msg]
000109        if {$rc} {
000110          regsub {database} $msg {database:} msg
000111          error "[sqlite3_errcode $db] - $msg"
000112        }
000113        set msg
000114      }
000115    }
000116  
000117    3 {
000118      proc checkpoint {db mode args} {
000119        if {$mode == "passive"} {
000120          set rc [eval sqlite3_wal_checkpoint [list $db] $args]
000121          if {$rc != "SQLITE_OK"} {
000122            error "$rc - [sqlite3_errmsg $db]"
000123          }
000124        } else {
000125          eval wal_checkpoint_v2 [list $db] [list $mode] $args
000126        }
000127      }
000128    }
000129  
000130  } {
000131  
000132    eval $script
000133  
000134    reset_db
000135    forcedelete test.db2 test.db3 test.db4
000136    execsql {
000137      ATTACH 'test.db2' AS aux;
000138      ATTACH 'test.db3' AS aux2;
000139      ATTACH 'test.db4' AS aux3;
000140      CREATE TABLE t1(x);
000141      CREATE TABLE aux.t2(x);
000142      CREATE TABLE aux2.t3(x);
000143      CREATE TABLE aux3.t4(x);
000144      PRAGMA main.journal_mode = WAL;
000145      PRAGMA aux.journal_mode = WAL;
000146      PRAGMA aux2.journal_mode = WAL;
000147      /* Leave aux4 in rollback mode */
000148    }
000149  
000150    # EVIDENCE-OF: R-49787-09095 The sqlite3_wal_checkpoint_v2(D,X,M,L,C)
000151    # interface runs a checkpoint operation on database X of database
000152    # connection D in mode M. Status information is written back into
000153    # integers pointed to by L and C.
000154    #
000155    #     Tests 1, 2 and 3 below verify the "on database X" part of the
000156    #     above. Other parts of this requirement are tested below.
000157    #
000158    # EVIDENCE-OF: R-00653-06026 If parameter zDb is NULL or points to a
000159    # zero length string, then the specified operation is attempted on all
000160    # WAL databases attached to database connection db.
000161    #
000162    #     Tests 4 and 5 below test this.
000163    #
000164    foreach {tn2 zDb dblist} {
000165      1 main  test.db
000166      2 aux   test.db2
000167      3 aux2  test.db3
000168      4 ""    {test.db test.db2 test.db3}
000169      5 -     {test.db test.db2 test.db3}
000170      6 temp  {}
000171    } {
000172      do_test $tn.1.$tn2 {
000173        execsql {
000174          INSERT INTO t1 VALUES(1);
000175          INSERT INTO t2 VALUES(2);
000176          INSERT INTO t3 VALUES(3);
000177        }
000178        save_db_hashes
000179  
000180        if {$zDb == "-"} {
000181          checkpoint db passive
000182        } else {
000183          checkpoint db passive $zDb
000184        }
000185  
000186        compare_db_hashes
000187      } $dblist
000188    }
000189  
000190    # EVIDENCE-OF: R-38207-48996 If zDb is not NULL (or a zero length
000191    # string) and is not the name of any attached database, SQLITE_ERROR is
000192    # returned to the caller.
000193    do_test $tn.2.1 {
000194      list [catch { checkpoint db passive notadb } msg] $msg
000195    } {1 {SQLITE_ERROR - unknown database: notadb}}
000196  
000197    # EVIDENCE-OF: R-14303-42483 If database zDb is the name of an attached
000198    # database that is not in WAL mode, SQLITE_OK is returned and both
000199    # *pnLog and *pnCkpt set to -1.
000200    #
000201    if {$tn==3} {
000202      # With sqlite3_wal_checkpoint() the two output variables cannot be 
000203      # tested. So just test that no error is returned when attempting to
000204      # checkpoint a db in rollback mode.
000205      do_test $tn.2.2.a { checkpoint db passive aux3 } {}
000206    } else {
000207      do_test $tn.2.2.b { checkpoint db passive aux3 } {0 -1 -1}
000208    }
000209  
000210    # EVIDENCE-OF: R-62028-47212 All calls obtain an exclusive "checkpoint"
000211    # lock on the database file.
000212    db close
000213    testvfs tvfs
000214    tvfs filter xShmLock
000215    tvfs script filelock
000216    proc filelock {method file handle details} {
000217      # Test for an exclusive checkpoint lock. A checkpoint lock locks a
000218      # single byte starting at offset 1.
000219      if {$details == "1 1 lock exclusive"} { set ::seen_checkpoint_lock 1 }
000220    }
000221    sqlite3 db test.db -vfs tvfs
000222    do_test $tn.3.1 {
000223      execsql { INSERT INTO t1 VALUES('xyz') }
000224      unset -nocomplain ::seen_checkpoint_lock
000225      checkpoint db passive
000226      set ::seen_checkpoint_lock
000227    } {1}
000228    db close
000229    tvfs delete
000230    reset_db
000231  
000232  
000233   
000234  
000235    #-----------------------------------------------------------------------
000236    # EVIDENCE-OF: R-10421-19736 If any other process is running a
000237    # checkpoint operation at the same time, the lock cannot be obtained and
000238    # SQLITE_BUSY is returned.
000239    #
000240    # EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured,
000241    # it will not be invoked in this case.
000242    #
000243    testvfs tvfs
000244    tvfs filter xWrite
000245    sqlite3 db test.db -vfs tvfs
000246    sqlite3 db2 test.db -vfs tvfs
000247  
000248    do_test $tn.3.2.1 {
000249      db2 eval {
000250        PRAGMA auto_vacuum = 0;
000251        PRAGMA journal_mode = WAL;
000252        CREATE TABLE t1(x, y);
000253        INSERT INTO t1 VALUES(1,2);
000254        INSERT INTO t1 VALUES(3,4);
000255        INSERT INTO t1 VALUES(5,6);
000256      }
000257      file size test.db-wal
000258    } [wal_file_size 5 1024]
000259  
000260  
000261    # Connection [db] runs a checkpoint. During this checkpoint, each
000262    # time it calls xWrite() to write a page into the database file, we
000263    # attempt to start a checkpoint using [db2]. According to the 
000264    # first requirement being tested, this should return SQLITE_BUSY. According
000265    # to the second, the busy-handler belonging to [db2] should not be
000266    # invoked.
000267    #
000268    set ::write_count 0
000269    set ::write_errors [list]
000270    proc busy_callback {args} {
000271      lappend ::write_errors "busy handler called!"
000272    }
000273    proc write_callback {args} {
000274      set rc [catch {checkpoint db2 passive} msg]
000275      if {0==[regexp "database is locked" $msg] && $msg!="1 -1 -1"} {
000276        lappend ::write_errors "$rc $msg"
000277      } 
000278      incr ::write_count
000279    }
000280    db2 busy busy_callback
000281    tvfs script write_callback
000282  
000283    do_test $tn.3.2.2 {
000284      db eval {SELECT * FROM sqlite_master}
000285      checkpoint db full
000286      set ::write_count
000287    } {2}
000288  
000289    do_test $tn.3.2.3 {
000290      set ::write_errors
000291    } {}
000292  
000293    db close
000294    db2 close
000295    tvfs delete
000296  
000297    proc busy_handler {mode busy_handler_mode n} {
000298      incr ::busy_handler_counter
000299      switch -- $busy_handler_mode {
000300        1 {
000301          # Do nothing. Do not block.
000302          return 1
000303        }
000304  
000305        2 {
000306          # Close first the reader, then later the writer. Give up before
000307          # closing the [db6] reader.
000308          if {$n==5}  { catch {db2 eval commit} }
000309          if {$n==10} { catch {db3 eval commit} }
000310          if {$n==15} { return 1 }
000311          return 0
000312        }
000313  
000314        3 {
000315          # Close first the writer, then later the reader. And finally the 
000316          # [db6] reader.
000317          if {$n==5}  { catch {db2 eval commit} }
000318          if {$n==10} { catch {db3 eval commit} }
000319          if {$n==15} { catch {db6 eval commit} }
000320          return 0
000321        }
000322      }
000323    }
000324  
000325    foreach {mode busy_handler_mode} { 
000326      passive  1
000327      full     1       full     2       full    3
000328      restart  1       restart  2       restart  3
000329      truncate 1       truncate 2       truncate 3
000330    } {
000331      set tp "$tn.$mode.$busy_handler_mode"
000332  
000333      set ::sync_counter 0
000334  
000335      # Set up a callback function for xSync and xWrite calls made during
000336      # the checkpoint.
000337      #
000338      set ::checkpoint_ongoing 0
000339      proc tvfs_callback {method args} {
000340        if {$::checkpoint_ongoing==0} return
000341  
000342        set tail [file tail [lindex $args 0]]
000343        if {$method == "xSync" && $tail == "test.db"} {
000344          incr ::sync_counter
000345        }
000346        if {$method == "xWrite" && $tail=="test.db"} {
000347          if {$::write_ok < 0} {
000348            set ::write_ok [expr ![catch {db5 eval { BEGIN IMMEDIATE }}]]
000349            catch { db5 eval ROLLBACK }
000350          }
000351          if {$::read_ok < 0} {
000352            set ::read_ok [expr ![catch {db5 eval { SELECT * FROM t1 }}]]
000353          }
000354  
000355          # If one has not already been opened, open a read-transaction using
000356          # connection [db6]
000357          catch { db6 eval { BEGIN ; SELECT * FROM sqlite_master } } msg
000358        }
000359        if {$method == "xShmLock" } {
000360          set details [lindex $args 2]
000361          if {$details == "0 1 lock exclusive"} { set ::seen_writer_lock 1 }
000362        }
000363      }
000364  
000365      catch { db close }
000366      forcedelete test.db
000367      testvfs tvfs
000368      sqlite3 db test.db -vfs tvfs
000369      #tvfs filter xSync
000370      tvfs script tvfs_callback
000371  
000372      do_execsql_test $tp.0 {
000373        CREATE TABLE t1(a, b);
000374        CREATE TABLE t2(a, b);
000375        PRAGMA journal_mode = wal;
000376        INSERT INTO t1 VALUES(1, 2);
000377        INSERT INTO t1 VALUES(3, 4);
000378        INSERT INTO t1 VALUES(5, 6);
000379      } {wal}
000380  
000381      # Open a reader on the current database snapshot.
000382      do_test $tp.1 {
000383        sqlite3 db2 test.db -vfs tvfs
000384        execsql {
000385          BEGIN;
000386            SELECT * FROM t1 UNION ALL SELECT * FROM t2;
000387        } db2
000388      } {1 2 3 4 5 6}
000389  
000390      # Open a writer. Write a transaction. Then begin, but do not commit,
000391      # a second transaction.
000392      do_test $tp.2 {
000393        sqlite3 db3 test.db -vfs tvfs
000394        execsql {
000395          INSERT INTO t2 VALUES(7, 8);
000396          BEGIN;
000397            INSERT INTO t2 VALUES(9, 10);
000398            SELECT * FROM t1 UNION ALL SELECT * FROM t2;
000399        } db3
000400      } {1 2 3 4 5 6 7 8 9 10}
000401  
000402      sqlite3 db5 test.db -vfs tvfs
000403      sqlite3 db6 test.db -vfs tvfs
000404  
000405      # Register a busy-handler with connection [db].
000406      #
000407      db busy [list busy_handler $mode $busy_handler_mode]
000408      set ::sync_counter 0
000409      set ::busy_handler_counter 0
000410      set ::read_ok -1
000411      set ::write_ok -1
000412      set ::seen_writer_lock 0
000413      
000414      set ::checkpoint_ongoing 1
000415      do_test $tp.3 {
000416        checkpoint db $mode main
000417        set {} {}
000418      } {}
000419      set ::checkpoint_ongoing 0
000420      set ::did_restart_blocking [expr {[catch {db6 eval commit}]}]
000421  
000422      if { $mode=="passive" } {
000423        # EVIDENCE-OF: R-16333-64433 Checkpoint as many frames as possible
000424        # without waiting for any database readers or writers to finish, then
000425        # sync the database file if all frames in the log were checkpointed.
000426        #
000427        #   "As many frames as possible" means all but the last two transactions
000428        #   (the two that write to table t2, of which the scond is unfinished).
000429        #   So copying the db file only we see the t1 change, but not the t2
000430        #   modifications.
000431        #
000432        #   The busy handler is not invoked (see below) and the db reader and
000433        #   writer are still active - so the checkpointer did not wait for either
000434        #   readers or writers. As a result the checkpoint was not finished and
000435        #   so the db file is not synced.
000436        #
000437        # EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked
000438        # in the SQLITE_CHECKPOINT_PASSIVE mode.
000439        #
000440        #   It's not. Test case "$tp.6".
000441        #
000442        do_test $tp.4 {
000443          forcecopy test.db abc.db
000444          sqlite3 db4 abc.db
000445          db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 }
000446        } {1 2 3 4 5 6}
000447        do_test $tp.5 { set ::sync_counter } 0
000448        do_test $tp.6 { set ::busy_handler_counter } 0
000449        db4 close
000450    
000451        db2 eval COMMIT
000452        db3 eval COMMIT
000453    
000454        # EVIDENCE-OF: R-65499-53765 On the other hand, passive mode might leave
000455        # the checkpoint unfinished if there are concurrent readers or writers.
000456        #
000457        #   The reader and writer have now dropped their locks. And so a 
000458        #   checkpoint now is able to checkpoint more frames. Showing that the
000459        #   attempt above was left "unfinished".
000460        #
000461        #   Also, because the checkpoint finishes this time, the db is synced.
000462        #   Which is part of R-16333-64433 above.
000463        #
000464        set ::checkpoint_ongoing 1
000465        do_test $tp.7 {
000466          checkpoint db $mode main
000467          forcecopy test.db abc.db
000468          sqlite3 db4 abc.db
000469          db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 }
000470        } {1 2 3 4 5 6 7 8 9 10}
000471        set ::checkpoint_ongoing 0
000472        do_test $tp.7 { set ::sync_counter } 1
000473        do_test $tp.8 { set ::busy_handler_counter } 0
000474        db4 close
000475      }
000476  
000477      if { $mode=="full" || $mode=="restart" || $mode=="truncate" } {
000478  
000479        # EVIDENCE-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and
000480        # TRUNCATE modes also obtain the exclusive "writer" lock on the 
000481        # database file.
000482        #
000483        #   Or at least attempts to obtain.
000484        #
000485        do_test $tp.9 {
000486          set ::seen_writer_lock
000487        } {1}
000488  
000489        if {$busy_handler_mode==2 || $busy_handler_mode==3} {
000490          # EVIDENCE-OF: R-59171-47567 This mode blocks (it invokes the
000491          # busy-handler callback) until there is no database writer and all
000492          # readers are reading from the most recent database snapshot.
000493          #
000494          #   The test below shows that both the reader and writer have 
000495          #   finished:
000496          #
000497          #   Also restated by the following two. That both busy_handler_mode
000498          #   values 2 and 3 work show that both of the following are true - as
000499          #   they release the reader and writer transactions in different
000500          #   orders.
000501          #
000502          # EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained
000503          # immediately, and a busy-handler is configured, it is invoked and the
000504          # writer lock retried until either the busy-handler returns 0 or the
000505          # lock is successfully obtained.
000506          #
000507          # EVIDENCE-OF: R-48107-00250 The busy-handler is also invoked while
000508          # waiting for database readers as described above.
000509          #
000510          do_test $tp.7 {
000511            list [catchsql COMMIT db2] [catchsql COMMIT db3]
000512          } [list                                             \
000513              {1 {cannot commit - no transaction is active}}  \
000514              {1 {cannot commit - no transaction is active}}  \
000515          ]
000516  
000517          # EVIDENCE-OF: R-29177-48281 It then checkpoints all frames in the log
000518          # file and syncs the database file.
000519          #
000520          do_test $tp.8 {
000521            forcecopy test.db abc.db
000522            sqlite3 db4 abc.db
000523            db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 }
000524          } {1 2 3 4 5 6 7 8 9 10}
000525          do_test $tp.9 { set ::sync_counter } 1
000526          db4 close
000527  
000528          # EVIDENCE-OF: R-51867-44713 This mode blocks new database writers
000529          # while it is pending, but new database readers are allowed to continue
000530          # unimpeded.
000531          #
000532          # EVIDENCE-OF: R-47276-58266 Like SQLITE_CHECKPOINT_FULL, this mode
000533          # blocks new database writer attempts while it is pending, but does not
000534          # impede readers.
000535          #
000536          #   The first of the above two refers to "full" mode. The second
000537          #   to "restart".
000538          #
000539          do_test $tp.10.1 {
000540            list $::write_ok $::read_ok
000541          } {0 1}
000542  
000543          # EVIDENCE-OF: R-12410-31217 This mode works the same way as
000544          # SQLITE_CHECKPOINT_FULL with the addition that after checkpointing the
000545          # log file it blocks (calls the busy-handler callback) until all
000546          # readers are reading from the database file only.
000547          #
000548          #     The stuff above passed, so the first part of this requirement
000549          #     is met. The second part is tested below. If the checkpoint mode
000550          #     was "restart" or "truncate", then the busy-handler will have
000551          #     been called to block on wal-file readers.
000552          #
000553          do_test $tp.11 {
000554            set ::did_restart_blocking
000555          } [expr {($mode=="restart"||$mode=="truncate")&&$busy_handler_mode==3}]
000556  
000557          # EVIDENCE-OF: R-44699-57140 This mode works the same way as
000558          # SQLITE_CHECKPOINT_RESTART with the addition that it also truncates
000559          # the log file to zero bytes just prior to a successful return.
000560          if {$mode=="truncate" && $busy_handler_mode==3} {
000561            do_test $tp.12 {
000562              file size test.db-wal
000563            } 0
000564          }
000565        } elseif {$busy_handler_mode==1} {
000566  
000567          # EVIDENCE-OF: R-34519-06271 SQLITE_BUSY is returned in this case.
000568          if {$tn!=2} {
000569            # ($tn==2) is the loop that uses "PRAGMA wal_checkpoint"
000570            do_test $tp.13 { sqlite3_errcode db } {SQLITE_BUSY}
000571          }
000572  
000573          # EVIDENCE-OF: R-49155-63541 If the busy-handler returns 0 before the
000574          # writer lock is obtained or while waiting for database readers, the
000575          # checkpoint operation proceeds from that point in the same way as
000576          # SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible
000577          # without blocking any further.
000578          do_test $tp.14 {
000579            forcecopy test.db abc.db
000580              sqlite3 db4 abc.db
000581              db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 }
000582          } {1 2 3 4 5 6}
000583          do_test $tp.15 { set ::sync_counter } 0
000584          do_test $tp.16 { set ::busy_handler_counter } 1
000585          db4 close
000586        }
000587      }
000588  
000589      db2 close
000590      db3 close
000591      db5 close
000592      db6 close
000593    }
000594  
000595    db close
000596    tvfs delete
000597  }
000598  
000599  #-----------------------------------------------------------------------
000600  # EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint
000601  # mode:
000602  #
000603  #   Valid checkpoint modes are 0, 1, 2 and 3.
000604  #
000605  sqlite3 db test.db
000606  foreach {tn mode res} {
000607    0 -1001    {1 {SQLITE_MISUSE - not an error}}
000608    1 -1       {1 {SQLITE_MISUSE - not an error}}
000609    2  0       {0 {0 -1 -1}}
000610    3  1       {0 {0 -1 -1}}
000611    4  2       {0 {0 -1 -1}}
000612    5  3       {0 {0 -1 -1}}
000613    6  4       {1 {SQLITE_MISUSE - not an error}}
000614    7  114     {1 {SQLITE_MISUSE - not an error}}
000615    8  1000000 {1 {SQLITE_MISUSE - not an error}}
000616  } {
000617    do_test 4.$tn {
000618      list [catch "wal_checkpoint_v2 db $mode" msg] $msg
000619    } $res
000620  }
000621  db close
000622  
000623  foreach tn {1 2 3} {
000624    forcedelete test.db test.db2 test.db3
000625    testvfs tvfs
000626  
000627    sqlite3 db test.db -vfs tvfs
000628    execsql {
000629      ATTACH 'test.db2' AS aux2;
000630      ATTACH 'test.db3' AS aux3;
000631      PRAGMA main.journal_mode = WAL;
000632      PRAGMA aux2.journal_mode = WAL;
000633      PRAGMA aux3.journal_mode = WAL;
000634  
000635      CREATE TABLE main.t1(x,y);
000636      CREATE TABLE aux2.t2(x,y);
000637      CREATE TABLE aux3.t3(x,y);
000638  
000639      INSERT INTO t1 VALUES('a', 'b');
000640      INSERT INTO t2 VALUES('a', 'b');
000641      INSERT INTO t3 VALUES('a', 'b');
000642    }
000643    sqlite3 db2 test.db2 -vfs tvfs
000644  
000645    switch -- $tn {
000646      1 {
000647        # EVIDENCE-OF: R-41299-52117 If no error (SQLITE_BUSY or otherwise) is
000648        # encountered while processing the attached databases, SQLITE_OK is
000649        # returned.
000650        do_test 5.$tn.1 {
000651          lindex [wal_checkpoint_v2 db truncate] 0
000652        } {0}    ;# 0 -> SQLITE_OK
000653        do_test 5.$tn.2 {
000654          list [expr [file size test.db-wal]==0]  \
000655               [expr [file size test.db2-wal]==0] \
000656               [expr [file size test.db3-wal]==0]
000657        } {1 1 1}
000658      }
000659  
000660      2 {
000661        # EVIDENCE-OF: R-38578-34175 If an SQLITE_BUSY error is encountered when
000662        # processing one or more of the attached WAL databases, the operation is
000663        # still attempted on any remaining attached databases and SQLITE_BUSY is
000664        # returned at the end.
000665        db2 eval { BEGIN; INSERT INTO t2 VALUES('d', 'e'); }
000666        do_test 5.$tn.1 {
000667          lindex [wal_checkpoint_v2 db truncate] 0
000668        } {1}    ;# 1 -> SQLITE_BUSY
000669        do_test 5.$tn.2 {
000670          list [expr [file size test.db-wal]==0]  \
000671               [expr [file size test.db2-wal]==0] \
000672               [expr [file size test.db3-wal]==0]
000673        } {1 0 1}
000674        db2 eval ROLLBACK
000675      }
000676  
000677      3 {
000678        # EVIDENCE-OF: R-38049-07913 If any other error occurs while processing
000679        # an attached database, processing is abandoned and the error code is
000680        # returned to the caller immediately.
000681        tvfs filter xWrite
000682        tvfs script inject_ioerr
000683        proc inject_ioerr {method file args} {
000684          if {[file tail $file]=="test.db2"} {
000685            return "SQLITE_IOERR"
000686          }
000687          return 0
000688        }
000689        do_test 5.$tn.1 {
000690          list [catch { wal_checkpoint_v2 db truncate } msg] $msg
000691        } {1 {SQLITE_IOERR - disk I/O error}}
000692        do_test 5.$tn.2 {
000693          list [expr [file size test.db-wal]==0]  \
000694               [expr [file size test.db2-wal]==0] \
000695               [expr [file size test.db3-wal]==0]
000696        } {1 0 0}
000697        tvfs script ""
000698      }
000699    }
000700  
000701    db close
000702    db2 close
000703  }
000704  
000705  reset_db
000706  sqlite3 db2 test.db
000707  
000708  do_test 6.1 {
000709    execsql {
000710      PRAGMA auto_vacuum = 0; 
000711      PRAGMA journal_mode = WAL;
000712      CREATE TABLE t1(a, b);
000713      INSERT INTO t1 VALUES(1, 2);
000714    }
000715    file size test.db-wal
000716  } [wal_file_size 3 1024]
000717  
000718  do_test 6.2 {
000719    db2 eval { BEGIN; SELECT * FROM t1; }
000720    db  eval { INSERT INTO t1 VALUES(3, 4) }
000721    file size test.db-wal
000722  } [wal_file_size 4 1024]
000723  
000724  #   At this point the log file contains 4 frames. 3 of which it should
000725  #   be possible to checkpoint.
000726  #
000727  # EVIDENCE-OF: R-16642-42503 If pnLog is not NULL, then *pnLog is set to
000728  # the total number of frames in the log file or to -1 if the checkpoint
000729  # could not run because of an error or because the database is not in
000730  # WAL mode.
000731  #
000732  # EVIDENCE-OF: R-10514-25250 If pnCkpt is not NULL,then *pnCkpt is set
000733  # to the total number of checkpointed frames in the log file (including
000734  # any that were already checkpointed before the function was called) or
000735  # to -1 if the checkpoint could not run due to an error or because the
000736  # database is not in WAL mode.
000737  #
000738  do_test 6.4 {
000739    lrange [wal_checkpoint_v2 db passive] 1 2
000740  } {4 3} 
000741  
000742  # EVIDENCE-OF: R-37257-17813 Note that upon successful completion of an
000743  # SQLITE_CHECKPOINT_TRUNCATE, the log file will have been truncated to
000744  # zero bytes and so both *pnLog and *pnCkpt will be set to zero.
000745  #
000746  do_test 6.5 {
000747    db2 eval COMMIT
000748    wal_checkpoint_v2 db truncate
000749  } {0 0 0}
000750  
000751  
000752  
000753  finish_test