SQLite

Artifact [8f480128b1]
Login

Artifact 8f480128b140e54253684bc395f7af0254dc4e03:


# 2010 April 13
#
# 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/lock_common.tcl

proc reopen_db {} {
  catch { db close }
  file delete -force test.db test.db-wal
  sqlite3_wal db test.db
}

proc sqlite3_wal {args} {
  eval sqlite3 $args
  [lindex $args 0] eval { PRAGMA journal_mode = wal }
}

#
# These are 'warm-body' tests used while developing the WAL code. They
# serve to prove that a few really simple cases work:
#
# wal-1.*: Read and write the database.
# wal-2.*: Test MVCC with one reader, one writer.
# wal-3.*: Test transaction rollback.
# wal-4.*: Test savepoint/statement rollback.
# wal-5.*: Test the temp database.
# wal-6.*: Test creating databases with different page sizes.
#

do_test wal-0.1 {
  execsql { PRAGMA journal_mode = wal }
} {wal}

do_test wal-1.0 {
  execsql { 
    BEGIN;
    CREATE TABLE t1(a, b); 
  }
  list [file exists test.db-journal] [file exists test.db-wal]
} {0 1}
do_test wal-1.1 {
  execsql COMMIT
  list [file exists test.db-journal] [file exists test.db-wal]
} {0 1}
do_test wal-1.2 {
  # There are now two pages in the log.
  file size test.db-wal
} [expr (20+1024)*2]

do_test wal-1.3 {
  execsql { SELECT * FROM sqlite_master }
} {table t1 t1 2 {CREATE TABLE t1(a, b)}}

do_test wal-1.4 {
  execsql { INSERT INTO t1 VALUES(1, 2) }
  execsql { INSERT INTO t1 VALUES(3, 4) }
  execsql { INSERT INTO t1 VALUES(5, 6) }
  execsql { INSERT INTO t1 VALUES(7, 8) }
  execsql { INSERT INTO t1 VALUES(9, 10) }
} {}

do_test wal-1.5 {
  execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10}

do_test wal-2.1 {
  sqlite3_wal db2 ./test.db
  execsql { BEGIN; SELECT * FROM t1 } db2
} {1 2 3 4 5 6 7 8 9 10}

do_test wal-2.2 {
  execsql { INSERT INTO t1 VALUES(11, 12) }
  execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10 11 12}

do_test wal-2.3 {
  execsql { SELECT * FROM t1 } db2
} {1 2 3 4 5 6 7 8 9 10}

do_test wal-2.4 {
  execsql { INSERT INTO t1 VALUES(13, 14) }
  execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}

do_test wal-2.5 {
  execsql { SELECT * FROM t1 } db2
} {1 2 3 4 5 6 7 8 9 10}

do_test wal-2.6 {
  execsql { COMMIT; SELECT * FROM t1 } db2
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}

do_test wal-3.1 {
  execsql { BEGIN; DELETE FROM t1 }
  execsql { SELECT * FROM t1 }
} {}
do_test wal-3.2 {
  execsql { SELECT * FROM t1 } db2
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
do_test wal-3.3 {
  execsql { ROLLBACK }
  execsql { SELECT * FROM t1 }
} {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
db2 close

do_test wal-4.1 {
  execsql {
    DELETE FROM t1;
    BEGIN;
      INSERT INTO t1 VALUES('a', 'b');
      SAVEPOINT sp;
        INSERT INTO t1 VALUES('c', 'd');
        SELECT * FROM t1;
  }
} {a b c d}
do_test wal-4.2 {
  execsql {
      ROLLBACK TO sp;
      SELECT * FROM t1;
  }
} {a b}
do_test wal-4.3 {
  execsql {
    COMMIT;
    SELECT * FROM t1;
  }
} {a b}

do_test wal-5.1 {
  execsql {
    CREATE TEMP TABLE t2(a, b);
    INSERT INTO t2 VALUES(1, 2);
  }
} {}
do_test wal-5.2 {
  execsql {
    BEGIN;
      INSERT INTO t2 VALUES(3, 4);
      SELECT * FROM t2;
  }
} {1 2 3 4}
do_test wal-5.3 {
  execsql {
    ROLLBACK;
    SELECT * FROM t2;
  }
} {1 2}
do_test wal-5.4 {
  execsql {
    CREATE TEMP TABLE t3(x UNIQUE);
    BEGIN;
      INSERT INTO t2 VALUES(3, 4);
      INSERT INTO t3 VALUES('abc');
  }
  catchsql { INSERT INTO t3 VALUES('abc') }
} {1 {column x is not unique}}
do_test wal-5.5 {
  execsql {
    COMMIT;
    SELECT * FROM t2;
  }
} {1 2 3 4}
db close


foreach sector {512 4096} {
  sqlite3_simulate_device -sectorsize $sector
  foreach pgsz {512 1024 2048 4096} {
    file delete -force test.db test.db-wal
    do_test wal-6.$sector.$pgsz.1 {
      sqlite3_wal db test.db -vfs devsym
      execsql "
        PRAGMA page_size = $pgsz ;
      "
      execsql "
        CREATE TABLE t1(a, b);
        INSERT INTO t1 VALUES(1, 2);
      "
      db close
      file size test.db
    } [expr $pgsz*2]
  
    do_test wal-6.$sector.$pgsz.2 {
      file size test.db-wal
    } {0}
  }
}

do_test wal-7.1 {
  file delete -force test.db test.db-wal
  sqlite3_wal db test.db
  execsql {
    PRAGMA page_size = 1024;
    CREATE TABLE t1(a, b);
    INSERT INTO t1 VALUES(1, 2);
  }
  list [file size test.db] [file size test.db-wal]
} [list 0 [expr (1024+20)*3]]
do_test wal-7.2 {
  execsql { PRAGMA checkpoint }
  list [file size test.db] [file size test.db-wal]
} [list 2048 [expr (1024+20)*3]]

# Execute some transactions in auto-vacuum mode to test database file
# truncation.
#
do_test wal-8.1 {
  reopen_db
  execsql {
    PRAGMA auto_vacuum = 1;
    PRAGMA auto_vacuum;
  }
} {1}
do_test wal-8.2 {
  execsql {
    PRAGMA page_size = 1024;
    CREATE TABLE t1(x);
    INSERT INTO t1 VALUES(randomblob(900));
    INSERT INTO t1 VALUES(randomblob(900));
    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 */
    PRAGMA checkpoint;
  }
  file size test.db
} [expr 68*1024]
do_test wal-8.3 {
  execsql { 
    DELETE FROM t1 WHERE rowid<54;
    PRAGMA checkpoint;
  }
  file size test.db
} [expr 14*1024]

# Run some "warm-body" tests to ensure that log-summary files with more
# than 256 entries (log summaries that contain index blocks) work Ok.
#
do_test wal-9.1 {
  reopen_db
  execsql {
    PRAGMA page_size = 1024;
    CREATE TABLE t1(x PRIMARY KEY);
    INSERT INTO t1 VALUES(randomblob(900));
    INSERT INTO t1 VALUES(randomblob(900));
    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 */
  }
  file size test.db
} 0
do_test wal-9.2 {
  sqlite3_wal db2 test.db
  execsql {PRAGMA integrity_check } db2
} {ok}

do_test wal-9.3 {
  file delete -force test2.db test2.db-wal
  file copy test.db test2.db
  file copy test.db-wal test2.db-wal
  sqlite3_wal db3 test2.db 
  execsql {PRAGMA integrity_check } db3
} {ok}
db3 close

do_test wal-9.4 {
  execsql { PRAGMA checkpoint }
  db2 close
  sqlite3_wal db2 test.db
  execsql {PRAGMA integrity_check } db2
} {ok}

foreach handle {db db2 db3} { catch { $handle close } }
unset handle

#-------------------------------------------------------------------------
# The following block of tests - wal-10.* - test that the WAL locking 
# scheme works in simple cases. This block of tests is run twice. Once
# using multiple connections in the address space of the current process,
# and once with all connections except one running in external processes.
#
foreach code [list {
  set ::code2_chan [launch_testfixture]
  set ::code3_chan [launch_testfixture]
  proc code2 {tcl} { testfixture $::code2_chan $tcl }
  proc code3 {tcl} { testfixture $::code3_chan $tcl }
  set tn 1
} {
  proc code2 {tcl} { uplevel #0 $tcl }
  proc code3 {tcl} { uplevel #0 $tcl }
  set tn 2
}] {

  eval $code
  reopen_db

  # Open connections [db2] and [db3]. Depending on which iteration this
  # is, the connections may be created in this interpreter, or in 
  # interpreters running in other OS processes. As such, the [db2] and [db3]
  # commands should only be accessed within [code2] and [code3] blocks,
  # respectively.
  #
  code2 { sqlite3 db2 test.db ; db2 eval { PRAGMA journal_mode = WAL } }
  code3 { sqlite3 db3 test.db ; db3 eval { PRAGMA journal_mode = WAL } }

  # Shorthand commands. Execute SQL using database connection [db2] or 
  # [db3]. Return the results.
  #
  proc sql2 {sql} { code2 [list db2 eval $sql] }
  proc sql3 {sql} { code3 [list db3 eval $sql] }

  # Initialize the database schema and contents.
  #
  do_test wal-10.$tn.1 {
    execsql {
      CREATE TABLE t1(a, b);
      INSERT INTO t1 VALUES(1, 2);
      SELECT * FROM t1;
    }
  } {1 2}

  # Open a transaction and write to the database using [db]. Check that [db2]
  # is still able to read the snapshot before the transaction was opened.
  #
  do_test wal-10.$tn.2 {
    execsql { BEGIN; INSERT INTO t1 VALUES(3, 4); }
    sql2 {SELECT * FROM t1}
  } {1 2}

  # Have [db] commit the transaction. Check that [db2] is now seeing the 
  # new, updated snapshot.
  #
  do_test wal-10.$tn.3 {
    execsql { COMMIT }
    sql2 {SELECT * FROM t1}
  } {1 2 3 4}

  # Have [db2] open a read transaction. Then write to the db via [db]. Check
  # that [db2] is still seeing the original snapshot. Then read with [db3].
  # [db3] should see the newly committed data.
  #
  do_test wal-10.$tn.4 {
    sql2 { BEGIN ; SELECT * FROM t1}
  } {1 2 3 4}
  do_test wal-10.$tn.5 {
    execsql { INSERT INTO t1 VALUES(5, 6); }
    sql2 {SELECT * FROM t1}
  } {1 2 3 4}
  do_test wal-10.$tn.6 {
    sql3 {SELECT * FROM t1}
  } {1 2 3 4 5 6}
  do_test wal-10.$tn.7 {
    sql2 COMMIT
  } {}

  # Have [db2] open a write transaction. Then attempt to write to the 
  # database via [db]. This should fail (writer lock cannot be obtained).
  #
  # Then open a read-transaction with [db]. Commit the [db2] transaction
  # to disk. Verify that [db] still cannot write to the database (because
  # it is reading an old snapshot).
  #
  # Close the current [db] transaction. Open a new one. [db] can now write
  # to the database (as it is not locked and [db] is reading the latest
  # snapshot).
  #
  do_test wal-10.$tn.7 {
    sql2 { BEGIN; INSERT INTO t1 VALUES(7, 8) ; }
    catchsql { INSERT INTO t1 VALUES(9, 10) }
  } {1 {database is locked}}
  do_test wal-10.$tn.8 {
    execsql { BEGIN ; SELECT * FROM t1 }
  } {1 2 3 4 5 6}
  do_test wal-10.$tn.9 {
    sql2 COMMIT
    catchsql { INSERT INTO t1 VALUES(9, 10) }
  } {1 {database is locked}}
  do_test wal-10.$tn.10 {
    execsql { COMMIT; BEGIN; INSERT INTO t1 VALUES(9, 10); COMMIT; }
    execsql { SELECT * FROM t1 }
  } {1 2 3 4 5 6 7 8 9 10}

  # Open a read transaction with [db2]. Check that this prevents [db] from
  # checkpointing the database. But not from writing to it.
  #
  do_test wal-10.$tn.11 {
    sql2 { BEGIN; SELECT * FROM t1 }
  } {1 2 3 4 5 6 7 8 9 10}
  do_test wal-10.$tn.12 {
    catchsql { PRAGMA checkpoint } 
  } {1 {database is locked}}
  do_test wal-10.$tn.13 {
    execsql { INSERT INTO t1 VALUES(11, 12) }
    sql2 {SELECT * FROM t1}
  } {1 2 3 4 5 6 7 8 9 10}

  # Connection [db2] is holding a lock on a snapshot, preventing [db] from
  # checkpointing the database. Add a busy-handler to [db]. If [db2] completes
  # its transaction from within the busy-handler, [db] is able to complete
  # the checkpoint operation.
  #
  proc busyhandler x {
    if {$x==4} { sql2 COMMIT }
    if {$x<5} { return 0 }
    return 1
  }
  db busy busyhandler
  do_test wal-10.$tn.14 {
    execsql { PRAGMA checkpoint } 
  } {}

  # Similar to the test above. Except this time, a new read transaction is
  # started (db3) while the checkpointer is waiting for an old one (db2) to 
  # finish. The checkpointer can finish, but any subsequent write operations 
  # must wait until after db3 has closed the read transaction, as db3 is a
  # "region D" writer.
  #
  db busy {}
  do_test wal-10.$tn.15 {
    sql2 { BEGIN; SELECT * FROM t1; }
  } {1 2 3 4 5 6 7 8 9 10 11 12}
  do_test wal-10.$tn.16 {
    catchsql { PRAGMA checkpoint } 
  } {1 {database is locked}}
  proc busyhandler x {
    if {$x==3} { sql3 { BEGIN; SELECT * FROM t1 } }
    if {$x==4} { sql2 COMMIT }
    if {$x<5}  { return 0 }
    return 1
  }
  db busy busyhandler
  do_test wal-10.$tn.17 {
    execsql { PRAGMA checkpoint } 
  } {}
  do_test wal-10.$tn.18 {
    sql3 { SELECT * FROM t1 }
  } {1 2 3 4 5 6 7 8 9 10 11 12}
  do_test wal-10.$tn.19 {
    catchsql { INSERT INTO t1 VALUES(13, 14) }
  } {1 {database is locked}}
  do_test wal-10.$tn.20 {
    execsql { SELECT * FROM t1 }
  } {1 2 3 4 5 6 7 8 9 10 11 12}
  do_test wal-10.$tn.21 {
    sql3 COMMIT
  } {}
  do_test wal-10.$tn.22 {
    execsql { INSERT INTO t1 VALUES(13, 14) }
    execsql { SELECT * FROM t1 }
  } {1 2 3 4 5 6 7 8 9 10 11 12 13 14}

  # Set [db3] up as a "region D" reader again. Then upgrade it to a writer
  # and back down to a reader. Then, check that a checkpoint is not possible
  # (as [db3] still has a snapshot locked).
  #
  do_test wal-10.$tn.23 {
    execsql { PRAGMA checkpoint }
  } {}
  do_test wal-10.$tn.24 {
    sql2 { BEGIN; SELECT * FROM t1; }
  } {1 2 3 4 5 6 7 8 9 10 11 12 13 14}
  do_test wal-10.$tn.25 {
    execsql { PRAGMA checkpoint }
  } {}
  do_test wal-10.$tn.26 {
    catchsql { INSERT INTO t1 VALUES(15, 16) }
  } {1 {database is locked}}
  do_test wal-10.$tn.27 {
    sql3 { INSERT INTO t1 VALUES(15, 16) }
  } {}
  do_test wal-10.$tn.28 {
    code3 {
      set ::STMT [sqlite3_prepare db3 "SELECT * FROM t1" -1 TAIL]
      sqlite3_step $::STMT
    }
    sql3 COMMIT
    execsql { SELECT * FROM t1 }
  } {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16}
  db busy {}
  do_test wal-10.$tn.29 {
    execsql { INSERT INTO t1 VALUES(17, 18) }
    catchsql { PRAGMA checkpoint }
  } {1 {database is locked}}
  do_test wal-10.$tn.30 {
    code3 { sqlite3_finalize $::STMT }
    execsql { PRAGMA checkpoint }
  } {}

  # At one point, if a reader failed to upgrade to a writer because it
  # was reading an old snapshot, the write-locks were not being released.
  # Test that this bug has been fixed.
  #
  do_test wal-10.$tn.31 {
    execsql { BEGIN ; SELECT * FROM t1 }
    sql2 { INSERT INTO t1 VALUES(19, 20) }
    catchsql { INSERT INTO t1 VALUES(21, 22) }
  } {1 {database is locked}}
  do_test wal-10.$tn.32 {
    # This statement would fail when the bug was present.
    sql2 { INSERT INTO t1 VALUES(21, 22) }
  } {}
  do_test wal-10.$tn.33 {
    execsql { SELECT * FROM t1 ; COMMIT }
  } {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18}
  do_test wal-10.$tn.34 {
    execsql { SELECT * FROM t1 }
  } {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22}

  catch { db close }
  catch { code2 { db2 close } }
  catch { code3 { db3 close } }
  catch { close $::code2_chan }
  catch { close $::code3_chan }
}

finish_test