-C Add\sextra\stest\scases\sto\spager1.test.
-D 2010-06-16T12:30:11
+C Experimental\schange:\sOn\ssystems\swhere\sit\sis\snot\spossible\sto\sunlink\sa\sfile\swhile\sone\sor\smore\sprocesses\shas\sit\sopen\s(i.e.\snot\sunix),\savoid\sclosing\sthe\sjournal\sfile\seach\stime\sthe\sdatabase\sis\sunlocked\sand\sreopening\sit\sat\sthe\sstart\sof\seach\stransaction.
+D 2010-06-16T19:04:23
F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
F Makefile.in a5cad1f8f3e021356bfcc6c77dc16f6f1952bbc3
F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
F src/os_os2.c 665876d5eec7585226b0a1cf5e18098de2b2da19
F src/os_unix.c ae173c9f6afaa58b2833a1c95c6cd32021755c42
F src/os_win.c dfde7d33c446e89dd9a277c036f2c4cc564b3138
-F src/pager.c 2964185d4356d0dc159b8340e52d2538d32394e5
+F src/pager.c 133cc49da000b2685a104ed9812084c61bd7e40f
F src/pager.h ca1f23c0cf137ac26f8908df2427c8b308361efd
F src/parse.y ace5c7a125d9f2a410e431ee3209034105045f7e
F src/pcache.c 1e9aa2dbc0845b52e1b51cc39753b6d1e041cb07
F src/rowset.c 69afa95a97c524ba6faf3805e717b5b7ae85a697
F src/select.c c03d8a0565febcde8c6a12c5d77d065fddae889b
F src/shell.c fd4ccdb37c3b68de0623eb938a649e0990710714
-F src/sqlite.h.in 46c01e55cea31b91565ae41276c6310ee4032be8
+F src/sqlite.h.in 706e41c4526ed2674fa042ab3b7ba473b20cb141
F src/sqlite3ext.h 69dfb8116af51b84a029cddb3b35062354270c89
F src/sqliteInt.h 242987ebd2366ea36650a09cdab04a9163c62109
F src/sqliteLimit.h 196e2f83c3b444c4548fc1874f52f84fdbda40f3
F src/test_server.c bbba05c144b5fc4b52ff650a4328027b3fa5fcc6
F src/test_tclvar.c f4dc67d5f780707210d6bb0eb6016a431c04c7fa
F src/test_thread.c aa9919c885a1fe53eafc73492f0898ee6c0a0726
-F src/test_vfs.c 001c34e08748a4a02cd1c2d5531c160a007a84d8
+F src/test_vfs.c 687ba8db7830909955896488a66d3c6b655827f0
F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
F src/tokenize.c 25ceb0f0a746ea1d0f9553787f3f0a56853cfaeb
F src/trigger.c 8927588cb9e6d47f933b53bfe74200fbb504100d
F test/join5.test 86675fc2919269aa923c84dd00ee4249b97990fe
F test/join6.test bf82cf3f979e9eade83ad0d056a66c5ed71d1901
F test/journal1.test 36f2d1bb9bf03f790f43fbdb439e44c0657fab19
+F test/journal2.test 0f867f26c7f30bda2b69dc0e6451caa8fed18ef1
F test/jrnlmode.test 76f94d61528c5ff32102a12f8cf34f4cc36f7849
F test/jrnlmode2.test fe79ea1f0375c926b8de0362ddf94f34a64135fd
F test/jrnlmode3.test cfcdb12b90e640a23b92785a002d96c0624c8710
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
-P 6c5c04eea1f0e8d61883ee8675c249fbf895dc01
-R d490808417f08b44a0b162eb48b4aec9
+P ad3209572d0e6afe5c8b52313e334509661045e2
+R 55fd31c025a3067f63c5d2afd30242b8
+T *branch * experimental
+T *sym-experimental *
+T -sym-trunk *
U dan
-Z 861e5ff117287c588eecf635e107e097
+Z 33e1ea27519902b3169c3c25730a3e25
-ad3209572d0e6afe5c8b52313e334509661045e2
\ No newline at end of file
+bede8c8a148fb9be5ffbf38df7fa733e35cc68c3
\ No newline at end of file
u8 tempFile; /* zFilename is a temporary file */
u8 readOnly; /* True for a read-only database */
u8 memDb; /* True to inhibit all file I/O */
+ u8 safeJrnlHandle; /* True if jrnl may be held open with no lock */
/* The following block contains those class members that are dynamically
** modified during normal operations. The other variables in this structure
** Otherwise, another connection with journal_mode=delete might
** delete the file out from under us.
*/
- sqlite3OsClose(pPager->jfd);
+ if( pPager->safeJrnlHandle==0
+ || (pPager->journalMode!=PAGER_JOURNALMODE_TRUNCATE
+ && pPager->journalMode!=PAGER_JOURNALMODE_PERSIST)
+ ){
+ sqlite3OsClose(pPager->jfd);
+ }
+
sqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
releaseAllSavepoints(pPager);
sqlite3BeginBenignMalloc();
pPager->errCode = 0;
pPager->exclusiveMode = 0;
+ pPager->safeJrnlHandle = 0;
#ifndef SQLITE_OMIT_WAL
sqlite3WalClose(pPager->pWal,
(pPager->noSync ? 0 : pPager->sync_flags),
*/
static int hasHotJournal(Pager *pPager, int *pExists){
sqlite3_vfs * const pVfs = pPager->pVfs;
- int rc; /* Return code */
- int exists; /* True if a journal file is present */
+ int rc = SQLITE_OK; /* Return code */
+ int exists = 1; /* True if a journal file is present */
+ int jrnlOpen = !!isOpen(pPager->jfd);
assert( pPager!=0 );
assert( pPager->useJournal );
assert( isOpen(pPager->fd) );
- assert( !isOpen(pPager->jfd) );
assert( pPager->state <= PAGER_SHARED );
+ assert( jrnlOpen==0
+ || sqlite3OsDeviceCharacteristics(pPager->jfd)&SQLITE_IOCAP_SAFE_DELETE
+ );
*pExists = 0;
- rc = sqlite3OsAccess(pVfs, pPager->zJournal, SQLITE_ACCESS_EXISTS, &exists);
+ if( !jrnlOpen ){
+ rc = sqlite3OsAccess(pVfs, pPager->zJournal, SQLITE_ACCESS_EXISTS, &exists);
+ }
if( rc==SQLITE_OK && exists ){
int locked; /* True if some process holds a RESERVED lock */
** If there is, then we consider this journal to be hot. If not,
** it can be ignored.
*/
- int f = SQLITE_OPEN_READONLY|SQLITE_OPEN_MAIN_JOURNAL;
- rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, f, &f);
+ if( !jrnlOpen ){
+ int f = SQLITE_OPEN_READONLY|SQLITE_OPEN_MAIN_JOURNAL;
+ rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, f, &f);
+ }
if( rc==SQLITE_OK ){
u8 first = 0;
rc = sqlite3OsRead(pPager->jfd, (void *)&first, 1, 0);
if( rc==SQLITE_IOERR_SHORT_READ ){
rc = SQLITE_OK;
}
- sqlite3OsClose(pPager->jfd);
+ if( !jrnlOpen ){
+ sqlite3OsClose(pPager->jfd);
+ }
*pExists = (first!=0);
}else if( rc==SQLITE_CANTOPEN ){
/* If we cannot open the rollback journal file in order to see if
);
#else
rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, flags, 0);
+ if( rc==SQLITE_OK ){
+ int iDc = sqlite3OsDeviceCharacteristics(pPager->jfd);
+ pPager->safeJrnlHandle = (iDc&SQLITE_IOCAP_SAFE_DELETE)!=0;
+ }
#endif
}
assert( rc!=SQLITE_OK || isOpen(pPager->jfd) );
#define SQLITE_IOCAP_ATOMIC64K 0x00000100
#define SQLITE_IOCAP_SAFE_APPEND 0x00000200
#define SQLITE_IOCAP_SEQUENTIAL 0x00000400
+#define SQLITE_IOCAP_SAFE_DELETE 0x00000800
/*
** CAPI3REF: File Locking Levels
int iIoerrCnt;
int ioerr;
int nIoerrFail;
+
+ int iDevchar;
+ int iSectorsize;
};
/*
#define TESTVFS_OPEN_MASK 0x00000100
#define TESTVFS_SYNC_MASK 0x00000200
#define TESTVFS_DELETE_MASK 0x00000400
-#define TESTVFS_ALL_MASK 0x000007FF
+#define TESTVFS_CLOSE_MASK 0x00000800
+#define TESTVFS_WRITE_MASK 0x00001000
+#define TESTVFS_TRUNCATE_MASK 0x00002000
+#define TESTVFS_ALL_MASK 0x00003FFF
#define TESTVFS_MAX_PAGES 256
** Close an tvfs-file.
*/
static int tvfsClose(sqlite3_file *pFile){
- TestvfsFile *p = (TestvfsFile *)pFile;
- if( p->pShmId ){
- Tcl_DecrRefCount(p->pShmId);
- p->pShmId = 0;
+ TestvfsFile *pFd = (TestvfsFile *)pFile;
+ Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+
+ if( p->pScript && p->mask&TESTVFS_CLOSE_MASK ){
+ tvfsExecTcl(p, "xClose",
+ Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0
+ );
+ }
+
+ if( pFd->pShmId ){
+ Tcl_DecrRefCount(pFd->pShmId);
+ pFd->pShmId = 0;
}
if( pFile->pMethods ){
ckfree((char *)pFile->pMethods);
}
- return sqlite3OsClose(p->pReal);
+ return sqlite3OsClose(pFd->pReal);
}
/*
int iAmt,
sqlite_int64 iOfst
){
- TestvfsFile *p = (TestvfsFile *)pFile;
- return sqlite3OsWrite(p->pReal, zBuf, iAmt, iOfst);
+ int rc = SQLITE_OK;
+ TestvfsFile *pFd = (TestvfsFile *)pFile;
+ Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+
+ if( p->pScript && p->mask&TESTVFS_WRITE_MASK ){
+ tvfsExecTcl(p, "xWrite",
+ Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0
+ );
+ tvfsResultCode(p, &rc);
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsWrite(pFd->pReal, zBuf, iAmt, iOfst);
+ }
+ return rc;
}
/*
** Truncate an tvfs-file.
*/
static int tvfsTruncate(sqlite3_file *pFile, sqlite_int64 size){
- TestvfsFile *p = (TestvfsFile *)pFile;
- return sqlite3OsTruncate(p->pReal, size);
+ int rc = SQLITE_OK;
+ TestvfsFile *pFd = (TestvfsFile *)pFile;
+ Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+
+ if( p->pScript && p->mask&TESTVFS_TRUNCATE_MASK ){
+ tvfsExecTcl(p, "xTruncate",
+ Tcl_NewStringObj(pFd->zFilename, -1), pFd->pShmId, 0
+ );
+ tvfsResultCode(p, &rc);
+ }
+
+ if( rc==SQLITE_OK ){
+ rc = sqlite3OsTruncate(pFd->pReal, size);
+ }
+ return rc;
}
/*
** Return the sector-size in bytes for an tvfs-file.
*/
static int tvfsSectorSize(sqlite3_file *pFile){
- TestvfsFile *p = (TestvfsFile *)pFile;
- return sqlite3OsSectorSize(p->pReal);
+ TestvfsFile *pFd = (TestvfsFile *)pFile;
+ Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+ if( p->iSectorsize>=0 ){
+ return p->iSectorsize;
+ }
+ return sqlite3OsSectorSize(pFd->pReal);
}
/*
** Return the device characteristic flags supported by an tvfs-file.
*/
static int tvfsDeviceCharacteristics(sqlite3_file *pFile){
- TestvfsFile *p = (TestvfsFile *)pFile;
- return sqlite3OsDeviceCharacteristics(p->pReal);
+ TestvfsFile *pFd = (TestvfsFile *)pFile;
+ Testvfs *p = (Testvfs *)pFd->pVfs->pAppData;
+ if( p->iDevchar>=0 ){
+ return p->iDevchar;
+ }
+ return sqlite3OsDeviceCharacteristics(pFd->pReal);
}
/*
){
Testvfs *p = (Testvfs *)cd;
- static const char *CMD_strs[] = {
- "shm", "delete", "filter", "ioerr", "script", 0
- };
enum DB_enum {
- CMD_SHM, CMD_DELETE, CMD_FILTER, CMD_IOERR, CMD_SCRIPT
+ CMD_SHM, CMD_DELETE, CMD_FILTER, CMD_IOERR, CMD_SCRIPT,
+ CMD_DEVCHAR, CMD_SECTORSIZE
+ };
+ struct TestvfsSubcmd {
+ char *zName;
+ enum DB_enum eCmd;
+ } aSubcmd[] = {
+ { "shm", CMD_SHM },
+ { "delete", CMD_DELETE },
+ { "filter", CMD_FILTER },
+ { "ioerr", CMD_IOERR },
+ { "script", CMD_SCRIPT },
+ { "devchar", CMD_DEVCHAR },
+ { "sectorsize", CMD_SECTORSIZE },
+ { 0, 0 }
};
-
int i;
if( objc<2 ){
Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
return TCL_ERROR;
}
- if( Tcl_GetIndexFromObj(interp, objv[1], CMD_strs, "subcommand", 0, &i) ){
+ if( Tcl_GetIndexFromObjStruct(
+ interp, objv[1], aSubcmd, sizeof(aSubcmd[0]), "subcommand", 0, &i)
+ ){
return TCL_ERROR;
}
Tcl_ResetResult(interp);
- switch( (enum DB_enum)i ){
+ switch( aSubcmd[i].eCmd ){
case CMD_SHM: {
Tcl_Obj *pObj;
int i;
{ "xShmMap", TESTVFS_SHMMAP_MASK },
{ "xSync", TESTVFS_SYNC_MASK },
{ "xDelete", TESTVFS_DELETE_MASK },
+ { "xWrite", TESTVFS_WRITE_MASK },
+ { "xTruncate", TESTVFS_TRUNCATE_MASK },
{ "xOpen", TESTVFS_OPEN_MASK },
+ { "xClose", TESTVFS_CLOSE_MASK },
};
Tcl_Obj **apElem = 0;
int nElem = 0;
Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
break;
}
+
+ case CMD_DEVCHAR: {
+ struct DeviceFlag {
+ char *zName;
+ int iValue;
+ } aFlag[] = {
+ { "default", -1 },
+ { "atomic", SQLITE_IOCAP_ATOMIC },
+ { "atomic512", SQLITE_IOCAP_ATOMIC512 },
+ { "atomic1k", SQLITE_IOCAP_ATOMIC1K },
+ { "atomic2k", SQLITE_IOCAP_ATOMIC2K },
+ { "atomic4k", SQLITE_IOCAP_ATOMIC4K },
+ { "atomic8k", SQLITE_IOCAP_ATOMIC8K },
+ { "atomic16k", SQLITE_IOCAP_ATOMIC16K },
+ { "atomic32k", SQLITE_IOCAP_ATOMIC32K },
+ { "atomic64k", SQLITE_IOCAP_ATOMIC64K },
+ { "sequential", SQLITE_IOCAP_SEQUENTIAL },
+ { "safe_append", SQLITE_IOCAP_SAFE_APPEND },
+ { "safe_delete", SQLITE_IOCAP_SAFE_DELETE },
+ { 0, 0 }
+ };
+ Tcl_Obj *pRet;
+ int iFlag;
+
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?ATTR-LIST?");
+ return TCL_ERROR;
+ }
+ if( objc==3 ){
+ int j;
+ int iNew = 0;
+ Tcl_Obj **flags = 0;
+ int nFlags = 0;
+
+ if( Tcl_ListObjGetElements(interp, objv[2], &nFlags, &flags) ){
+ return TCL_ERROR;
+ }
+
+ for(j=0; j<nFlags; j++){
+ int idx = 0;
+ if( Tcl_GetIndexFromObjStruct(interp, flags[j], aFlag,
+ sizeof(aFlag[0]), "flag", 0, &idx)
+ ){
+ return TCL_ERROR;
+ }
+ if( aFlag[idx].iValue<0 && nFlags>1 ){
+ Tcl_AppendResult(interp, "bad flags: ", Tcl_GetString(objv[2]), 0);
+ return TCL_ERROR;
+ }
+ iNew |= aFlag[idx].iValue;
+ }
+
+ p->iDevchar = iNew;
+ }
+
+ pRet = Tcl_NewObj();
+ for(iFlag=0; iFlag<sizeof(aFlag)/sizeof(aFlag[0]); iFlag++){
+ if( p->iDevchar & aFlag[iFlag].iValue ){
+ Tcl_ListObjAppendElement(
+ interp, pRet, Tcl_NewStringObj(aFlag[iFlag].zName, -1)
+ );
+ }
+ }
+ Tcl_SetObjResult(interp, pRet);
+
+ break;
+ }
+
+ case CMD_SECTORSIZE: {
+ if( objc>3 ){
+ Tcl_WrongNumArgs(interp, 2, objv, "?VALUE?");
+ return TCL_ERROR;
+ }
+ if( objc==3 ){
+ int iNew = 0;
+ if( Tcl_GetIntFromObj(interp, objv[2], &iNew) ){
+ return TCL_ERROR;
+ }
+ p->iSectorsize = iNew;
+ }
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(p->iSectorsize));
+ break;
+ }
}
return TCL_OK;
nByte = sizeof(Testvfs) + strlen(zVfs)+1;
p = (Testvfs *)ckalloc(nByte);
memset(p, 0, nByte);
+ p->iDevchar = -1;
+ p->iSectorsize = -1;
/* Create the new object command before querying SQLite for a default VFS
** to use for 'real' IO operations. This is because creating the new VFS
--- /dev/null
+# 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.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/lock_common.tcl
+source $testdir/malloc_common.tcl
+db close
+
+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 safe_delete
+
+# 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 {$method == "xDelete" || $method == "xWrite" || $method == "xTruncate"} {
+ return SQLITE_IOERR
+ }
+ return SQLITE_OK
+ }
+
+ if {[string match *journal* $filename]==0} return
+
+ set f [file tail $filename]
+ lappend ::oplog $method $f
+
+ 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. Then, from a
+# journal_mode=DELETE connection, attempt to commit a large transaction (one
+# that involves upgrading to an exclusive lock and writing the database
+# before the transaction is committed).
+#
+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
+
+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
+file copy -force 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
+tvfs delete
+finish_test
+