# 2010 May 03
#
# 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 implements regression tests for SQLite library.  The
# focus of this file is testing the operation of the library in
# "PRAGMA journal_mode=WAL" mode.
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
source $testdir/malloc_common.tcl

ifcapable !wal {finish_test ; return }

do_malloc_test walfault-oom-1 -sqlbody {
  PRAGMA journal_mode = WAL;
  CREATE TABLE t1(a, b);
  INSERT INTO t1 VALUES(1, 2);
  PRAGMA wal_checkpoint;
}

do_malloc_test walfault-oom-2 -tclprep {
  execsql {
    PRAGMA journal_mode = WAL;
    BEGIN;
      CREATE TABLE x(y, z, UNIQUE(y, z));
      INSERT INTO x VALUES(randomblob(100), randomblob(100));
    COMMIT;
    PRAGMA wal_checkpoint;

    INSERT INTO x SELECT randomblob(100), randomblob(100) FROM x;
    INSERT INTO x SELECT randomblob(100), randomblob(100) FROM x;
    INSERT INTO x SELECT randomblob(100), randomblob(100) FROM x;
  }
  file copy -force test.db testX.db
  file copy -force test.db-wal testX.db-wal
  db close
  file rename -force testX.db test.db
  file rename -force testX.db-wal test.db-wal

  sqlite3 db test.db
  sqlite3_extended_result_codes db 1
  sqlite3_db_config_lookaside db 0 0 0
} -sqlbody {
  SELECT count(*) FROM x;
}

do_ioerr_test walfault-ioerr-1 -sqlprep {
  PRAGMA auto_vacuum = 1;
  PRAGMA journal_mode = WAL;
  CREATE TABLE abc(a PRIMARY KEY);
  INSERT INTO abc VALUES(randomblob(1500));
} -sqlbody {
  DELETE FROM abc;
  PRAGMA wal_checkpoint;
}
catch {db close}

# A [testvfs] callback for the VFS created by [do_shmfault_test]. This
# callback injects SQLITE_IOERR faults into methods for which an entry
# in array ::shmfault_ioerr_methods is defined. For example, to enable
# errors in xShmOpen:
#
#   set ::shmfault_ioerr_methods(xShmOpen) 1
# 
# Faults are not injected into xShmRelease, xShmClose or xShmLock method 
# calls. The global tcl variables used are:
#
#   $::shmfault_ioerr_countdown
#   $::shmfault_ioerr_persist
#   $::shmfault_ioerr_methods
#
proc shmfault_vfs_cb {method args} {

  # If ::shmfault_ioerr_countdown is not set, always return SQLITE_OK.
  #
  if {[info exists ::shmfault_ioerr_countdown]==0} { return SQLITE_OK }

  if {[info exists ::shmfault_ioerr_methods($method)]} {
    incr ::shmfault_ioerr_countdown -1
    if { ($::shmfault_ioerr_countdown==0)
      || ($::shmfault_ioerr_countdown<=0 && $::shmfault_ioerr_persist)
    } {
      return SQLITE_IOERR
    }
  }
  return SQLITE_OK
}

# Options are:
#
#   -tclprep TCL
#   -sqlprep SQL
#   -sqlbody SQL
#
proc do_shmfault_test {name args} {

  set A(-tclprep) "sqlite3 db test.db -vfs shmfault"
  set A(-sqlprep) ""
  set A(-sqlbody) ""
  set A(-methods) [list xShmGet xShmOpen xShmSize]
  set A(-coverageonly) 0
  array set A $args

  # Create a VFS to use:
  testvfs shmfault shmfault_vfs_cb

  unset -nocomplain ::shmfault_ioerr_methods
  foreach m $A(-methods) { set ::shmfault_ioerr_methods($m) 1 }
  
  foreach mode {transient persistent} {
    set ::shmfault_ioerr_persist [expr {$mode == "persistent"}]

    for {set nDelay 1} {$nDelay < 10000} {incr nDelay} {
  
      file delete -force test.db test.db-wal test.db-journal

      eval $A(-tclprep)
      db eval $A(-sqlprep)

      set ::shmfault_ioerr_countdown $nDelay
      set rc [catch { db eval $A(-sqlbody) } msg]
      set hit_error [expr {$::shmfault_ioerr_countdown<=0}]
      unset ::shmfault_ioerr_countdown

      catch { db close }
      
      if {$A(-coverageonly)} { set rc $hit_error }
      do_test $name-$mode.$nDelay.1 [list set {} $hit_error] $rc
  
      if {$hit_error==0} break
    }
  }

  shmfault delete
}

do_shmfault_test walfault-shm-1 -sqlbody {
  PRAGMA journal_mode = WAL;
  CREATE TABLE t1(a PRIMARY KEY, b);
  INSERT INTO t1 VALUES('a', 'b');
  PRAGMA wal_checkpoint;
}

do_shmfault_test walfault-shm-2 -methods xShmSize -sqlprep {
  PRAGMA page_size = 512;
  PRAGMA journal_mode = WAL;
  PRAGMA wal_autocheckpoint = 0;
} -sqlbody {
  CREATE TABLE t1(x);
  BEGIN;
    INSERT INTO t1 VALUES(randomblob(400));           /* 1 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 2 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 4 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 8 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 16 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 32 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 64 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 128 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 256 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 512 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 1024 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 2048 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 4096 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 8192 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 16384 */
  COMMIT;
}

do_shmfault_test walfault-shm-3 -methods xShmSize -tclprep {
  sqlite3 db test.db -vfs shmfault
  unset -nocomplain ::shmfault_ioerr_countdown
  db eval {
    PRAGMA page_size = 512;
    PRAGMA journal_mode = WAL;
    PRAGMA wal_autocheckpoint = 0;
    CREATE TABLE t1(x);
    BEGIN;
      INSERT INTO t1 VALUES(randomblob(400));           /* 1 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 2 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 4 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 8 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 16 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 32 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 64 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 128 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 256 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 512 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 1024 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 2048 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 4096 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 8192 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 16384 */
    COMMIT;
  }

  set ::shmfault_ioerr_countdown 1
  set ::shmfault_ioerr_methods(xShmGet) 1
  db close
  unset ::shmfault_ioerr_methods(xShmGet)
  if {[file exists test.db-wal]==0} {error "Failed to create WAL file!"}

  sqlite3 db test.db -vfs shmfault
} -sqlbody {
  SELECT count(*) FROM t1;
}

do_shmfault_test walfault-shm-4 -tclprep {
  sqlite3 db test.db -vfs shmfault
  unset -nocomplain ::shmfault_ioerr_countdown
  db eval {
    PRAGMA page_size = 512;
    PRAGMA journal_mode = WAL;
    PRAGMA wal_autocheckpoint = 0;
    CREATE TABLE t1(x);
    BEGIN;
      INSERT INTO t1 VALUES(randomblob(400));           /* 1 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 2 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 4 */
    COMMIT;
  }

  set ::shmfault_ioerr_countdown 1
  set ::shmfault_ioerr_methods(xShmGet) 1
  db close
  unset ::shmfault_ioerr_methods(xShmGet)
  if {[file exists test.db-wal]==0} {error "Failed to create WAL file!"}
  sqlite3 db test.db -vfs shmfault
} -sqlbody {
  SELECT count(*) FROM t1;
}

do_shmfault_test walfault-shm-5 -coverageonly 1 -sqlprep {
  PRAGMA cache_size = 10;
  PRAGMA journal_mode = WAL;
  CREATE TABLE abc(a PRIMARY KEY);
  INSERT INTO abc VALUES(randomblob(900));
} -sqlbody {
  BEGIN;
    INSERT INTO abc SELECT randomblob(900) FROM abc;    /* 1 */
    INSERT INTO abc SELECT randomblob(900) FROM abc;    /* 2 */
    INSERT INTO abc SELECT randomblob(900) FROM abc;    /* 4 */
    INSERT INTO abc SELECT randomblob(900) FROM abc;    /* 8 */
  ROLLBACK;
}

#-------------------------------------------------------------------------
# When a database is checkpointed, SQLite does the following:
#
#   1. xShmLock(CHECKPOINT) to lock the WAL.
#   2. xShmGet(-1) to get a mapping to read the wal-index header.
#   3. If the mapping obtained in (2) is not large enough to cover the
#      entire wal-index, call xShmGet(nReq) to get a larger mapping.
#   4. Do the checkpoint.
#   5. Release the lock and mapping.
#
# This test case tests the outcome of an IO error in step 2.
#
proc shmfault_vfs_cb_6 {method args} {
  switch -- $::shm_state {
    0 { return SQLITE_OK }
    1 {
      if {$method == "xShmGet"} {
        set ::wal_index [tvfs shm [lindex $args 0]]
        tvfs shm [lindex $args 0] [string range $::wal_index 0 65535]
        set ::shm_state 2
      }
    }
    2 {
      if {$method == "xShmGet"} {
        tvfs shm [lindex $args 0] $::wal_index
        return SQLITE_IOERR
      }
    }
  }
  return SQLITE_OK
}
do_test walfault-shm-6.1 {
  set ::shm_state 0
  testvfs tvfs shmfault_vfs_cb_6

  sqlite3 db  test.db -vfs tvfs
  sqlite3 db2 test.db -vfs tvfs

  execsql {
    PRAGMA journal_mode = WAL;
    PRAGMA wal_autocheckpoint = 0;
    CREATE TABLE t1(x);
    INSERT INTO t1 VALUES(randomblob(900));
  }
} {wal 0}
do_test walfault-shm-6.2 {
  execsql {
    PRAGMA wal_autocheckpoint = 0;
    BEGIN;
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 2 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 4 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 8 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 16 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 32 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 64 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 128 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 256 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 512 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 1024 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 2048 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 4096 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 8192 */
      INSERT INTO t1 SELECT randomblob(900) FROM t1;    /* 16384 */
    COMMIT;
  } db2
} {0}
do_test walfault-shm-6.3 {
  set ::shm_state 1
  catchsql { PRAGMA wal_checkpoint } db2
} {1 {disk I/O error}}
set ::shm_state 0
db close
db2 close
tvfs delete
unset -nocomplain ::wal_index ::shm_state

finish_test

