# 2011 May 09
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#***********************************************************************
#
# This file contains tests for using WAL databases in read-only mode.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/lock_common.tcl
source $testdir/wal_common.tcl
set ::testprefix walro2

# And only if the build is WAL-capable.
#
ifcapable !wal {
  finish_test
  return
}

proc copy_to_test2 {bZeroShm} {
  forcecopy test.db test.db2
  forcecopy test.db-wal test.db2-wal
  if {$bZeroShm} {
    forcedelete test.db2-shm
    set fd [open test.db2-shm w]
    seek $fd [expr [file size test.db-shm]-1]
    puts -nonewline $fd "\0"
    close $fd
  } else {
    forcecopy test.db-shm test.db2-shm
  }
}

# Most systems allocate the *-shm file in 32KB trunks. But on UNIX systems
# for which the getpagesize() call returns greater than 32K, the *-shm
# file is allocated in page-sized units (since you cannot mmap part of
# a page). The following code sets variable $MINSHMSZ to the smallest
# possible *-shm file (i.e. the greater of 32KB and the system page-size).
#
do_execsql_test 0.0 {
  PRAGMA journal_mode = wal;
  CREATE TABLE t1(x);
} {wal}
set MINSHMSZ [file size test.db-shm]
set dfltpgsz [db one {PRAGMA page_size}]

foreach bZeroShm {0 1} {
  for {set pgsz 512} {$pgsz<=65536} {set pgsz [expr {$pgsz*2}]} {
    set TN [expr $bZeroShm+1]-$pgsz
    do_multiclient_test tn {
      
      # Close all connections and delete the database.
      #
      code1 { db close  }
      code2 { db2 close }
      code3 { db3 close }
      forcedelete test.db
      
      # Do not run tests with the connections in the same process.
      #
      if {$tn==2} continue
    
      foreach c {code1 code2 code3} {
        $c {
          sqlite3_shutdown
          sqlite3_config_uri 1
        }
      }
    
      do_test $TN.1.1 {
        code2 { sqlite3 db2 test.db }
        sql2 "PRAGMA page_size=$::pgsz"
        sql2 {
          CREATE TABLE t1(x, y);
          PRAGMA journal_mode = WAL;
          INSERT INTO t1 VALUES('a', 'b');
          INSERT INTO t1 VALUES('c', 'd');
        }
        file exists test.db-shm
      } {1}
    
      do_test $TN.1.2.1 {
        copy_to_test2 $bZeroShm
        code1 {
          sqlite3 db file:test.db2?readonly_shm=1
        }
    
        sql1 { SELECT * FROM t1 }
      } {a b c d}
      do_test $TN.1.2.2 {
        sql1 { SELECT * FROM t1 }
      } {a b c d}
    
      do_test $TN.1.3.1 {
        code3 { sqlite3 db3 test.db2 }
        sql3 { SELECT * FROM t1 }
      } {a b c d}
    
      do_test $TN.1.3.2 {
        sql1 { SELECT * FROM t1 }
      } {a b c d}
    
      code1 { db close  }
      code2 { db2 close }
      code3 { db3 close }
    
      do_test $TN.2.1 {
        code2 { sqlite3 db2 test.db }
        sql2 "PRAGMA page_size=$::pgsz;"
        sql2 { 
          INSERT INTO t1 VALUES('e', 'f');
          INSERT INTO t1 VALUES('g', 'h');
        }
        file exists test.db-shm
      } {1}
    
      do_test $TN.2.2 {
        copy_to_test2 $bZeroShm
        code1 {
          sqlite3 db file:test.db2?readonly_shm=1
        }
        sql1 { 
          BEGIN;
          SELECT * FROM t1;
        }
      } {a b c d e f g h}
    
      do_test $TN.2.3.1 {
        code3 { sqlite3 db3 test.db2 }
        sql3 { SELECT * FROM t1 }
      } {a b c d e f g h}
      do_test $TN.2.3.2 {
        sql3 { INSERT INTO t1 VALUES('i', 'j') }
        code3 { db3 close }
        sql1 { COMMIT } 
      } {}
      do_test $TN.2.3.3 {
        sql1 { SELECT * FROM t1 }
      } {a b c d e f g h i j}
    
    
      #-----------------------------------------------------------------------
      # 3.1.*: That a readonly_shm connection can read a database file if both
      #        the *-wal and *-shm files are zero bytes in size.
      #
      # 3.2.*: That it flushes the cache if, between transactions on a db with a
      #        zero byte *-wal file, some other connection modifies the db, then
      #        does "PRAGMA wal_checkpoint=truncate" to truncate the wal file
      #        back to zero bytes in size.
      #
      # 3.3.*: That, if between transactions some other process wraps the wal
      #        file, the readonly_shm client reruns recovery.
      #
      catch { code1 { db close } }
      catch { code2 { db2 close } }
      catch { code3 { db3 close } }
      do_test $TN.3.1.0 {
        list [file exists test.db-wal] [file exists test.db-shm]
      } {0 0}
      do_test $TN.3.1.1 {
        close [open test.db-wal w]
        close [open test.db-shm w]
        code1 {
          sqlite3 db file:test.db?readonly_shm=1
        }
        sql1 { SELECT * FROM t1 }
      } {a b c d e f g h}
    
      do_test $TN.3.2.0 {
        list [file size test.db-wal] [file size test.db-shm]
      } {0 0}
      do_test $TN.3.2.1 {
        code2 { sqlite3 db2 test.db }
        sql2 { INSERT INTO t1 VALUES(1, 2) ; PRAGMA wal_checkpoint=truncate }
        code2 { db2 close }
        sql1 { SELECT * FROM t1 }
      } {a b c d e f g h 1 2}
      if {$pgsz!=$dfltpgsz} continue
      do_test $TN.3.2.2 {
        list [file size test.db-wal] [file size test.db-shm]
      } [list 0 $MINSHMSZ]
      do_test $TN.3.3.0 {
        code2 { sqlite3 db2 test.db }
        sql2 { 
          INSERT INTO t1 VALUES(3, 4);
          INSERT INTO t1 VALUES(5, 6);
          INSERT INTO t1 VALUES(7, 8);
          INSERT INTO t1 VALUES(9, 10);
        }
        code2 { db2 close }
        code1 { db close }
        list [file size test.db-wal] [file size test.db-shm]
      } [list [wal_file_size 4 1024] $MINSHMSZ]
      do_test $TN.3.3.1 {
        code1 { sqlite3 db file:test.db?readonly_shm=1 }
        sql1 { SELECT * FROM t1 }
      } {a b c d e f g h 1 2 3 4 5 6 7 8 9 10}
      do_test $TN.3.3.2 {
        code2 { sqlite3 db2 test.db }
        sql2 { 
          PRAGMA wal_checkpoint; 
          DELETE FROM t1;
          INSERT INTO t1 VALUES('i', 'ii');
        }
        code2 { db2 close }
        list [file size test.db-wal] [file size test.db-shm]
      } [list [wal_file_size 4 1024] $MINSHMSZ]
      do_test $TN.3.3.3 {
        sql1 { SELECT * FROM t1 }
      } {i ii}
    
      #-----------------------------------------------------------------------
      #
      #
      catch { code1 { db close } }
      catch { code2 { db2 close } }
      catch { code3 { db3 close } }
    
      do_test $TN.4.0 {
        code1 { forcedelete test.db }
        code1 { sqlite3 db test.db }
        sql1 {
          PRAGMA journal_mode = wal;
          CREATE TABLE t1(x);
          INSERT INTO t1 VALUES('hello');
          INSERT INTO t1 VALUES('world');
        }
    
        copy_to_test2 $bZeroShm
    
        code1 { db close }
      } {}
    
      do_test $TN.4.1.1 {
        code2 { sqlite3 db2 file:test.db2?readonly_shm=1 }
        sql2 { SELECT * FROM t1 }
      } {hello world}
    
      do_test $TN.4.1.2 {
        code3 { sqlite3 db3 test.db2 }
        sql3 {
          INSERT INTO t1 VALUES('!');
          PRAGMA wal_checkpoint = truncate;
        }
        code3 { db3 close }
      } {}
      do_test $TN.4.1.3 {
        sql2 { SELECT * FROM t1 }
      } {hello world !}
    
      catch { code1 { db close } }
      catch { code2 { db2 close } }
      catch { code3 { db3 close } }
    
      do_test $TN.4.2.1 {
        code1 { sqlite3 db test.db }
        sql1 {
          INSERT INTO t1 VALUES('!');
          INSERT INTO t1 VALUES('!');
    
          PRAGMA cache_size = 10;
          CREATE TABLE t2(x);
    
          BEGIN;
            WITH s(i) AS (
              SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<500
              )
            INSERT INTO t2 SELECT randomblob(500) FROM s;
            SELECT count(*) FROM t2;
        } 
      } {500}
      set sz [file size test.db-wal]
      do_test $TN.4.2.2.(sz=$sz) {
        expr {$sz>400000}
      } {1}
      do_test $TN.4.2.4 {
        file_control_persist_wal db 1; db close
    
        copy_to_test2 $bZeroShm
        code2 { sqlite3 db2 file:test.db2?readonly_shm=1 }
        sql2 {
          SELECT * FROM t1;
          SELECT count(*) FROM t2;
        }
      } {hello world ! ! 0}
    
      #-----------------------------------------------------------------------
      #
      #
      catch { code1 { db close } }
      catch { code2 { db2 close } }
      catch { code3 { db3 close } }
    
      do_test $TN.5.0 {
        code1 { forcedelete test.db }
        code1 { sqlite3 db test.db }
        sql1 {
          PRAGMA journal_mode = wal;
          CREATE TABLE t1(x);
          INSERT INTO t1 VALUES('hello');
          INSERT INTO t1 VALUES('world');
          INSERT INTO t1 VALUES('!');
          INSERT INTO t1 VALUES('world');
          INSERT INTO t1 VALUES('hello');
        }
    
        copy_to_test2 $bZeroShm
        
        code1 { db close }
      } {}
    
      do_test $TN.5.1 {
        code2 { sqlite3 db2 file:test.db2?readonly_shm=1 }
        sql2 {
          SELECT * FROM t1;
        }
      } {hello world ! world hello}
    
      do_test $TN.5.2 {
        code1 {
          proc handle_read {op args} {
            if {$op=="xRead" && [file tail [lindex $args 0]]=="test.db2-wal"} {
              set ::res2 [sql2 { SELECT * FROM t1 }]
            }
            puts "$msg xRead $args"
            return "SQLITE_OK"
          }
          testvfs tvfs -fullshm 1
    
          sqlite3 db file:test.db2?vfs=tvfs
          db eval { SELECT * FROM sqlite_master }
    
          tvfs filter xRead
          tvfs script handle_read
        }
        sql1 {
          PRAGMA wal_checkpoint = truncate;
        }
        code1 { set ::res2 }
      } {hello world ! world hello}
    
      do_test $TN.5.3 {
        code1 { db close }
        code1 { tvfs delete }
      } {}
    
      #-----------------------------------------------------------------------
      #
      #
      catch { code1 { db close } }
      catch { code2 { db2 close } }
      catch { code3 { db3 close } }
    
      do_test $TN.6.1 {
        code1 { forcedelete test.db }
        code1 { sqlite3 db test.db }
        sql1 {
          PRAGMA journal_mode = wal;
          CREATE TABLE t1(x);
          INSERT INTO t1 VALUES('hello');
          INSERT INTO t1 VALUES('world');
          INSERT INTO t1 VALUES('!');
          INSERT INTO t1 VALUES('world');
          INSERT INTO t1 VALUES('hello');
        }
    
        copy_to_test2 $bZeroShm
        
        code1 { db close }
      } {}
    
      do_test $TN.6.2 {
        code1 {
          set ::nRem 5
          proc handle_read {op args} {
            if {$op=="xRead" && [file tail [lindex $args 0]]=="test.db2-wal"} {
              incr ::nRem -1
              if {$::nRem==0} {
                code2 { sqlite3 db2 test.db2 }
                sql2  { PRAGMA wal_checkpoint = truncate }
              }
            }
            return "SQLITE_OK"
          }
          testvfs tvfs -fullshm 1
    
          tvfs filter xRead
          tvfs script handle_read
    
          sqlite3 db file:test.db2?readonly_shm=1&vfs=tvfs
          db eval { SELECT * FROM t1 }
        }
      } {hello world ! world hello}
    
      do_test $TN.6.3 {
        code1 { db close }
        code1 { tvfs delete }
      } {}
    }
  } ;# for pgsz
} ;# foreach bZeroShm

finish_test