xref: /sqlite-3.40.0/test/snapshot_fault.test (revision 20ba3d7f)
17d159794Sdan# 2015 December 10
27d159794Sdan#
37d159794Sdan# The author disclaims copyright to this source code.  In place of
47d159794Sdan# a legal notice, here is a blessing:
57d159794Sdan#
67d159794Sdan#    May you do good and not evil.
77d159794Sdan#    May you find forgiveness for yourself and forgive others.
87d159794Sdan#    May you share freely, never taking more than you give.
97d159794Sdan#
107d159794Sdan#***********************************************************************
117d159794Sdan# This file implements regression tests for SQLite library. The focus
127d159794Sdan# of this file is the sqlite3_snapshot_xxx() APIs.
137d159794Sdan#
147d159794Sdan
157d159794Sdanset testdir [file dirname $argv0]
167d159794Sdansource $testdir/tester.tcl
1768d28ea4Sdrhifcapable !snapshot {finish_test; return}
187d159794Sdanset testprefix snapshot_fault
197d159794Sdan
207d159794Sdan#-------------------------------------------------------------------------
217d159794Sdan# Check that an sqlite3_snapshot_open() client cannot be tricked into
227d159794Sdan# reading a corrupt snapshot even if a second client fails while
237d159794Sdan# checkpointing the db.
247d159794Sdan#
257d159794Sdando_faultsim_test 1.0 -prep {
267d159794Sdan  faultsim_delete_and_reopen
277d159794Sdan  sqlite3 db2 test.db
287d159794Sdan  db2 eval {
297d159794Sdan    CREATE TABLE t1(a, b UNIQUE, c UNIQUE);
307d159794Sdan    INSERT INTO t1 VALUES(1, randomblob(500), randomblob(500));
317d159794Sdan    INSERT INTO t1 VALUES(2, randomblob(500), randomblob(500));
327d159794Sdan    PRAGMA journal_mode = wal;
337d159794Sdan    INSERT INTO t1 VALUES(3, randomblob(500), randomblob(500));
347d159794Sdan    BEGIN;
357d159794Sdan      SELECT a FROM t1;
367d159794Sdan  }
377d159794Sdan  set ::snapshot [sqlite3_snapshot_get db2 main]
387d159794Sdan  db2 eval COMMIT
397d159794Sdan  db2 eval {
407d159794Sdan    UPDATE t1 SET b=randomblob(501), c=randomblob(501) WHERE a=1;
417d159794Sdan    INSERT INTO t1 VALUES(4, randomblob(500), randomblob(500));
427d159794Sdan    INSERT INTO t1 VALUES(5, randomblob(500), randomblob(500));
437d159794Sdan    INSERT INTO t1 VALUES(6, randomblob(500), randomblob(500));
447d159794Sdan  }
457d159794Sdan} -body {
467d159794Sdan  db eval { PRAGMA wal_checkpoint }
477d159794Sdan} -test {
487d159794Sdan  db2 eval BEGIN
497d159794Sdan  if {[catch { sqlite3_snapshot_open db2 main $::snapshot } msg]} {
507d159794Sdan    if {$msg != "SQLITE_BUSY_SNAPSHOT" && $msg != "SQLITE_BUSY"} {
517d159794Sdan      error "error is $msg"
527d159794Sdan    }
537d159794Sdan  } else {
547d159794Sdan    set res [db2 eval {
557d159794Sdan      SELECT a FROM t1;
567d159794Sdan      PRAGMA integrity_check;
577d159794Sdan    }]
587d159794Sdan    if {$res != "1 2 3 ok"} { error "res is $res" }
597d159794Sdan  }
607d159794Sdan
617d159794Sdan  sqlite3_snapshot_free $::snapshot
627d159794Sdan}
637d159794Sdan
647d159794Sdan#-------------------------------------------------------------------------
657d159794Sdan# This test is similar to the previous one. Except, after the
667d159794Sdan# "PRAGMA wal_checkpoint" command fails the db is closed and reopened
677d159794Sdan# so as to require wal file recovery. It should not be possible to open
687d159794Sdan# a snapshot that is part of the body of a recovered wal file.
697d159794Sdan#
707d159794Sdando_faultsim_test 2.0 -prep {
717d159794Sdan  faultsim_delete_and_reopen
727d159794Sdan  db eval {
737d159794Sdan    CREATE TABLE t1(a, b UNIQUE, c UNIQUE);
747d159794Sdan    INSERT INTO t1 VALUES(1, randomblob(500), randomblob(500));
757d159794Sdan    INSERT INTO t1 VALUES(2, randomblob(500), randomblob(500));
767d159794Sdan    PRAGMA journal_mode = wal;
777d159794Sdan    INSERT INTO t1 VALUES(3, randomblob(500), randomblob(500));
787d159794Sdan    BEGIN;
797d159794Sdan      SELECT a FROM t1;
807d159794Sdan  }
817d159794Sdan  set ::snapshot [sqlite3_snapshot_get db main]
827d159794Sdan  db eval COMMIT
837d159794Sdan
847d159794Sdan  db eval {
857d159794Sdan    UPDATE t1 SET b=randomblob(501), c=randomblob(501) WHERE a=1;
867d159794Sdan    INSERT INTO t1 VALUES(4, randomblob(500), randomblob(500));
877d159794Sdan    INSERT INTO t1 VALUES(5, randomblob(500), randomblob(500));
887d159794Sdan    INSERT INTO t1 VALUES(6, randomblob(500), randomblob(500));
897d159794Sdan  }
907d159794Sdan} -body {
917d159794Sdan  db eval { PRAGMA wal_checkpoint }
927d159794Sdan} -test {
937d159794Sdan
947d159794Sdan  db_save
957d159794Sdan  db close
967d159794Sdan  db_restore_and_reopen
977d159794Sdan  db eval { SELECT * FROM t1 }
987d159794Sdan
997d159794Sdan  db eval BEGIN
1007d159794Sdan  if {[catch { sqlite3_snapshot_open db main $::snapshot } msg]} {
1017d159794Sdan    if {$msg != "SQLITE_BUSY_SNAPSHOT" && $msg != "SQLITE_BUSY"} {
1027d159794Sdan      error "error is $msg"
1037d159794Sdan    }
1047d159794Sdan  } else {
1057d159794Sdan    # This branch should actually never be taken. But it was useful in
1067d159794Sdan    # determining whether or not this test was actually working (by
1077d159794Sdan    # running a modified version of SQLite that allowed snapshots to be
1087d159794Sdan    # opened following a recovery).
1097d159794Sdan    error "TEST HAS FAILED"
1107d159794Sdan
1117d159794Sdan    set res [db eval {
1127d159794Sdan      SELECT a FROM t1;
1137d159794Sdan      PRAGMA integrity_check;
1147d159794Sdan    }]
1157d159794Sdan    if {$res != "1 2 3 ok"} { error "res is $res" }
1167d159794Sdan  }
1177d159794Sdan
1187d159794Sdan  sqlite3_snapshot_free $::snapshot
1197d159794Sdan}
1207d159794Sdan
1217d159794Sdan#-------------------------------------------------------------------------
1227d159794Sdan# Test the handling of faults that occur within sqlite3_snapshot_open().
1237d159794Sdan#
1247d159794Sdando_faultsim_test 3.0 -prep {
1257d159794Sdan  faultsim_delete_and_reopen
1267d159794Sdan  db eval {
1277d159794Sdan    CREATE TABLE t1(a, b UNIQUE, c UNIQUE);
1287d159794Sdan    INSERT INTO t1 VALUES(1, randomblob(500), randomblob(500));
1297d159794Sdan    INSERT INTO t1 VALUES(2, randomblob(500), randomblob(500));
1307d159794Sdan    PRAGMA journal_mode = wal;
1317d159794Sdan    INSERT INTO t1 VALUES(3, randomblob(500), randomblob(500));
1327d159794Sdan    BEGIN;
1337d159794Sdan      SELECT a FROM t1;
1347d159794Sdan  }
1357d159794Sdan  set ::snapshot [sqlite3_snapshot_get db main]
1367d159794Sdan  db eval COMMIT
1377d159794Sdan  db eval {
1387d159794Sdan    UPDATE t1 SET b=randomblob(501), c=randomblob(501) WHERE a=1;
1397d159794Sdan    INSERT INTO t1 VALUES(4, randomblob(500), randomblob(500));
1407d159794Sdan    INSERT INTO t1 VALUES(5, randomblob(500), randomblob(500));
1417d159794Sdan    INSERT INTO t1 VALUES(6, randomblob(500), randomblob(500));
1427d159794Sdan    BEGIN;
1437d159794Sdan  }
1447d159794Sdan} -body {
1457d159794Sdan  if { [catch { sqlite3_snapshot_open db main $::snapshot } msg] } {
1467d159794Sdan    error $msg
1477d159794Sdan  }
1487d159794Sdan} -test {
149cc47eac0Sdan  faultsim_test_result {0 {}} {1 SQLITE_IOERR} {1 SQLITE_NOMEM} \
1507d159794Sdan                              {1 SQLITE_IOERR_NOMEM} {1 SQLITE_IOERR_READ}
1517d159794Sdan  if {$testrc==0} {
1527d159794Sdan    set res [db eval {
1537d159794Sdan      SELECT a FROM t1;
1547d159794Sdan      PRAGMA integrity_check;
1557d159794Sdan    }]
1567d159794Sdan    if {$res != "1 2 3 ok"} { error "res is $res" }
1577d159794Sdan  }
1587d159794Sdan
1597d159794Sdan  sqlite3_snapshot_free $::snapshot
1607d159794Sdan}
1617d159794Sdan
1626a9e7f16Sdan#-------------------------------------------------------------------------
1636a9e7f16Sdan# Test the handling of faults that occur within sqlite3_snapshot_recover().
1646a9e7f16Sdan#
1656a9e7f16Sdanreset_db
1666a9e7f16Sdando_execsql_test 4.0 {
1676a9e7f16Sdan  PRAGMA journal_mode = wal;
1686a9e7f16Sdan  CREATE TABLE t1(zzz);
1696a9e7f16Sdan  INSERT INTO t1 VALUES('abc');
1706a9e7f16Sdan  INSERT INTO t1 VALUES('def');
1716a9e7f16Sdan} {wal}
1726a9e7f16Sdanfaultsim_save_and_close
1736a9e7f16Sdan
174*20ba3d7fSdando_test 4.0.1 {
1756a9e7f16Sdan  faultsim_restore_and_reopen
1766a9e7f16Sdan  db eval { SELECT * FROM sqlite_master }
1776a9e7f16Sdan  sqlite3_snapshot_recover db main
1786a9e7f16Sdan} {}
1796a9e7f16Sdandb close
1806a9e7f16Sdan
181*20ba3d7fSdando_faultsim_test 4.0 -faults oom* -prep {
1826a9e7f16Sdan  faultsim_restore_and_reopen
1836a9e7f16Sdan  db eval { SELECT * FROM sqlite_master }
1846a9e7f16Sdan} -body {
1856a9e7f16Sdan  sqlite3_snapshot_recover db main
1866a9e7f16Sdan} -test {
1876a9e7f16Sdan  faultsim_test_result {0 {}} {1 SQLITE_NOMEM} {1 SQLITE_IOERR_NOMEM}
1886a9e7f16Sdan}
1897d159794Sdan
190*20ba3d7fSdan# The following test cases contrive to call sqlite3_snapshot_recover()
191*20ba3d7fSdan# before all pages of the *-shm file have been mapped. This tests an
192*20ba3d7fSdan# extra branch of error handling logic in snapshot_recover().
193*20ba3d7fSdan#
194*20ba3d7fSdanreset_db
195*20ba3d7fSdando_execsql_test 4.1.0 {
196*20ba3d7fSdan  PRAGMA page_size = 512;
197*20ba3d7fSdan  PRAGMA journal_mode = wal;
198*20ba3d7fSdan  PRAGMA wal_autocheckpoint = 0;
199*20ba3d7fSdan  CREATE TABLE t1(zzz);
200*20ba3d7fSdan  INSERT INTO t1 VALUES(randomblob( 500 * 9500 ));
201*20ba3d7fSdan  PRAGMA user_version = 211;
202*20ba3d7fSdan} {wal 0}
203*20ba3d7fSdan
204*20ba3d7fSdando_test 4.1.1 {
205*20ba3d7fSdan  list [file size test.db-shm] [file size test.db]
206*20ba3d7fSdan} {98304 512}
207*20ba3d7fSdan
208*20ba3d7fSdanfaultsim_save_and_close
209*20ba3d7fSdando_faultsim_test 4.1 -faults shm* -prep {
210*20ba3d7fSdan  catch { db2 close }
211*20ba3d7fSdan  catch { db close }
212*20ba3d7fSdan  faultsim_restore_and_reopen
213*20ba3d7fSdan  sqlite3 db2 test.db
214*20ba3d7fSdan  db2 eval { SELECT * FROM sqlite_master }
215*20ba3d7fSdan  db eval BEGIN
216*20ba3d7fSdan  sqlite3_snapshot_get_blob db main
217*20ba3d7fSdan  db eval COMMIT
218*20ba3d7fSdan} -body {
219*20ba3d7fSdan  sqlite3_snapshot_recover db main
220*20ba3d7fSdan} -test {
221*20ba3d7fSdan  faultsim_test_result {0 {}} {1 SQLITE_IOERR}
222*20ba3d7fSdan}
223*20ba3d7fSdan
224*20ba3d7fSdan
2257d159794Sdan
2267d159794Sdanfinish_test
227