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 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
|
# 2009 January 8
#
# 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 test verifies a couple of specific potential data corruption
# scenarios involving crashes or power failures.
#
# Later: Also, some other specific scenarios required for coverage
# testing that do not lead to corruption.
#
# $Id: crash8.test,v 1.4 2009/01/11 00:44:48 drh Exp $
set testdir [file dirname $argv0]
source $testdir/tester.tcl
ifcapable !crashtest {
finish_test
return
}
do_not_use_codec
do_test crash8-1.1 {
execsql {
PRAGMA auto_vacuum=OFF;
CREATE TABLE t1(a, b);
CREATE INDEX i1 ON t1(a, b);
INSERT INTO t1 VALUES(1, randstr(1000,1000));
INSERT INTO t1 VALUES(2, randstr(1000,1000));
INSERT INTO t1 VALUES(3, randstr(1000,1000));
INSERT INTO t1 VALUES(4, randstr(1000,1000));
INSERT INTO t1 VALUES(5, randstr(1000,1000));
INSERT INTO t1 VALUES(6, randstr(1000,1000));
CREATE TABLE t2(a, b);
CREATE TABLE t3(a, b);
CREATE TABLE t4(a, b);
CREATE TABLE t5(a, b);
CREATE TABLE t6(a, b);
CREATE TABLE t7(a, b);
CREATE TABLE t8(a, b);
CREATE TABLE t9(a, b);
CREATE TABLE t10(a, b);
PRAGMA integrity_check
}
} {ok}
# Potential corruption scenario 1. A second process opens the database
# and modifies a large portion of it. It then opens a second transaction
# and modifies a small part of the database, but crashes before it commits
# the transaction.
#
# When the first process accessed the database again, it was rolling back
# the aborted transaction, but was not purging its in-memory cache (which
# was loaded before the second process made its first, successful,
# modification). Producing an inconsistent cache.
#
do_test crash8-1.2 {
crashsql -delay 2 -file test.db {
PRAGMA cache_size = 10;
UPDATE t1 SET b = randstr(1000,1000);
INSERT INTO t9 VALUES(1, 2);
}
} {1 {child process exited abnormally}}
do_test crash8-1.3 {
execsql {PRAGMA integrity_check}
} {ok}
# Potential corruption scenario 2. The second process, operating in
# persistent-journal mode, makes a large change to the database file
# with a small in-memory cache. Such that more than one journal-header
# was written to the file. It then opens a second transaction and makes
# a smaller change that requires only a single journal-header to be
# written to the journal file. The second change is such that the
# journal content written to the persistent journal file exactly overwrites
# the first journal-header and set of subsequent records written by the
# first, successful, change. The second process crashes before it can
# commit its second change.
#
# When the first process accessed the database again, it was rolling back
# the second aborted transaction, then continuing to rollback the second
# and subsequent journal-headers written by the first, successful, change.
# Database corruption.
#
do_test crash8.2.1 {
crashsql -delay 2 -file test.db {
PRAGMA journal_mode = persist;
PRAGMA cache_size = 10;
UPDATE t1 SET b = randstr(1000,1000);
PRAGMA cache_size = 100;
BEGIN;
INSERT INTO t2 VALUES('a', 'b');
INSERT INTO t3 VALUES('a', 'b');
INSERT INTO t4 VALUES('a', 'b');
INSERT INTO t5 VALUES('a', 'b');
INSERT INTO t6 VALUES('a', 'b');
INSERT INTO t7 VALUES('a', 'b');
INSERT INTO t8 VALUES('a', 'b');
INSERT INTO t9 VALUES('a', 'b');
INSERT INTO t10 VALUES('a', 'b');
COMMIT;
}
} {1 {child process exited abnormally}}
do_test crash8-2.3 {
execsql {PRAGMA integrity_check}
} {ok}
proc read_file {zFile} {
set fd [open $zFile]
fconfigure $fd -translation binary
set zData [read $fd]
close $fd
return $zData
}
proc write_file {zFile zData} {
set fd [open $zFile w]
fconfigure $fd -translation binary
puts -nonewline $fd $zData
close $fd
}
# The following tests check that SQLite will not roll back a hot-journal
# file if the sector-size field in the first journal file header is
# suspect. Definition of suspect:
#
# a) Not a power of 2, or (crash8-3.5)
# b) Greater than 0x01000000 (16MB), or (crash8-3.6)
# c) Less than 512. (crash8-3.7)
#
# Also test that SQLite will not rollback a hot-journal file with a
# suspect page-size. In this case "suspect" means:
#
# a) Not a power of 2, or
# b) Less than 512, or
# c) Greater than SQLITE_MAX_PAGE_SIZE
#
if {[atomic_batch_write test.db]==0} {
do_test crash8-3.1 {
list [file exists test.db-joural] [file exists test.db]
} {0 1}
do_test crash8-3.2 {
execsql {
PRAGMA synchronous = off;
BEGIN;
DELETE FROM t1;
SELECT count(*) FROM t1;
}
} {0}
do_test crash8-3.3 {
set zJournal [read_file test.db-journal]
execsql {
COMMIT;
SELECT count(*) FROM t1;
}
} {0}
do_test crash8-3.4 {
binary scan [string range $zJournal 20 23] I nSector
set nSector
} {512}
do_test crash8-3.5 {
set zJournal2 [string replace $zJournal 20 23 [binary format I 513]]
write_file test.db-journal $zJournal2
execsql {
SELECT count(*) FROM t1;
PRAGMA integrity_check
}
} {0 ok}
do_test crash8-3.6 {
set zJournal2 [string replace $zJournal 20 23 [binary format I 0x2000000]]
write_file test.db-journal $zJournal2
execsql {
SELECT count(*) FROM t1;
PRAGMA integrity_check
}
} {0 ok}
do_test crash8-3.7 {
set zJournal2 [string replace $zJournal 20 23 [binary format I 256]]
write_file test.db-journal $zJournal2
execsql {
SELECT count(*) FROM t1;
PRAGMA integrity_check
}
} {0 ok}
do_test crash8-3.8 {
set zJournal2 [string replace $zJournal 24 27 [binary format I 513]]
write_file test.db-journal $zJournal2
execsql {
SELECT count(*) FROM t1;
PRAGMA integrity_check
}
} {0 ok}
do_test crash8-3.9 {
set big [expr $SQLITE_MAX_PAGE_SIZE * 2]
set zJournal2 [string replace $zJournal 24 27 [binary format I $big]]
write_file test.db-journal $zJournal2
execsql {
SELECT count(*) FROM t1;
PRAGMA integrity_check
}
} {0 ok}
do_test crash8-3.10 {
set zJournal2 [string replace $zJournal 24 27 [binary format I 256]]
write_file test.db-journal $zJournal2
execsql {
SELECT count(*) FROM t1;
PRAGMA integrity_check
}
} {0 ok}
do_test crash8-3.11 {
set fd [open test.db-journal w]
fconfigure $fd -translation binary
puts -nonewline $fd $zJournal
close $fd
execsql {
SELECT count(*) FROM t1;
PRAGMA integrity_check
}
} {6 ok}
}
# If a connection running in persistent-journal mode is part of a
# multi-file transaction, it must ensure that the master-journal name
# appended to the journal file contents during the commit is located
# at the end of the physical journal file. If there was already a
# large journal file allocated at the start of the transaction, this
# may mean truncating the file so that the master journal name really
# is at the physical end of the file.
#
# This block of tests test that SQLite correctly truncates such
# journal files, and that the results behave correctly if a hot-journal
# rollback occurs.
#
ifcapable pragma {
reset_db
forcedelete test2.db
do_test crash8-4.1 {
execsql {
PRAGMA journal_mode = persist;
CREATE TABLE ab(a, b);
INSERT INTO ab VALUES(0, 'abc');
INSERT INTO ab VALUES(1, NULL);
INSERT INTO ab VALUES(2, NULL);
INSERT INTO ab VALUES(3, NULL);
INSERT INTO ab VALUES(4, NULL);
INSERT INTO ab VALUES(5, NULL);
INSERT INTO ab VALUES(6, NULL);
UPDATE ab SET b = randstr(1000,1000);
ATTACH 'test2.db' AS aux;
PRAGMA aux.journal_mode = persist;
CREATE TABLE aux.ab(a, b);
INSERT INTO aux.ab SELECT * FROM main.ab;
UPDATE aux.ab SET b = randstr(1000,1000) WHERE a>=1;
UPDATE ab SET b = randstr(1000,1000) WHERE a>=1;
}
} {persist persist}
if {[atomic_batch_write test.db]==0} {
do_test crash8.4.1.1 {
list [file exists test.db-journal] [file exists test2.db-journal]
} {1 1}
}
do_test crash8-4.2 {
execsql {
BEGIN;
UPDATE aux.ab SET b = 'def' WHERE a = 0;
UPDATE main.ab SET b = 'def' WHERE a = 0;
COMMIT;
}
} {}
do_test crash8-4.3 {
execsql {
UPDATE aux.ab SET b = randstr(1000,1000) WHERE a>=1;
UPDATE ab SET b = randstr(1000,1000) WHERE a>=1;
}
} {}
set contents_main [db eval {SELECT b FROM main.ab WHERE a = 1}]
set contents_aux [db eval {SELECT b FROM aux.ab WHERE a = 1}]
do_test crash8-4.4 {
crashsql -file test2.db -delay 1 {
ATTACH 'test2.db' AS aux;
BEGIN;
UPDATE aux.ab SET b = 'ghi' WHERE a = 0;
UPDATE main.ab SET b = 'ghi' WHERE a = 0;
COMMIT;
}
} {1 {child process exited abnormally}}
do_test crash8-4.5 {
list [file exists test.db-journal] [file exists test2.db-journal]
} {1 1}
do_test crash8-4.6 {
execsql {
SELECT b FROM main.ab WHERE a = 0;
SELECT b FROM aux.ab WHERE a = 0;
}
} {def def}
do_test crash8-4.7 {
crashsql -file test2.db -delay 1 {
ATTACH 'test2.db' AS aux;
BEGIN;
UPDATE aux.ab SET b = 'jkl' WHERE a = 0;
UPDATE main.ab SET b = 'jkl' WHERE a = 0;
COMMIT;
}
} {1 {child process exited abnormally}}
do_test crash8-4.8 {
set fd [open test.db-journal]
fconfigure $fd -translation binary
seek $fd -16 end
binary scan [read $fd 4] I len
seek $fd [expr {-1 * ($len + 16)}] end
set zMasterJournal [read $fd $len]
close $fd
file exists $zMasterJournal
} {1}
do_test crash8-4.9 {
execsql { SELECT b FROM aux.ab WHERE a = 0 }
} {def}
do_test crash8-4.10 {
delete_file $zMasterJournal
execsql { SELECT b FROM main.ab WHERE a = 0 }
} {jkl}
}
#
# Since the following tests (crash8-5.*) rely upon being able
# to copy a file while open, they will not work on Windows.
#
# They also depend on being able to copy the journal file, which
# is not created on F2FS file-systems that support atomic
# write. So do not run these tests in that case either.
#
if {$::tcl_platform(platform)=="unix" && [atomic_batch_write test.db]==0 } {
for {set i 1} {$i < 10} {incr i} {
catch { db close }
forcedelete test.db test.db-journal
sqlite3 db test.db
do_test crash8-5.$i.1 {
execsql {
CREATE TABLE t1(x PRIMARY KEY);
INSERT INTO t1 VALUES(randomblob(900));
INSERT INTO t1 SELECT randomblob(900) FROM t1;
INSERT INTO t1 SELECT randomblob(900) FROM t1;
INSERT INTO t1 SELECT randomblob(900) FROM t1;
INSERT INTO t1 SELECT randomblob(900) FROM t1;
INSERT INTO t1 SELECT randomblob(900) FROM t1;
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 64 rows */
}
crashsql -file test.db -delay [expr ($::i%2) + 1] {
PRAGMA cache_size = 10;
BEGIN;
UPDATE t1 SET x = randomblob(900);
ROLLBACK;
INSERT INTO t1 VALUES(randomblob(900));
}
execsql { PRAGMA integrity_check }
} {ok}
catch { db close }
forcedelete test.db test.db-journal
sqlite3 db test.db
do_test crash8-5.$i.2 {
execsql {
PRAGMA cache_size = 10;
CREATE TABLE t1(x PRIMARY KEY);
INSERT INTO t1 VALUES(randomblob(900));
INSERT INTO t1 SELECT randomblob(900) FROM t1;
INSERT INTO t1 SELECT randomblob(900) FROM t1;
INSERT INTO t1 SELECT randomblob(900) FROM t1;
INSERT INTO t1 SELECT randomblob(900) FROM t1;
INSERT INTO t1 SELECT randomblob(900) FROM t1;
INSERT INTO t1 SELECT randomblob(900) FROM t1; /* 64 rows */
BEGIN;
UPDATE t1 SET x = randomblob(900);
}
forcedelete testX.db testX.db-journal testX.db-wal
forcecopy test.db testX.db
forcecopy test.db-journal testX.db-journal
db close
crashsql -file test.db -delay [expr ($::i%2) + 1] {
SELECT * FROM sqlite_master;
INSERT INTO t1 VALUES(randomblob(900));
}
sqlite3 db2 testX.db
execsql { PRAGMA integrity_check } db2
} {ok}
}
catch {db2 close}
}
finish_test
|