summaryrefslogtreecommitdiff
path: root/test/journal2.test
blob: 25ce941696eeaa7b2ab44c5180bd6bf5e7736152 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# 2010 June 16
#
# 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. Specifically,
# it tests SQLite when using a VFS that claims the SAFE_DELETE property.
#

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

if {[permutation] == "inmemory_journal"} {
  finish_test
  return
}

set a_string_counter 1
proc a_string {n} {
  global a_string_counter
  incr a_string_counter
  string range [string repeat "${a_string_counter}." $n] 1 $n
}

# Create a [testvfs] and install it as the default VFS. Set the device
# characteristics flags to "SAFE_DELETE".
#
testvfs tvfs -default 1
tvfs devchar undeletable_when_open

# Set up a hook so that each time a journal file is opened, closed or
# deleted, the method name ("xOpen", "xClose" or "xDelete") and the final
# segment of the journal file-name (i.e. "test.db-journal") are appended to
# global list variable $::oplog.
#
tvfs filter {xOpen xClose xDelete}
tvfs script journal_op_catcher
proc journal_op_catcher {method filename args} {

  # If global variable ::tvfs_error_on_write is defined, then return an
  # IO error to every attempt to modify the file-system. Otherwise, return
  # SQLITE_OK.
  #
  if {[info exists ::tvfs_error_on_write]} {
    if {[lsearch {xDelete xWrite xTruncate} $method]>=0} {
      return SQLITE_IOERR 
    }
  }

  # The rest of this command only deals with xOpen(), xClose() and xDelete()
  # operations on journal files. If this invocation does not represent such
  # an operation, return with no further ado.
  #
  set f [file tail $filename]
  if {[string match *journal $f]==0} return
  if {[lsearch {xOpen xDelete xClose} $method]<0} return

  # Append a record of this operation to global list variable $::oplog.
  #
  lappend ::oplog $method $f

  # If this is an attempt to delete a journal file for which there exists
  # one ore more open handles, return an error. The code in test_vfs.c
  # will not invoke the xDelete method of the "real" VFS in this case.
  #
  if {[info exists ::open_journals($f)]==0} { set ::open_journals($f) 0 }
  switch -- $method {
    xOpen   { incr ::open_journals($f) +1 }
    xClose  { incr ::open_journals($f) -1 }
    xDelete { if {$::open_journals($f)>0} { return SQLITE_IOERR } }
  }

  return ""
}


do_test journal2-1.1 {
  set ::oplog [list]
  sqlite3 db test.db
  execsql { CREATE TABLE t1(a, b) }
  set ::oplog
} {xOpen test.db-journal xClose test.db-journal xDelete test.db-journal}
do_test journal2-1.2 {
  set ::oplog [list]
  execsql { 
    PRAGMA journal_mode = truncate;
    INSERT INTO t1 VALUES(1, 2);
  }
  set ::oplog
} {xOpen test.db-journal}
do_test journal2-1.3 {
  set ::oplog [list]
  execsql { INSERT INTO t1 VALUES(3, 4) }
  set ::oplog
} {}
do_test journal2-1.4 { execsql { SELECT * FROM t1 } } {1 2 3 4}

# Add a second connection. This connection attempts to commit data in
# journal_mode=DELETE mode. When it tries to delete the journal file,
# the VFS layer returns an IO error.
#
do_test journal2-1.5 {
  set ::oplog [list]
  sqlite3 db2 test.db
  execsql  { PRAGMA journal_mode = delete } db2
  catchsql { INSERT INTO t1 VALUES(5, 6)  } db2
} {1 {disk I/O error}}
do_test journal2-1.6 { file exists test.db-journal } 1
do_test journal2-1.7 { execsql { SELECT * FROM t1 } } {1 2 3 4}
do_test journal2-1.8 {
  execsql { PRAGMA journal_mode = truncate } db2
  execsql { INSERT INTO t1 VALUES(5, 6)  } db2
} {}
do_test journal2-1.9 { execsql { SELECT * FROM t1 } } {1 2 3 4 5 6}

# Grow the database until it is reasonably large.
#
do_test journal2-1.10 {
  db2 close
  db func a_string a_string
  execsql {
    CREATE TABLE t2(a UNIQUE, b UNIQUE);
    INSERT INTO t2 VALUES(a_string(200), a_string(300));
    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  --  2
    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  --  4
    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  --  8
    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  -- 16
    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  -- 32
    INSERT INTO t2 SELECT a_string(200), a_string(300) FROM t2;  -- 64
  }
  file size test.db-journal
} {0}
do_test journal2-1.11 {
  set sz [expr [file size test.db] / 1024]
  expr {$sz>120 && $sz<200}
} 1

# Using new connection [db2] (with journal_mode=DELETE), write a lot of
# data to the database. So that many pages within the database file are
# modified before the transaction is committed.
#
# Then, enable simulated IO errors in all calls to xDelete, xWrite
# and xTruncate before committing the transaction and closing the 
# database file. From the point of view of other file-system users, it
# appears as if the process hosting [db2] unexpectedly exited.
# 
do_test journal2-1.12 {
  sqlite3 db2 test.db
  execsql {
    PRAGMA cache_size = 10;
    BEGIN;
      INSERT INTO t2 SELECT randomblob(200), randomblob(300) FROM t2;  -- 128
  } db2
} {}
do_test journal2-1.13 {
  tvfs filter {xOpen xClose xDelete xWrite xTruncate}
  set ::tvfs_error_on_write 1
  catchsql { COMMIT } db2
} {1 {disk I/O error}}
db2 close
unset ::tvfs_error_on_write
forcecopy test.db testX.db

do_test journal2-1.14 { file exists test.db-journal } 1
do_test journal2-1.15 {
  execsql {
    SELECT count(*) FROM t2;
    PRAGMA integrity_check;
  }
} {64 ok}

# This block checks that in the test case above, connection [db2] really
# did begin writing to the database file before it hit IO errors. If
# this is true, then the copy of the database file made before [db]
# rolled back the hot journal should fail the integrity-check.
#
do_test journal2-1.16 {
  set sz [expr [file size testX.db] / 1024]
  expr {$sz>240 && $sz<400}
} 1
do_test journal2-1.17 {
  expr {[catchsql { PRAGMA integrity_check } db] == "0 ok"}
} {1}
do_test journal2-1.20 {
  sqlite3 db2 testX.db
  expr {[catchsql { PRAGMA integrity_check } db2] == "0 ok"}
} {0}
do_test journal2-1.21 {
  db2 close
} {}
db close

#-------------------------------------------------------------------------
# Test that it is possible to switch from journal_mode=truncate to
# journal_mode=WAL on a SAFE_DELETE file-system. SQLite should close and
# delete the journal file when committing the transaction that switches
# the system to WAL mode.
#
ifcapable wal {
  do_test journal2-2.1 {
    faultsim_delete_and_reopen
    set ::oplog [list]
    execsql { PRAGMA journal_mode = persist }
    set ::oplog
  } {}
  do_test journal2-2.2 {
    execsql { 
      CREATE TABLE t1(x);
      INSERT INTO t1 VALUES(3.14159);
    }
    set ::oplog
  } {xOpen test.db-journal}
  do_test journal2-2.3 {
    expr {[file size test.db-journal] > 512}
  } {1}
  do_test journal2-2.4 {
    set ::oplog [list]
    execsql { PRAGMA journal_mode = WAL }
    set ::oplog
  } {xClose test.db-journal xDelete test.db-journal}
  db close
}

tvfs delete
finish_test