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. Specifically,
12: # it tests SQLite when using a VFS that claims the SAFE_DELETE property.
13: #
14:
15: set testdir [file dirname $argv0]
16: source $testdir/tester.tcl
17: source $testdir/lock_common.tcl
18: source $testdir/malloc_common.tcl
19: db close
20:
21: if {[permutation] == "inmemory_journal"} {
22: finish_test
23: return
24: }
25:
26: set a_string_counter 1
27: proc a_string {n} {
28: global a_string_counter
29: incr a_string_counter
30: string range [string repeat "${a_string_counter}." $n] 1 $n
31: }
32:
33: # Create a [testvfs] and install it as the default VFS. Set the device
34: # characteristics flags to "SAFE_DELETE".
35: #
36: testvfs tvfs -default 1
37: tvfs devchar {undeletable_when_open powersafe_overwrite}
38:
39: # Set up a hook so that each time a journal file is opened, closed or
40: # deleted, the method name ("xOpen", "xClose" or "xDelete") and the final
41: # segment of the journal file-name (i.e. "test.db-journal") are appended to
42: # global list variable $::oplog.
43: #
44: tvfs filter {xOpen xClose xDelete}
45: tvfs script journal_op_catcher
46: proc journal_op_catcher {method filename args} {
47:
48: # If global variable ::tvfs_error_on_write is defined, then return an
49: # IO error to every attempt to modify the file-system. Otherwise, return
50: # SQLITE_OK.
51: #
52: if {[info exists ::tvfs_error_on_write]} {
53: if {[lsearch {xDelete xWrite xTruncate} $method]>=0} {
54: return SQLITE_IOERR
55: }
56: }
57:
58: # The rest of this command only deals with xOpen(), xClose() and xDelete()
59: # operations on journal files. If this invocation does not represent such
60: # an operation, return with no further ado.
61: #
62: set f [file tail $filename]
63: if {[string match *journal $f]==0} return
64: if {[lsearch {xOpen xDelete xClose} $method]<0} return
65:
66: # Append a record of this operation to global list variable $::oplog.
67: #
68: lappend ::oplog $method $f
69:
70: # If this is an attempt to delete a journal file for which there exists
71: # one ore more open handles, return an error. The code in test_vfs.c
72: # will not invoke the xDelete method of the "real" VFS in this case.
73: #
74: if {[info exists ::open_journals($f)]==0} { set ::open_journals($f) 0 }
75: switch -- $method {
76: xOpen { incr ::open_journals($f) +1 }
77: xClose { incr ::open_journals($f) -1 }
78: xDelete { if {$::open_journals($f)>0} { return SQLITE_IOERR } }
79: }
80:
81: return ""
82: }
83:
84:
85: do_test journal2-1.1 {
86: set ::oplog [list]
87: sqlite3 db test.db
88: execsql { CREATE TABLE t1(a, b) }
89: set ::oplog
90: } {xOpen test.db-journal xClose test.db-journal xDelete test.db-journal}
91: do_test journal2-1.2 {
92: set ::oplog [list]
93: execsql {
94: PRAGMA journal_mode = truncate;
95: INSERT INTO t1 VALUES(1, 2);
96: }
97: set ::oplog
98: } {xOpen test.db-journal}
99: do_test journal2-1.3 {
100: set ::oplog [list]
101: execsql { INSERT INTO t1 VALUES(3, 4) }
102: set ::oplog
103: } {}
104: do_test journal2-1.4 { execsql { SELECT * FROM t1 } } {1 2 3 4}
105:
106: # Add a second connection. This connection attempts to commit data in
107: # journal_mode=DELETE mode. When it tries to delete the journal file,
108: # the VFS layer returns an IO error.
109: #
110: do_test journal2-1.5 {
111: set ::oplog [list]
112: sqlite3 db2 test.db
113: execsql { PRAGMA journal_mode = delete } db2
114: catchsql { INSERT INTO t1 VALUES(5, 6) } db2
115: } {1 {disk I/O error}}
116: do_test journal2-1.6 { file exists test.db-journal } 1
117: do_test journal2-1.7 { execsql { SELECT * FROM t1 } } {1 2 3 4}
118: do_test journal2-1.8 {
119: execsql { PRAGMA journal_mode = truncate } db2
120: execsql { INSERT INTO t1 VALUES(5, 6) } db2
121: } {}
122: do_test journal2-1.9 { execsql { SELECT * FROM t1 } } {1 2 3 4 5 6}
123:
124: # Grow the database until it is reasonably large.
125: #
126: do_test journal2-1.10 {
127: db2 close
128: db func a_string a_string
129: execsql {
130: CREATE TABLE t2(a UNIQUE, b UNIQUE);
131: INSERT INTO t2 VALUES(a_string(200), a_string(300));
132: INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 2
133: INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 4
134: INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 8
135: INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 16
136: INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 32
137: INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2; -- 64
138: }
139: file size test.db-journal
140: } {0}
141: do_test journal2-1.11 {
142: set sz [expr [file size test.db] / 1024]
143: expr {$sz>120 && $sz<200}
144: } 1
145:
146: # Using new connection [db2] (with journal_mode=DELETE), write a lot of
147: # data to the database. So that many pages within the database file are
148: # modified before the transaction is committed.
149: #
150: # Then, enable simulated IO errors in all calls to xDelete, xWrite
151: # and xTruncate before committing the transaction and closing the
152: # database file. From the point of view of other file-system users, it
153: # appears as if the process hosting [db2] unexpectedly exited.
154: #
155: do_test journal2-1.12 {
156: sqlite3 db2 test.db
157: execsql {
158: PRAGMA cache_size = 10;
159: BEGIN;
160: INSERT INTO t2 SELECT randomblob(200), randomblob(300) FROM t2; -- 128
161: } db2
162: } {}
163: do_test journal2-1.13 {
164: tvfs filter {xOpen xClose xDelete xWrite xTruncate}
165: set ::tvfs_error_on_write 1
166: catchsql { COMMIT } db2
167: } {1 {disk I/O error}}
168: db2 close
169: unset ::tvfs_error_on_write
170: forcecopy test.db testX.db
171:
172: do_test journal2-1.14 { file exists test.db-journal } 1
173: do_test journal2-1.15 {
174: execsql {
175: SELECT count(*) FROM t2;
176: PRAGMA integrity_check;
177: }
178: } {64 ok}
179:
180: # This block checks that in the test case above, connection [db2] really
181: # did begin writing to the database file before it hit IO errors. If
182: # this is true, then the copy of the database file made before [db]
183: # rolled back the hot journal should fail the integrity-check.
184: #
185: do_test journal2-1.16 {
186: set sz [expr [file size testX.db] / 1024]
187: expr {$sz>240 && $sz<400}
188: } 1
189: do_test journal2-1.17 {
190: expr {[catchsql { PRAGMA integrity_check } db] == "0 ok"}
191: } {1}
192: do_test journal2-1.20 {
193: sqlite3 db2 testX.db
194: expr {[catchsql { PRAGMA integrity_check } db2] == "0 ok"}
195: } {0}
196: do_test journal2-1.21 {
197: db2 close
198: } {}
199: db close
200:
201: #-------------------------------------------------------------------------
202: # Test that it is possible to switch from journal_mode=truncate to
203: # journal_mode=WAL on a SAFE_DELETE file-system. SQLite should close and
204: # delete the journal file when committing the transaction that switches
205: # the system to WAL mode.
206: #
207: ifcapable wal {
208: do_test journal2-2.1 {
209: faultsim_delete_and_reopen
210: set ::oplog [list]
211: execsql { PRAGMA journal_mode = persist }
212: set ::oplog
213: } {}
214: do_test journal2-2.2 {
215: execsql {
216: CREATE TABLE t1(x);
217: INSERT INTO t1 VALUES(3.14159);
218: }
219: set ::oplog
220: } {xOpen test.db-journal}
221: do_test journal2-2.3 {
222: expr {[file size test.db-journal] > 512}
223: } {1}
224: do_test journal2-2.4 {
225: set ::oplog [list]
226: execsql { PRAGMA journal_mode = WAL }
227: set ::oplog
228: } {xClose test.db-journal xDelete test.db-journal}
229: db close
230: }
231:
232: tvfs delete
233: finish_test
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>