xref: /sqlite-3.40.0/test/journal2.test (revision 2a321c75)
1# 2010 June 16
2#
3# The author disclaims copyright to this source code.  In place of
4# a legal notice, here is a blessing:
5#
6#    May you do good and not evil.
7#    May you find forgiveness for yourself and forgive others.
8#    May you share freely, never taking more than you give.
9#
10#***********************************************************************
11# This file implements regression tests for SQLite library.
12#
13
14set testdir [file dirname $argv0]
15source $testdir/tester.tcl
16source $testdir/lock_common.tcl
17source $testdir/malloc_common.tcl
18db close
19
20set a_string_counter 1
21proc a_string {n} {
22  global a_string_counter
23  incr a_string_counter
24  string range [string repeat "${a_string_counter}." $n] 1 $n
25}
26
27# Create a [testvfs] and install it as the default VFS. Set the device
28# characteristics flags to "SAFE_DELETE".
29#
30testvfs tvfs -default 1
31tvfs devchar safe_delete
32
33# Set up a hook so that each time a journal file is opened, closed or
34# deleted, the method name ("xOpen", "xClose" or "xDelete") and the final
35# segment of the journal file-name (i.e. "test.db-journal") are appended to
36# global list variable $::oplog.
37#
38tvfs filter {xOpen xClose xDelete}
39tvfs script journal_op_catcher
40
41proc journal_op_catcher {method filename args} {
42
43  # If global variable ::tvfs_error_on_write is defined, then return an
44  # IO error to every attempt to modify the file-system. Otherwise, return
45  # SQLITE_OK.
46  #
47  if {[info exists ::tvfs_error_on_write]} {
48    if {$method == "xDelete" || $method == "xWrite" || $method == "xTruncate"} {
49      return SQLITE_IOERR
50    }
51    return SQLITE_OK
52  }
53
54  if {[string match *journal* $filename]==0} return
55
56  set f [file tail $filename]
57  lappend ::oplog $method $f
58
59  if {[info exists ::open_journals($f)]==0} { set ::open_journals($f) 0 }
60  switch -- $method {
61    xOpen {
62      incr ::open_journals($f) +1
63    }
64    xClose {
65      incr ::open_journals($f) -1
66    }
67    xDelete {
68      if {$::open_journals($f)>0} { return SQLITE_IOERR }
69    }
70  }
71
72  return
73}
74
75
76do_test journal2-1.1 {
77  set ::oplog [list]
78  sqlite3 db test.db
79  execsql { CREATE TABLE t1(a, b) }
80  set ::oplog
81} {xOpen test.db-journal xClose test.db-journal xDelete test.db-journal}
82do_test journal2-1.2 {
83  set ::oplog [list]
84  execsql {
85    PRAGMA journal_mode = truncate;
86    INSERT INTO t1 VALUES(1, 2);
87  }
88  set ::oplog
89} {xOpen test.db-journal}
90do_test journal2-1.3 {
91  set ::oplog [list]
92  execsql { INSERT INTO t1 VALUES(3, 4) }
93  set ::oplog
94} {}
95do_test journal2-1.4 { execsql { SELECT * FROM t1 } } {1 2 3 4}
96
97# Add a second connection. This connection attempts to commit data in
98# journal_mode=DELETE mode. When it tries to delete the journal file,
99# the VFS layer returns an IO error.
100#
101do_test journal2-1.5 {
102  set ::oplog [list]
103  sqlite3 db2 test.db
104  execsql  { PRAGMA journal_mode = delete } db2
105  catchsql { INSERT INTO t1 VALUES(5, 6)  } db2
106} {1 {disk I/O error}}
107do_test journal2-1.6 { file exists test.db-journal } 1
108do_test journal2-1.7 { execsql { SELECT * FROM t1 } } {1 2 3 4}
109do_test journal2-1.8 {
110  execsql { PRAGMA journal_mode = truncate } db2
111  execsql { INSERT INTO t1 VALUES(5, 6)  } db2
112} {}
113do_test journal2-1.9 { execsql { SELECT * FROM t1 } } {1 2 3 4 5 6}
114
115# Grow the database until it is reasonably large. Then, from a
116# journal_mode=DELETE connection, attempt to commit a large transaction (one
117# that involves upgrading to an exclusive lock and writing the database
118# before the transaction is committed).
119#
120do_test journal2-1.10 {
121  db2 close
122  db func a_string a_string
123  execsql {
124    CREATE TABLE t2(a UNIQUE, b UNIQUE);
125    INSERT INTO t2 VALUES(a_string(200), a_string(300));
126    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  --  2
127    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  --  4
128    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  --  8
129    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  -- 16
130    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  -- 32
131    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  -- 64
132  }
133  file size test.db-journal
134} {0}
135do_test journal2-1.11 {
136  set sz [expr [file size test.db] / 1024]
137  expr {$sz>120 && $sz<200}
138} 1
139
140do_test journal2-1.12 {
141  sqlite3 db2 test.db
142  execsql {
143    PRAGMA cache_size = 10;
144    BEGIN;
145      INSERT INTO t2 SELECT randomblob(200), randomblob(300) FROM t2;  -- 128
146  } db2
147} {}
148do_test journal2-1.13 {
149  tvfs filter {xOpen xClose xDelete xWrite xTruncate}
150  set ::tvfs_error_on_write 1
151  catchsql { COMMIT } db2
152} {1 {disk I/O error}}
153db2 close
154unset ::tvfs_error_on_write
155file copy -force test.db testX.db
156
157do_test journal2-1.14 { file exists test.db-journal } 1
158do_test journal2-1.15 {
159  execsql {
160    SELECT count(*) FROM t2;
161    PRAGMA integrity_check;
162  }
163} {64 ok}
164
165# This block checks that in the test case above, connection [db2] really
166# did begin writing to the database file before it hit IO errors. If
167# this is true, then the copy of the database file made before [db]
168# rolled back the hot journal should fail the integrity-check.
169#
170do_test journal2-1.16 {
171  set sz [expr [file size testX.db] / 1024]
172  expr {$sz>240 && $sz<400}
173} 1
174do_test journal2-1.17 {
175  expr {[catchsql { PRAGMA integrity_check } db] == "0 ok"}
176} {1}
177do_test journal2-1.20 {
178  sqlite3 db2 testX.db
179  expr {[catchsql { PRAGMA integrity_check } db2] == "0 ok"}
180} {0}
181do_test journal2-1.21 {
182  db2 close
183} {}
184
185db close
186tvfs delete
187finish_test
188
189