diff options
author | drh <drh@noemail.net> | 2010-06-21 12:47:41 +0000 |
---|---|---|
committer | drh <drh@noemail.net> | 2010-06-21 12:47:41 +0000 |
commit | 24f0f7716a8963454d77e0224c98dc76f1eebfd2 (patch) | |
tree | f76f3b8d9477bb70be62d337bd579479b4d23078 | |
parent | 19515c8da1df2330c7689315dcd70fc02a4b4e28 (diff) | |
parent | e08341c664e41bb084a7a95bfb47b90b13868694 (diff) | |
download | sqlite-24f0f7716a8963454d77e0224c98dc76f1eebfd2.tar.gz sqlite-24f0f7716a8963454d77e0224c98dc76f1eebfd2.zip |
Merge the experimental UNDELETABLE_WHEN_OPEN optimization into the trunk.
FossilOrigin-Name: ee0acef1faffd480fd2136f81fb2b6f6a17b5388
-rw-r--r-- | manifest | 46 | ||||
-rw-r--r-- | manifest.uuid | 2 | ||||
-rw-r--r-- | src/os_unix.c | 6 | ||||
-rw-r--r-- | src/os_win.c | 2 | ||||
-rw-r--r-- | src/pager.c | 38 | ||||
-rw-r--r-- | src/sqlite.h.in | 23 | ||||
-rw-r--r-- | src/test_vfs.c | 263 | ||||
-rw-r--r-- | src/vdbe.c | 11 | ||||
-rw-r--r-- | src/vdbeblob.c | 4 | ||||
-rw-r--r-- | test/journal2.test | 228 | ||||
-rw-r--r-- | test/jrnlmode2.test | 34 | ||||
-rw-r--r-- | test/malloc_common.tcl | 33 | ||||
-rw-r--r-- | test/pager1.test | 448 | ||||
-rw-r--r-- | test/pager2.test | 117 | ||||
-rw-r--r-- | test/pagerfault.test | 197 | ||||
-rw-r--r-- | test/permutations.test | 2 | ||||
-rw-r--r-- | test/tester.tcl | 10 | ||||
-rw-r--r-- | test/walmode.test | 31 |
18 files changed, 1379 insertions, 116 deletions
@@ -1,8 +1,8 @@ -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 -C Fix\san\suninitialized\svariable\sin\sos_unix.c. -D 2010-06-19T23:53:11 +C Merge\sthe\sexperimental\sUNDELETABLE_WHEN_OPEN\soptimization\sinto\sthe\strunk. +D 2010-06-21T12:47:41 F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0 F Makefile.in a5cad1f8f3e021356bfcc6c77dc16f6f1952bbc3 F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654 @@ -157,9 +157,9 @@ F src/os.c 9c4a2f82a50306a33907678ec0187b6ad1486bfe F src/os.h d7775504a51e6e0d40315aa427b3e229ff9ff9ca F src/os_common.h a8f95b81eca8a1ab8593d23e94f8a35f35d4078f F src/os_os2.c 665876d5eec7585226b0a1cf5e18098de2b2da19 -F src/os_unix.c 838e0c0cabb722ac573c8cbd666b47bdf44eb8f9 -F src/os_win.c dfde7d33c446e89dd9a277c036f2c4cc564b3138 -F src/pager.c 4fe451d68950002eb985e6325d666ab54956a37f +F src/os_unix.c 5231a75a3799872b1250bc70c0e6a1a5960bc865 +F src/os_win.c 73608839342de32280cb378d3c2fc85a5dd80bd2 +F src/pager.c 9f138b79b47090c1e31efe3d9ea191cc92981643 F src/pager.h ca1f23c0cf137ac26f8908df2427c8b308361efd F src/parse.y ace5c7a125d9f2a410e431ee3209034105045f7e F src/pcache.c 1e9aa2dbc0845b52e1b51cc39753b6d1e041cb07 @@ -173,7 +173,7 @@ F src/resolve.c ac5f1a713cd1ae77f08b83cc69581e11bf5ae6f9 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 301476d8556cbb1c5d4bc906370b2dafe4d98a44 F src/sqlite3ext.h 69dfb8116af51b84a029cddb3b35062354270c89 F src/sqliteInt.h 242987ebd2366ea36650a09cdab04a9163c62109 F src/sqliteLimit.h 196e2f83c3b444c4548fc1874f52f84fdbda40f3 @@ -212,7 +212,7 @@ F src/test_schema.c 8c06ef9ddb240c7a0fcd31bc221a6a2aade58bf0 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 9ba0bb227f5fa08d7e3533ff21063c5acf13dabb F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 F src/tokenize.c 25ceb0f0a746ea1d0f9553787f3f0a56853cfaeb F src/trigger.c 8927588cb9e6d47f933b53bfe74200fbb504100d @@ -220,12 +220,12 @@ F src/update.c 9859f2056c7739a1db0d9774ccb6c2f0cee6d1de F src/utf.c 1baeeac91707a4df97ccc6141ec0f808278af685 F src/util.c 32aebf04c10e51ad3977a928b7416bed671b620b F src/vacuum.c 241a8386727c1497eba4955933356dfba6ff8c9f -F src/vdbe.c e115585b14d2cc4128cb53a7e42f207750e80f55 +F src/vdbe.c 290d20ed92b560dc0b602ac50b252f9553a2d8e8 F src/vdbe.h 471f6a3dcec4817ca33596fe7f6654d56c0e75f3 F src/vdbeInt.h 19ebc8c2a2e938340051ee65af3f377fb99102d1 F src/vdbeapi.c dc3138f10afbc95ed3c21dd25abb154504b1db9d F src/vdbeaux.c 4e96a5169b988a8697d4a417f902277b4152e43e -F src/vdbeblob.c 5327132a42a91e8b7acfb60b9d2c3b1c5c863e0e +F src/vdbeblob.c 258a6010ba7a82b72b327fb24c55790655689256 F src/vdbemem.c 2a82f455f6ca6f78b59fb312f96054c04ae0ead1 F src/vdbetrace.c 864cef96919323482ebd9986f2132435115e9cc2 F src/vtab.c a0f8a40274e4261696ef57aa806de2776ab72cda @@ -469,8 +469,9 @@ F test/join4.test 1a352e4e267114444c29266ce79e941af5885916 F test/join5.test 86675fc2919269aa923c84dd00ee4249b97990fe F test/join6.test bf82cf3f979e9eade83ad0d056a66c5ed71d1901 F test/journal1.test 36f2d1bb9bf03f790f43fbdb439e44c0657fab19 +F test/journal2.test a08ea6545d987385e7cbb1d4e7dc2eaacd83b00b F test/jrnlmode.test 76f94d61528c5ff32102a12f8cf34f4cc36f7849 -F test/jrnlmode2.test fe79ea1f0375c926b8de0362ddf94f34a64135fd +F test/jrnlmode2.test a19e28de1a6ec898067e46a122f1b71c9323bf00 F test/jrnlmode3.test cfcdb12b90e640a23b92785a002d96c0624c8710 F test/keyword1.test a2400977a2e4fde43bf33754c2929fda34dbca05 F test/lastinsert.test 474d519c68cb79d07ecae56a763aa7f322c72f51 @@ -511,7 +512,7 @@ F test/mallocH.test 79b65aed612c9b3ed2dcdaa727c85895fd1bfbdb F test/mallocI.test e3ea401904d010cb7c1e4b2ee8803f4a9f5b999d F test/mallocJ.test b5d1839da331d96223e5f458856f8ffe1366f62e F test/mallocK.test d79968641d1b70d88f6c01bdb9a7eb4a55582cc9 -F test/malloc_common.tcl fbf369eb2828825c5f319c101917aff91ea87556 +F test/malloc_common.tcl 58caffc4be307b56c5b1438b5eba3eb278bd81f5 F test/manydb.test b3d3bc4c25657e7f68d157f031eb4db7b3df0d3c F test/memdb.test 0825155b2290e900264daaaf0334b6dfe69ea498 F test/memleak.test 10b9c6c57e19fc68c32941495e9ba1c50123f6e2 @@ -536,13 +537,14 @@ F test/notify2.test 195a467e021f74197be2c4fb02d6dee644b8d8db F test/notnull.test cc7c78340328e6112a13c3e311a9ab3127114347 F test/null.test a8b09b8ed87852742343b33441a9240022108993 F test/openv2.test af02ed0a9cbc0d2a61b8f35171d4d117e588e4ec -F test/pager1.test 60dec408563461f9fbf04d4d301b1b4db23f7525 -F test/pagerfault.test 16e560bc4332d5b089b369d82ae4b65b8805b5eb +F test/pager1.test 86d034bf3ffe4e99648714443776440d0555f705 +F test/pager2.test ad062a51030dc1e2749f506528db4cc5bae6474c +F test/pagerfault.test e67e9c18bf7b4bb8cc8d458d3a5ecc980f18a225 F test/pageropt.test 8146bf448cf09e87bb1867c2217b921fb5857806 F test/pagesize.test 76aa9f23ecb0741a4ed9d2e16c5fa82671f28efb F test/pcache.test eebc4420b37cb07733ae9b6e99c9da7c40dd6d58 F test/pcache2.test 0d85f2ab6963aee28c671d4c71bec038c00a1d16 -F test/permutations.test f044eaba204ff13d530ceb72a22b0ed2c43562ef +F test/permutations.test 9296368f1d14d9e042f146a804ca38f551d35435 F test/pragma.test 6960f9efbce476f70ba9ee2171daf5042f9e3d8a F test/pragma2.test 5364893491b9231dd170e3459bfc2e2342658b47 F test/printf.test 05970cde31b1a9f54bd75af60597be75a5c54fea @@ -613,7 +615,7 @@ F test/tclsqlite.test 8c154101e704170c2be10f137a5499ac2c6da8d3 F test/tempdb.test 800c36623d67a2ad1f58784b9c5644e0405af6e6 F test/temptable.test f42121a0d29a62f00f93274464164177ab1cc24a F test/temptrigger.test b0273db072ce5f37cf19140ceb1f0d524bbe9f05 -F test/tester.tcl 7912c3c8768320fd7bcb217637c2f0a607fbbc24 +F test/tester.tcl ab89e8e592ff26e2b65ff3cae9de5f26863ae766 F test/thread001.test a3e6a7254d1cb057836cb3145b60c10bf5b7e60f F test/thread002.test afd20095e6e845b405df4f2c920cb93301ca69db F test/thread003.test b824d4f52b870ae39fc5bae4d8070eca73085dca @@ -780,7 +782,7 @@ F test/walcrash.test f6d5fb2bb108876f04848720a488065d9deef69f F test/walcrash2.test 14585ad1a2c85da2de721caa3b4deeea55213008 F test/walfault.test c2b524299dede269282a0795e11396cc446ca9af F test/walhook.test 67e675127f4acb72f061a12667ce6e5460b06b78 -F test/walmode.test 6ca9d710cc9f6545b913abcded6d6b0b15641048 +F test/walmode.test b54e2f91f34179c65cab02a6916578617a33eef0 F test/walslow.test d21625e2e99e11c032ce949e8a94661576548933 F test/walthread.test a25a393c068a2b42b44333fa3fdaae9072f1617c F test/where.test de337a3fe0a459ec7c93db16a519657a90552330 @@ -826,14 +828,14 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P 2241788bc85fbc48e9cfecb95fe0a858338e37cb -R 519d2c82f8071bfeedb3eae3a50de5e0 +P 822a0283c6bc1c75001f3d1c528a4ff89c6b039c a64d96db09ef2b7651fa4e98d3c7bf3ae5d3fe96 +R aa81338105a8066f92038016780c9df7 U drh -Z fb28c7b4632831ff9010ebcbf771882d +Z e0549d09ee79c82d436d5aa9bbd618a3 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) -iD8DBQFMHVhqoxKgR168RlERAl0KAJ0Yf2pBemdWub4Zh3gc9Vg3H0bRrgCfb73m -7DvKYVz1pMDVAwrfS/3PYDY= -=DvU8 +iD8DBQFMH19xoxKgR168RlERAt6AAJ96RqboVXAAENNG3NTV/9NSo4zbEgCghTxO +GuKlnNnL+XTagpq68iLKUhk= +=R4ik -----END PGP SIGNATURE----- diff --git a/manifest.uuid b/manifest.uuid index 3af1e5905..e41f233b0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -822a0283c6bc1c75001f3d1c528a4ff89c6b039c
\ No newline at end of file +ee0acef1faffd480fd2136f81fb2b6f6a17b5388
\ No newline at end of file diff --git a/src/os_unix.c b/src/os_unix.c index e949c20cd..77aafccce 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -4604,6 +4604,12 @@ static int unixAccess( assert(!"Invalid flags argument"); } *pResOut = (access(zPath, amode)==0); + if( flags==SQLITE_ACCESS_EXISTS && *pResOut ){ + struct stat buf; + if( 0==stat(zPath, &buf) && buf.st_size==0 ){ + *pResOut = 0; + } + } return SQLITE_OK; } diff --git a/src/os_win.c b/src/os_win.c index ec62e394c..24d0e7ca3 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -1151,7 +1151,7 @@ static int winSectorSize(sqlite3_file *id){ */ static int winDeviceCharacteristics(sqlite3_file *id){ UNUSED_PARAMETER(id); - return 0; + return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN; } /**************************************************************************** diff --git a/src/pager.c b/src/pager.c index c875dd9df..a5377d4c3 100644 --- a/src/pager.c +++ b/src/pager.c @@ -1219,12 +1219,24 @@ static int pagerUseWal(Pager *pPager){ static void pager_unlock(Pager *pPager){ if( !pPager->exclusiveMode ){ int rc = SQLITE_OK; /* Return code */ + int iDc = isOpen(pPager->fd)?sqlite3OsDeviceCharacteristics(pPager->fd):0; /* Always close the journal file when dropping the database lock. ** Otherwise, another connection with journal_mode=delete might ** delete the file out from under us. */ - sqlite3OsClose(pPager->jfd); + assert( (PAGER_JOURNALMODE_MEMORY & 5)!=1 ); + assert( (PAGER_JOURNALMODE_OFF & 5)!=1 ); + assert( (PAGER_JOURNALMODE_WAL & 5)!=1 ); + assert( (PAGER_JOURNALMODE_DELETE & 5)!=1 ); + assert( (PAGER_JOURNALMODE_TRUNCATE & 5)==1 ); + assert( (PAGER_JOURNALMODE_PERSIST & 5)==1 ); + if( 0==(iDc & SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN) + || 1!=(pPager->journalMode & 5) + ){ + sqlite3OsClose(pPager->jfd); + } + sqlite3BitvecDestroy(pPager->pInJournal); pPager->pInJournal = 0; releaseAllSavepoints(pPager); @@ -3115,6 +3127,7 @@ int sqlite3PagerClose(Pager *pPager){ enable_simulated_io_errors(); PAGERTRACE(("CLOSE %d\n", PAGERID(pPager))); IOTRACE(("CLOSE %p\n", pPager)) + sqlite3OsClose(pPager->jfd); sqlite3OsClose(pPager->fd); sqlite3PageFree(pTmp); sqlite3PcacheClose(pPager->pPCache); @@ -3908,17 +3921,22 @@ int sqlite3PagerOpen( */ 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_UNDELETABLE_WHEN_OPEN + )); *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 */ @@ -3956,15 +3974,19 @@ static int hasHotJournal(Pager *pPager, int *pExists){ ** 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 diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 28778f7fc..f91f25b3f 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -497,17 +497,18 @@ int sqlite3_exec( ** information is written to disk in the same order as calls ** to xWrite(). */ -#define SQLITE_IOCAP_ATOMIC 0x00000001 -#define SQLITE_IOCAP_ATOMIC512 0x00000002 -#define SQLITE_IOCAP_ATOMIC1K 0x00000004 -#define SQLITE_IOCAP_ATOMIC2K 0x00000008 -#define SQLITE_IOCAP_ATOMIC4K 0x00000010 -#define SQLITE_IOCAP_ATOMIC8K 0x00000020 -#define SQLITE_IOCAP_ATOMIC16K 0x00000040 -#define SQLITE_IOCAP_ATOMIC32K 0x00000080 -#define SQLITE_IOCAP_ATOMIC64K 0x00000100 -#define SQLITE_IOCAP_SAFE_APPEND 0x00000200 -#define SQLITE_IOCAP_SEQUENTIAL 0x00000400 +#define SQLITE_IOCAP_ATOMIC 0x00000001 +#define SQLITE_IOCAP_ATOMIC512 0x00000002 +#define SQLITE_IOCAP_ATOMIC1K 0x00000004 +#define SQLITE_IOCAP_ATOMIC2K 0x00000008 +#define SQLITE_IOCAP_ATOMIC4K 0x00000010 +#define SQLITE_IOCAP_ATOMIC8K 0x00000020 +#define SQLITE_IOCAP_ATOMIC16K 0x00000040 +#define SQLITE_IOCAP_ATOMIC32K 0x00000080 +#define SQLITE_IOCAP_ATOMIC64K 0x00000100 +#define SQLITE_IOCAP_SAFE_APPEND 0x00000200 +#define SQLITE_IOCAP_SEQUENTIAL 0x00000400 +#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 /* ** CAPI3REF: File Locking Levels diff --git a/src/test_vfs.c b/src/test_vfs.c index e94bef7d2..46f3a0630 100644 --- a/src/test_vfs.c +++ b/src/test_vfs.c @@ -58,6 +58,13 @@ struct Testvfs { int iIoerrCnt; int ioerr; int nIoerrFail; + + int iFullCnt; + int fullerr; + int nFullFail; + + int iDevchar; + int iSectorsize; }; /* @@ -77,7 +84,10 @@ struct Testvfs { #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 @@ -187,6 +197,30 @@ static int tvfsResultCode(Testvfs *p, int *pRc){ return 0; } +static int tvfsInjectIoerr(Testvfs *p){ + int ret = 0; + if( p->ioerr ){ + p->iIoerrCnt--; + if( p->iIoerrCnt==0 || (p->iIoerrCnt<0 && p->ioerr==2) ){ + ret = 1; + p->nIoerrFail++; + } + } + return ret; +} + +static int tvfsInjectFullerr(Testvfs *p){ + int ret = 0; + if( p->fullerr ){ + p->iFullCnt--; + if( p->iFullCnt<=0 ){ + ret = 1; + p->nFullFail++; + } + } + return ret; +} + static void tvfsExecTcl( Testvfs *p, @@ -245,15 +279,23 @@ static void tvfsExecTcl( ** 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); } /* @@ -278,16 +320,44 @@ static int tvfsWrite( 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 && tvfsInjectFullerr(p) ) rc = SQLITE_FULL; + + 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; } /* @@ -325,6 +395,8 @@ static int tvfsSync(sqlite3_file *pFile, int flags){ tvfsResultCode(p, &rc); } + if( rc==SQLITE_OK && tvfsInjectFullerr(p) ) rc = SQLITE_FULL; + if( rc==SQLITE_OK ){ rc = sqlite3OsSync(pFd->pReal, flags); } @@ -376,16 +448,24 @@ static int tvfsFileControl(sqlite3_file *pFile, int op, void *pArg){ ** 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); } /* @@ -555,18 +635,6 @@ static int tvfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ return PARENTVFS(pVfs)->xCurrentTime(PARENTVFS(pVfs), pTimeOut); } -static int tvfsInjectIoerr(Testvfs *p){ - int ret = 0; - if( p->ioerr ){ - p->iIoerrCnt--; - if( p->iIoerrCnt==0 || (p->iIoerrCnt<0 && p->ioerr==2) ){ - ret = 1; - p->nIoerrFail++; - } - } - return ret; -} - static int tvfsShmOpen( sqlite3_file *pFileDes ){ @@ -782,25 +850,38 @@ static int testvfs_obj_cmd( ){ 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, CMD_FULLERR + }; + struct TestvfsSubcmd { + char *zName; + enum DB_enum eCmd; + } aSubcmd[] = { + { "shm", CMD_SHM }, + { "delete", CMD_DELETE }, + { "filter", CMD_FILTER }, + { "ioerr", CMD_IOERR }, + { "fullerr", CMD_FULLERR }, + { "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; @@ -857,7 +938,10 @@ static int testvfs_obj_cmd( { "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; @@ -916,6 +1000,34 @@ static int testvfs_obj_cmd( } /* + ** TESTVFS fullerr ?IFAIL? + ** + ** Where IFAIL is an integer. + */ + case CMD_FULLERR: { + int iRet = p->nFullFail; + + p->nFullFail = 0; + p->fullerr = 0; + p->iFullCnt = 0; + + if( objc==3 ){ + int iCnt; + if( TCL_OK!=Tcl_GetIntFromObj(interp, objv[2], &iCnt) ){ + return TCL_ERROR; + } + p->fullerr = (iCnt>0); + p->iFullCnt = iCnt; + }else if( objc!=2 ){ + Tcl_AppendResult(interp, "Bad args", 0); + return TCL_ERROR; + } + + Tcl_SetObjResult(interp, Tcl_NewIntObj(iRet)); + break; + } + + /* ** TESTVFS ioerr ?IFAIL PERSIST? ** ** Where IFAIL is an integer and PERSIST is boolean. @@ -948,6 +1060,89 @@ static int testvfs_obj_cmd( 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 }, + { "undeletable_when_open", SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN }, + { 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; @@ -1067,6 +1262,8 @@ static int testvfs_cmd( 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 diff --git a/src/vdbe.c b/src/vdbe.c index 6803beafd..aec5712bf 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -5248,8 +5248,6 @@ case OP_JournalMode: { /* out2-prerelease */ rc = sqlite3PagerCloseWal(pPager); if( rc==SQLITE_OK ){ sqlite3PagerSetJournalMode(pPager, eNew); - }else if( rc==SQLITE_BUSY && pOp->p5==0 ){ - goto abort_due_to_error; } } @@ -5259,16 +5257,15 @@ case OP_JournalMode: { /* out2-prerelease */ assert( sqlite3BtreeIsInTrans(pBt)==0 ); if( rc==SQLITE_OK ){ rc = sqlite3BtreeSetVersion(pBt, (eNew==PAGER_JOURNALMODE_WAL ? 2 : 1)); - if( rc==SQLITE_BUSY && pOp->p5==0 ) goto abort_due_to_error; - } - if( rc==SQLITE_BUSY ){ - eNew = eOld; - rc = SQLITE_OK; } } } #endif /* ifndef SQLITE_OMIT_WAL */ + if( rc ){ + if( rc==SQLITE_BUSY && pOp->p5!=0 ) rc = SQLITE_OK; + eNew = eOld; + } eNew = sqlite3PagerSetJournalMode(pPager, eNew); pOut = &aMem[pOp->p2]; diff --git a/src/vdbeblob.c b/src/vdbeblob.c index 829b6de6d..b2b9f0ed0 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -191,10 +191,14 @@ int sqlite3_blob_open( sqlite3VdbeUsesBtree(v, iDb); /* Configure the OP_TableLock instruction */ +#ifdef SQLITE_OMIT_SHARED_CACHE + sqlite3VdbeChangeToNoop(v, 2, 1); +#else sqlite3VdbeChangeP1(v, 2, iDb); sqlite3VdbeChangeP2(v, 2, pTab->tnum); sqlite3VdbeChangeP3(v, 2, flags); sqlite3VdbeChangeP4(v, 2, pTab->zName, P4_TRANSIENT); +#endif /* Remove either the OP_OpenWrite or OpenRead. Set the P2 ** parameter of the other to pTab->tnum. */ diff --git a/test/journal2.test b/test/journal2.test new file mode 100644 index 000000000..f7145ebdf --- /dev/null +++ b/test/journal2.test @@ -0,0 +1,228 @@ +# 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 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 +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 + +#------------------------------------------------------------------------- +# 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 + diff --git a/test/jrnlmode2.test b/test/jrnlmode2.test index 9a1fe37c3..dc3bc270b 100644 --- a/test/jrnlmode2.test +++ b/test/jrnlmode2.test @@ -9,7 +9,6 @@ # #*********************************************************************** # -# $Id: jrnlmode2.test,v 1.6 2009/06/05 17:09:12 drh Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -20,10 +19,33 @@ ifcapable {!pager_pragmas} { } #------------------------------------------------------------------------- -# Test overview: +# The tests in this file check that the following two bugs (both now fixed) +# do not reappear. # -# jrnlmode2-1.*: Demonstrate bug #3745 -# jrnlmode2-2.*: Demonstrate bug #3751 +# jrnlmode2-1.*: Demonstrate bug #3745: +# +# In persistent journal mode, if: +# +# * There is a persistent journal in the file-system, AND +# * there exists a connection with a shared lock on the db file, +# +# then a second connection cannot open a read-transaction on the database. +# The reason is because while determining that the persistent-journal is +# not a hot-journal, SQLite currently grabs an exclusive lock on the +# database file. If this fails because another connection has a shared +# lock, then SQLITE_BUSY is returned to the user. +# +# jrnlmode2-2.*: Demonstrate bug #3751: +# +# If a connection is opened in SQLITE_OPEN_READONLY mode, the underlying +# unix file descriptor on the database file is opened in O_RDONLY mode. +# +# When SQLite queries the database file for the schema in order to compile +# the SELECT statement, it sees the empty journal in the file system, it +# attempts to obtain an exclusive lock on the database file (this is a +# bug). The attempt to obtain an exclusive (write) lock on a read-only file +# fails at the OS level. Under unix, fcntl() reports an EBADF - "Bad file +# descriptor" - error. # do_test jrnlmode2-1.1 { @@ -46,6 +68,8 @@ do_test jrnlmode2-1.3 { do_test jrnlmode2-1.4 { execsql { INSERT INTO t1 VALUES(3, 4); + } + execsql { BEGIN; SELECT * FROM t1; } @@ -87,9 +111,9 @@ do_test jrnlmode2-2.4 { } {0 {1 2 3 4 5 6}} do_test jrnlmode2-2.5 { + db close file delete test.db-journal } {} - do_test jrnlmode2-2.6 { sqlite3 db2 test.db -readonly 1 catchsql { SELECT * FROM t1 } db2 diff --git a/test/malloc_common.tcl b/test/malloc_common.tcl index a24716cd4..8202b19af 100644 --- a/test/malloc_common.tcl +++ b/test/malloc_common.tcl @@ -49,6 +49,16 @@ set FAULTSIM(ioerr-persistent) [list \ -injecterrlist {{1 {disk I/O error}}} \ ] +# SQLITE_FULL errors (always persistent): +# +set FAULTSIM(full) [list \ + -injectinstall fullerr_injectinstall \ + -injectstart fullerr_injectstart \ + -injectstop fullerr_injectstop \ + -injecterrlist {{1 {database or disk is full}}} \ + -injectuninstall fullerr_injectuninstall \ +] + # Transient and persistent SHM errors: # set FAULTSIM(shmerr-transient) [list \ @@ -126,14 +136,14 @@ proc faultsim_save_and_close {} { catch { db close } return "" } -proc faultsim_restore_and_reopen {} { +proc faultsim_restore_and_reopen {{dbfile test.db}} { catch { db close } foreach f [glob -nocomplain test.db*] { file delete -force $f } foreach f2 [glob -nocomplain sv_test.db*] { set f [string range $f2 3 end] file copy -force $f2 $f } - sqlite3 db test.db + sqlite3 db $dbfile sqlite3_extended_result_codes db 1 sqlite3_db_config_lookaside db 0 0 0 } @@ -146,7 +156,7 @@ proc faultsim_integrity_check {{db db}} { proc faultsim_delete_and_reopen {{file test.db}} { catch { db close } foreach f [glob -nocomplain test.db*] { file delete -force $f } - sqlite3 db test.db + sqlite3 db $file } @@ -177,6 +187,7 @@ proc ioerr_injectstop {} { return $sv } + # The following procs are used as [do_one_faultsim_test] callbacks when # injecting shared-memory related error faults into test cases. # @@ -195,6 +206,22 @@ proc shmerr_injectstop {} { shmfault ioerr 0 0 } +proc fullerr_injectinstall {} { + testvfs shmfault -default true +} +proc fullerr_injectuninstall {} { + catch {db close} + catch {db2 close} + shmfault delete +} +proc fullerr_injectstart {iFail} { + shmfault full $iFail +} +proc fullerr_injectstop {} { + shmfault full 0 +} + + # This command is not called directly. It is used by the # [faultsim_test_result] command created by [do_faultsim_test] and used # by -test scripts. diff --git a/test/pager1.test b/test/pager1.test index d0caaa9d4..f7a821b6d 100644 --- a/test/pager1.test +++ b/test/pager1.test @@ -14,6 +14,7 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl source $testdir/lock_common.tcl source $testdir/malloc_common.tcl +source $testdir/wal_common.tcl # # pager1-1.*: Test inter-process locking (clients in multiple processes). @@ -22,13 +23,14 @@ source $testdir/malloc_common.tcl # # pager1-3.*: Savepoint related tests. # - -proc do_execsql_test {testname sql result} { - uplevel do_test $testname [list "execsql {$sql}"] [list $result] -} -proc do_catchsql_test {testname sql result} { - uplevel do_test $testname [list "catchsql {$sql}"] [list $result] -} +# pager1-4.*: Hot-journal related tests. +# +# pager1-5.*: Cases related to multi-file commits. +# +# pager1-6.*: Cases related to "PRAGMA max_page_count" +# +# pager1-7.*: Cases specific to "PRAGMA journal_mode=TRUNCATE" +# set a_string_counter 1 proc a_string {n} { @@ -221,6 +223,17 @@ do_execsql_test pager1-3.6 { COMMIT } {} # compute) the associated journal is rolled back (and no # xAccess() call to check for the presence of any master # journal file is made). +# +# pager1.4.3.*: Test that the contents of a hot-journal are ignored if the +# page-size or sector-size in the journal header appear to +# be invalid (too large, too small or not a power of 2). +# +# pager1.4.4.*: Test hot-journal rollback of journal file with a master +# journal pointer generated in various "PRAGMA synchronous" +# modes. +# +# pager1.4.5.*: Test that hot-journal rollback stops if it encounters a +# journal-record for which the checksum fails. # do_test pager1.4.1.1 { faultsim_delete_and_reopen @@ -308,5 +321,426 @@ do_test pager1.4.2.5 { } } {4 ok} +do_test pager1.4.3.1 { + testvfs tstvfs -default 1 + tstvfs filter xSync + tstvfs script xSyncCallback + proc xSyncCallback {method file args} { + set file [file tail $file] + if { 0==[string match *journal $file] } { faultsim_save } + } + faultsim_delete_and_reopen + execsql { + PRAGMA journal_mode = DELETE; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(3, 4); + } + db close + tstvfs delete +} {} + +foreach {tn ofst value result} { + 2 20 31 {1 2 3 4} + 3 20 32 {1 2 3 4} + 4 20 33 {1 2 3 4} + 5 20 65536 {1 2 3 4} + 6 20 131072 {1 2 3 4} + + 7 24 511 {1 2 3 4} + 8 24 513 {1 2 3 4} + 9 24 65536 {1 2 3 4} + + 10 32 65536 {1 2} +} { + do_test pager1.4.3.$tn { + faultsim_restore_and_reopen + hexio_write test.db-journal $ofst [format %.8x $value] + execsql { SELECT * FROM t1 } + } $result +} +db close + +# Set up a VFS that snapshots the file-system just before a master journal +# file is deleted to commit a multi-file transaction. Specifically, the +# file-system is saved just before the xDelete() call to remove the +# master journal file from the file-system. +# +testvfs tv -default 1 +tv script copy_on_mj_delete +set ::mj_filename_length 0 +proc copy_on_mj_delete {method filename args} { + if {[string match *mj* [file tail $filename]]} { + set ::mj_filename_length [string length $filename] + faultsim_save + } + return SQLITE_OK +} + +set pwd [pwd] +foreach {tn1 tcl} { + 1 { set prefix "test.db" } + 2 { + # This test depends on the underlying VFS being able to open paths + # 512 bytes in length. The idea is to create a hot-journal file that + # contains a master-journal pointer so large that it could contain + # a valid page record (if the file page-size is 512 bytes). So as to + # make sure SQLite doesn't get confused by this. + # + set nPadding [expr 511 - $::mj_filename_length] + + # We cannot just create a really long database file name to open, as + # Linux limits a single component of a path to 255 bytes by default + # (and presumably other systems have limits too). So create a directory + # hierarchy to work in. + # + set dirname "d123456789012345678901234567890/" + set nDir [expr $nPadding / 32] + if { $nDir } { + set p [string repeat $dirname $nDir] + file mkdir $p + cd $p + } + + set padding [string repeat x [expr $nPadding %32]] + set prefix "test.db${padding}" + } +} { + eval $tcl + foreach {tn2 sql} { + o { + PRAGMA main.synchronous=OFF; + PRAGMA aux.synchronous=OFF; + } + o512 { + PRAGMA main.synchronous=OFF; + PRAGMA aux.synchronous=OFF; + PRAGMA main.page_size = 512; + PRAGMA aux.page_size = 512; + } + n { + PRAGMA main.synchronous=NORMAL; + PRAGMA aux.synchronous=NORMAL; + } + f { + PRAGMA main.synchronous=FULL; + PRAGMA aux.synchronous=FULL; + } + } { + + set tn "${tn1}.${tn2}" + + # Set up a connection to have two databases, test.db (main) and + # test.db2 (aux). Then run a multi-file transaction on them. The + # VFS will snapshot the file-system just before the master-journal + # file is deleted to commit the transaction. + # + tv filter xDelete + do_test pager1-4.4.$tn.1 { + faultsim_delete_and_reopen $prefix + execsql " + ATTACH '${prefix}2' AS aux; + $sql + CREATE TABLE a(x); + CREATE TABLE aux.b(x); + INSERT INTO a VALUES('double-you'); + INSERT INTO a VALUES('why'); + INSERT INTO a VALUES('zed'); + INSERT INTO b VALUES('won'); + INSERT INTO b VALUES('too'); + INSERT INTO b VALUES('free'); + " + execsql { + BEGIN; + INSERT INTO a SELECT * FROM b WHERE rowid<=3; + INSERT INTO b SELECT * FROM a WHERE rowid<=3; + COMMIT; + } + } {} + tv filter {} + + # Check that the transaction was committed successfully. + # + do_execsql_test pager1-4.4.$tn.2 { + SELECT * FROM a + } {double-you why zed won too free} + do_execsql_test pager1-4.4.$tn.3 { + SELECT * FROM b + } {won too free double-you why zed} + + # Restore the file-system and reopen the databases. Check that it now + # appears that the transaction was not committed (because the file-system + # was restored to the state where it had not been). + # + do_test pager1-4.4.$tn.4 { + faultsim_restore_and_reopen $prefix + execsql "ATTACH '${prefix}2' AS aux" + } {} + do_execsql_test pager1-4.4.$tn.5 {SELECT * FROM a} {double-you why zed} + do_execsql_test pager1-4.4.$tn.6 {SELECT * FROM b} {won too free} + + # Restore the file-system again. This time, before reopening the databases, + # delete the master-journal file from the file-system. It now appears that + # the transaction was committed (no master-journal file == no rollback). + # + do_test pager1-4.4.$tn.7 { + faultsim_restore_and_reopen $prefix + foreach f [glob ${prefix}-mj*] { file delete -force $f } + execsql "ATTACH '${prefix}2' AS aux" + } {} + do_execsql_test pager1-4.4.$tn.8 { + SELECT * FROM a + } {double-you why zed won too free} + do_execsql_test pager1-4.4.$tn.9 { + SELECT * FROM b + } {won too free double-you why zed} + } + + cd $pwd +} +db close +tv delete + +#------------------------------------------------------------------------- +# The following tests deal with multi-file commits. +# +# pager1-5.1.*: The case where a multi-file cannot be committed because +# another connection is holding a SHARED lock on one of the +# files. After the SHARED lock is removed, the COMMIT succeeds. +# +# pager1-5.2.*: Multi-file commits with journal_mode=memory. +# +# pager1-5.3.*: Multi-file commits with journal_mode=memory. +# +# pager1-5.4.*: Check that with synchronous=normal, the master-journal file +# name is added to a journal file immediately after the last +# journal record. But with synchronous=full, extra unused space +# is allocated between the last journal record and the +# master-journal file name so that the master-journal file +# name does not lie on the same sector as the last journal file +# record. +# +# pager1-5.5.*: Check that in journal_mode=PERSIST mode, a journal file is +# truncated to zero bytes when a multi-file transaction is +# committed (instead of the first couple of bytes being zeroed). +# +# +do_test pager1-5.1.1 { + faultsim_delete_and_reopen + execsql { + ATTACH 'test.db2' AS aux; + CREATE TABLE t1(a, b); + CREATE TABLE aux.t2(a, b); + INSERT INTO t1 VALUES(17, 'Lenin'); + INSERT INTO t1 VALUES(22, 'Stalin'); + INSERT INTO t1 VALUES(53, 'Khrushchev'); + } +} {} +do_test pager1-5.1.2 { + execsql { + BEGIN; + INSERT INTO t1 VALUES(64, 'Brezhnev'); + INSERT INTO t2 SELECT * FROM t1; + } + sqlite3 db2 test.db2 + execsql { + BEGIN; + SELECT * FROM t2; + } db2 +} {} +do_test pager1-5.1.3 { + catchsql COMMIT +} {1 {database is locked}} +do_test pager1-5.1.4 { + execsql COMMIT db2 + execsql COMMIT + execsql { SELECT * FROM t2 } db2 +} {17 Lenin 22 Stalin 53 Khrushchev 64 Brezhnev} +do_test pager1-5.1.5 { + db2 close +} {} + +do_test pager1-5.2.1 { + execsql { + PRAGMA journal_mode = memory; + BEGIN; + INSERT INTO t1 VALUES(84, 'Andropov'); + INSERT INTO t2 VALUES(84, 'Andropov'); + COMMIT; + } +} {memory} +do_test pager1-5.3.1 { + execsql { + PRAGMA journal_mode = off; + BEGIN; + INSERT INTO t1 VALUES(85, 'Gorbachev'); + INSERT INTO t2 VALUES(85, 'Gorbachev'); + COMMIT; + } +} {off} + +do_test pager1-5.4.1 { + db close + testvfs tv + sqlite3 db test.db -vfs tv + execsql { ATTACH 'test.db2' AS aux } + + tv filter xDelete + tv script max_journal_size + tv sectorsize 512 + set ::max_journal 0 + proc max_journal_size {method args} { + set sz 0 + catch { set sz [file size test.db-journal] } + if {$sz > $::max_journal} { + set ::max_journal $sz + } + return SQLITE_OK + } + execsql { + PRAGMA journal_mode = DELETE; + PRAGMA synchronous = NORMAL; + BEGIN; + INSERT INTO t1 VALUES(85, 'Gorbachev'); + INSERT INTO t2 VALUES(85, 'Gorbachev'); + COMMIT; + } + set ::max_journal +} [expr 2615+[string length [pwd]]] +do_test pager1-5.4.2 { + set ::max_journal 0 + execsql { + PRAGMA synchronous = full; + BEGIN; + DELETE FROM t1 WHERE b = 'Lenin'; + DELETE FROM t2 WHERE b = 'Lenin'; + COMMIT; + } + set ::max_journal +} [expr 3111+[string length [pwd]]] +db close +tv delete + +do_test pager1-5.5.1 { + sqlite3 db test.db + execsql { + ATTACH 'test.db2' AS aux; + PRAGMA journal_mode = PERSIST; + CREATE TABLE t3(a, b); + INSERT INTO t3 SELECT randomblob(1500), randomblob(1500) FROM t1; + UPDATE t3 SET b = randomblob(1500); + } + expr [file size test.db-journal] > 15000 +} {1} +do_test pager1-5.5.2 { + execsql { + PRAGMA synchronous = full; + BEGIN; + DELETE FROM t1 WHERE b = 'Stalin'; + DELETE FROM t2 WHERE b = 'Stalin'; + COMMIT; + } + file size test.db-journal +} {0} + + +#------------------------------------------------------------------------- +# The following tests work with "PRAGMA max_page_count" +# +do_test pager1-6.1 { + faultsim_delete_and_reopen + execsql { + PRAGMA max_page_count = 10; + 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); + } +} {10} +do_test pager1-6.2 { + catchsql { + CREATE TABLE t11(a, b); + } +} {1 {database or disk is full}} + + +#------------------------------------------------------------------------- +# The following tests work with "PRAGMA journal_mode=TRUNCATE" and +# "PRAGMA locking_mode=EXCLUSIVE". +# +# Each test is specified with 5 variables. As follows: +# +# $tn: Test Number. Used as part of the [do_test] test names. +# $sql: SQL to execute. +# $res: Expected result of executing $sql. +# $js: The expected size of the journal file, in bytes, after executing +# the SQL script. Or -1 if the journal is not expected to exist. +# $ws: The expected size of the WAL file, in bytes, after executing +# the SQL script. Or -1 if the WAL is not expected to exist. +# +faultsim_delete_and_reopen +foreach {tn sql res js ws} [subst { + + 1 { + CREATE TABLE t1(a, b); + PRAGMA auto_vacuum=OFF; + PRAGMA synchronous=NORMAL; + PRAGMA page_size=1024; + PRAGMA locking_mode=EXCLUSIVE; + PRAGMA journal_mode=TRUNCATE; + INSERT INTO t1 VALUES(1, 2); + } {exclusive truncate} 0 -1 + + 2 { + BEGIN IMMEDIATE; + SELECT * FROM t1; + COMMIT; + } {1 2} 0 -1 + + 3 { + BEGIN; + SELECT * FROM t1; + COMMIT; + } {1 2} 0 -1 + + 4 { PRAGMA journal_mode = WAL } wal -1 -1 + 5 { INSERT INTO t1 VALUES(3, 4) } {} -1 [wal_file_size 1 1024] + 6 { PRAGMA locking_mode = NORMAL } normal -1 [wal_file_size 1 1024] + 7 { INSERT INTO t1 VALUES(5, 6); } {} -1 [wal_file_size 2 1024] + + 8 { PRAGMA journal_mode = TRUNCATE } truncate 0 -1 + 9 { INSERT INTO t1 VALUES(7, 8) } {} 0 -1 + 10 { SELECT * FROM t1 } {1 2 3 4 5 6 7 8} 0 -1 + +}] { + do_execsql_test pager1-7.1.$tn.1 $sql $res + catch { set J -1 ; set J [file size test.db-journal] } + catch { set W -1 ; set W [file size test.db-wal] } + do_test pager1-7.1.$tn.2 { list $J $W } [list $js $ws] +} + +do_test pager1-8.1 { + faultsim_delete_and_reopen + db close + sqlite3 db :memory: + execsql { + CREATE TABLE x1(x); + INSERT INTO x1 VALUES('Charles'); + INSERT INTO x1 VALUES('James'); + INSERT INTO x1 VALUES('Mary'); + SELECT * FROM x1; + } +} {Charles James Mary} +do_test pager1-8.2 { + db close + sqlite3 db :memory: + catchsql { SELECT * FROM x1 } +} {1 {no such table: x1}} + finish_test diff --git a/test/pager2.test b/test/pager2.test new file mode 100644 index 000000000..855850f05 --- /dev/null +++ b/test/pager2.test @@ -0,0 +1,117 @@ +# 2010 June 15 +# +# 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. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +source $testdir/malloc_common.tcl + +set otn 0 +testvfs tv -default 1 +foreach code [list { + set s 512 +} { + set s 1024 + set sql { PRAGMA journal_mode = memory } +} { + set s 1024 + set sql { + PRAGMA journal_mode = memory; + PRAGMA locking_mode = exclusive; + } +} { + set s 2048 + tv devchar safe_append +} { + set s 4096 +} { + set s 4096 + set sql { PRAGMA journal_mode = WAL } +} { + set s 8192 + set sql { PRAGMA synchronous = off } +}] { + + incr otn + set sql "" + tv devchar {} + eval $code + tv sectorsize $s + + do_test pager2-1.$otn.0 { + faultsim_delete_and_reopen + execsql $sql + execsql { + PRAGMA cache_size = 10; + CREATE TABLE t1(i INTEGER PRIMARY KEY, j blob); + } + } {} + + set tn 0 + set lowpoint 0 + foreach x { + 100 x 0 100 + x + 70 22 96 59 96 50 22 56 21 16 37 64 43 40 0 38 22 38 55 0 6 + 43 62 32 93 54 18 13 29 45 66 29 25 61 31 53 82 75 25 96 86 10 69 + 2 29 6 60 80 95 42 82 85 50 68 96 90 39 78 69 87 97 48 74 65 43 + x + 86 34 26 50 41 85 58 44 89 22 6 51 45 46 58 32 97 6 1 12 32 2 + 69 39 48 71 33 31 5 58 90 43 24 54 12 9 18 57 4 38 91 42 27 45 + 50 38 56 29 10 0 26 37 83 1 78 15 47 30 75 62 46 29 68 5 30 4 + 27 96 33 95 79 75 56 10 29 70 32 75 52 88 5 36 50 57 46 63 88 65 + x + 44 95 64 20 24 35 69 61 61 2 35 92 42 46 23 98 78 1 38 72 79 35 + 94 37 13 59 5 93 27 58 80 75 58 7 67 13 10 76 84 4 8 70 81 45 + 8 41 98 5 60 26 92 29 91 90 2 62 40 4 5 22 80 15 83 76 52 88 + 29 5 68 73 72 7 54 17 89 32 81 94 51 28 53 71 8 42 54 59 70 79 + x + } { + incr tn + set now [db one {SELECT count(i) FROM t1}] + if {$x == "x"} { + execsql { COMMIT ; BEGIN } + set lowpoint $now + do_test pager2.1.$otn.$tn { + sqlite3 db2 test.db + execsql { + SELECT COALESCE(max(i), 0) FROM t1; + PRAGMA integrity_check; + } + } [list $lowpoint ok] + db2 close + } else { + if {$now > $x } { + if { $x>=$lowpoint } { + execsql "ROLLBACK TO sp_$x" + } else { + execsql "DELETE FROM t1 WHERE i>$x" + set lowpoint $x + } + } elseif {$now < $x} { + for {set k $now} {$k < $x} {incr k} { + execsql "SAVEPOINT sp_$k" + execsql { INSERT INTO t1(j) VALUES(randomblob(1500)) } + } + } + do_execsql_test pager2.1.$otn.$tn { + SELECT COALESCE(max(i), 0) FROM t1; + PRAGMA integrity_check; + } [list $x ok] + } + } +} +db close +tv delete + + +finish_test diff --git a/test/pagerfault.test b/test/pagerfault.test index 8dc99e0bc..0af8d5180 100644 --- a/test/pagerfault.test +++ b/test/pagerfault.test @@ -55,10 +55,55 @@ do_faultsim_test pagerfault-1 -prep { } #------------------------------------------------------------------------- +# Test fault-injection while rolling back a hot-journal file with a +# page-size different from the current value stored on page 1 of the +# database file. +# +do_test pagerfault-2-pre1 { + testvfs tv -default 1 + tv filter xSync + tv script xSyncCb + proc xSyncCb {filename args} { + if {[string match *journal filename]==0} faultsim_save + } + faultsim_delete_and_reopen + execsql { + PRAGMA page_size = 4096; + BEGIN; + CREATE TABLE abc(a, b, c); + INSERT INTO abc VALUES('o', 't', 't'); + INSERT INTO abc VALUES('f', 'f', 's'); + INSERT INTO abc SELECT * FROM abc; -- 4 + INSERT INTO abc SELECT * FROM abc; -- 8 + INSERT INTO abc SELECT * FROM abc; -- 16 + INSERT INTO abc SELECT * FROM abc; -- 32 + INSERT INTO abc SELECT * FROM abc; -- 64 + INSERT INTO abc SELECT * FROM abc; -- 128 + INSERT INTO abc SELECT * FROM abc; -- 256 + COMMIT; + PRAGMA page_size = 1024; + VACUUM; + } + db close + tv delete +} {} +do_faultsim_test pagerfault-2 -prep { + faultsim_restore_and_reopen +} -body { + execsql { SELECT * FROM abc } +} -test { + set answer [split [string repeat "ottffs" 128] ""] + faultsim_test_result [list 0 $answer] + faultsim_integrity_check + set res [db eval { SELECT * FROM abc }] + if {$res != $answer} { error "Database content appears incorrect ($res)" } +} -faults oom-transient + +#------------------------------------------------------------------------- # Test fault-injection while rolling back hot-journals that were created # as part of a multi-file transaction. # -do_test pagerfault-2-pre1 { +do_test pagerfault-3-pre1 { testvfs tstvfs -default 1 tstvfs filter xDelete tstvfs script xDeleteCallback @@ -96,7 +141,7 @@ do_test pagerfault-2-pre1 { db close tstvfs delete } {} -do_faultsim_test pagerfault-2 -faults ioerr-persistent -prep { +do_faultsim_test pagerfault-3 -faults ioerr-persistent -prep { faultsim_restore_and_reopen } -body { execsql { @@ -107,7 +152,6 @@ do_faultsim_test pagerfault-2 -faults ioerr-persistent -prep { } -test { faultsim_test_result {0 {4 4}} {1 {unable to open database: test.db2}} faultsim_integrity_check - catchsql { ATTACH 'test.db2' AS aux } if {[db one { SELECT count(*) FROM t1 }] != 4 || [db one { SELECT count(*) FROM t2 }] != 4 @@ -116,4 +160,151 @@ do_faultsim_test pagerfault-2 -faults ioerr-persistent -prep { } } +#------------------------------------------------------------------------- +# Test fault-injection as part of a vanilla, no-transaction, INSERT +# statement. +# +do_faultsim_test pagerfault-4 -prep { + faultsim_delete_and_reopen +} -body { + execsql { + CREATE TABLE x(y); + INSERT INTO x VALUES('z'); + SELECT * FROM x; + } +} -test { + faultsim_test_result {0 z} + faultsim_integrity_check +} + +#------------------------------------------------------------------------- +# Test fault-injection as part of a commit when using journal_mode=PERSIST. +# Three different cases: +# +# pagerfault-5.1: With no journal_size_limit configured. +# pagerfault-5.2: With a journal_size_limit configured. +# pagerfault-5.4: Multi-file transaction. One connection has a +# journal_size_limit of 0, the other has no limit. +# +do_test pagerfault-5-pre1 { + faultsim_delete_and_reopen + db func a_string a_string + execsql { + CREATE TABLE t1(a UNIQUE, b UNIQUE); + INSERT INTO t1 VALUES(a_string(200), a_string(300)); + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; + } + faultsim_save_and_close +} {} +do_faultsim_test pagerfault-5.1 -prep { + faultsim_restore_and_reopen + db func a_string a_string + execsql { PRAGMA journal_mode = PERSIST } +} -body { + execsql { INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1 } +} -test { + faultsim_test_result {0 {}} + faultsim_integrity_check +} +do_faultsim_test pagerfault-5.2 -prep { + faultsim_restore_and_reopen + db func a_string a_string + execsql { + PRAGMA journal_mode = PERSIST; + PRAGMA journal_size_limit = 2048; + } +} -body { + execsql { INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1 } +} -test { + faultsim_test_result {0 {}} + faultsim_integrity_check +} +do_faultsim_test pagerfault-5.3 -prep { + faultsim_restore_and_reopen + db func a_string a_string + file delete -force test2.db test2.db-journal test2.db-wal + execsql { + PRAGMA journal_mode = PERSIST; + ATTACH 'test2.db' AS aux; + PRAGMA aux.journal_mode = PERSIST; + PRAGMA aux.journal_size_limit = 0; + } +} -body { + execsql { + BEGIN; + INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1; + CREATE TABLE aux.t2 AS SELECT * FROM t1; + COMMIT; + } +} -test { + faultsim_test_result {0 {}} +} + +#------------------------------------------------------------------------- +# Test fault-injection as part of a commit when using +# journal_mode=TRUNCATE. +# +do_test pagerfault-6-pre1 { + faultsim_delete_and_reopen + db func a_string a_string + execsql { + CREATE TABLE t1(a UNIQUE, b UNIQUE); + INSERT INTO t1 VALUES(a_string(200), a_string(300)); + } + faultsim_save_and_close +} {} +do_faultsim_test pagerfault-6.1 -prep { + faultsim_restore_and_reopen + db func a_string a_string + execsql { PRAGMA journal_mode = TRUNCATE } +} -body { + execsql { INSERT INTO t1 SELECT a_string(200), a_string(300) FROM t1 } +} -test { + faultsim_test_result {0 {}} + faultsim_integrity_check +} + +# The following was an attempt to get a bitvec malloc to fail. Didn't work. +# +# do_test pagerfault-6-pre1 { +# faultsim_delete_and_reopen +# execsql { +# CREATE TABLE t1(x, y, UNIQUE(x, y)); +# INSERT INTO t1 VALUES(1, randomblob(1501)); +# INSERT INTO t1 VALUES(2, randomblob(1502)); +# INSERT INTO t1 VALUES(3, randomblob(1503)); +# INSERT INTO t1 VALUES(4, randomblob(1504)); +# INSERT INTO t1 +# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1; +# INSERT INTO t1 +# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1; +# INSERT INTO t1 +# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1; +# INSERT INTO t1 +# SELECT x, randomblob(1500+oid+(SELECT max(oid) FROM t1)) FROM t1; +# } +# faultsim_save_and_close +# } {} +# do_faultsim_test pagerfault-6 -prep { +# faultsim_restore_and_reopen +# } -body { +# execsql { +# BEGIN; +# UPDATE t1 SET x=x+4 WHERE x=1; +# SAVEPOINT one; +# UPDATE t1 SET x=x+4 WHERE x=2; +# SAVEPOINT three; +# UPDATE t1 SET x=x+4 WHERE x=3; +# SAVEPOINT four; +# UPDATE t1 SET x=x+4 WHERE x=4; +# RELEASE three; +# COMMIT; +# SELECT DISTINCT x FROM t1; +# } +# } -test { +# faultsim_test_result {0 {5 6 7 8}} +# faultsim_integrity_check +# } + finish_test diff --git a/test/permutations.test b/test/permutations.test index 921df33a4..e1846c13a 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -169,7 +169,9 @@ test_suite "coverage-pager" -description { Coverage tests for file pager.c. } -files { pager1.test + pager2.test pagerfault.test + journal2.test } diff --git a/test/tester.tcl b/test/tester.tcl index 625bdcc3e..d6ce7eac2 100644 --- a/test/tester.tcl +++ b/test/tester.tcl @@ -49,6 +49,8 @@ # crashsql ARGS... # integrity_check TESTNAME ?DB? # do_test TESTNAME SCRIPT EXPECTED +# do_execsql_test TESTNAME SQL EXPECTED +# do_catchsql_test TESTNAME SQL EXPECTED # # Commands providing a lower level interface to the global test counters: # @@ -316,6 +318,14 @@ proc do_test {name cmd expected} { } flush stdout } + +proc do_execsql_test {testname sql result} { + uplevel do_test $testname [list "execsql {$sql}"] [list $result] +} +proc do_catchsql_test {testname sql result} { + uplevel do_test $testname [list "catchsql {$sql}"] [list $result] +} + # Run an SQL script. # Return the number of microseconds per statement. diff --git a/test/walmode.test b/test/walmode.test index 714e0d818..48979b19c 100644 --- a/test/walmode.test +++ b/test/walmode.test @@ -124,46 +124,48 @@ do_test walmode-4.5 { # from WAL to rollback mode because a second connection has the database # open. Or from rollback to WAL. # -do_test walmode-4.1 { +do_test walmode-4.6 { sqlite3 db2 test.db execsql { PRAGMA main.journal_mode } db2 } {delete} -do_test walmode-4.2 { +do_test walmode-4.7 { execsql { PRAGMA main.journal_mode = wal } db } {wal} -do_test walmode-4.3 { +do_test walmode-4.8 { execsql { SELECT * FROM t1 } db2 } {1 2} -do_test walmode-4.4 { +do_test walmode-4.9 { catchsql { PRAGMA journal_mode = delete } db } {1 {database is locked}} -do_test walmode-4.5 { +do_test walmode-4.10 { execsql { PRAGMA main.journal_mode } db } {wal} -do_test walmode-4.6 { + +do_test walmode-4.11 { db2 close execsql { PRAGMA journal_mode = delete } db } {delete} -do_test walmode-4.7 { +do_test walmode-4.12 { execsql { PRAGMA main.journal_mode } db } {delete} -do_test walmode-4.8 { +do_test walmode-4.13 { list [file exists test.db-journal] [file exists test.db-wal] } {0 0} -do_test walmode-4.9 { +do_test walmode-4.14 { sqlite3 db2 test.db execsql { BEGIN; SELECT * FROM t1; } db2 } {1 2} -do_test walmode-4.11 { - execsql { PRAGMA main.journal_mode } db -} {delete} -do_test walmode-4.10 { + +do_test walmode-4.16 { execsql { PRAGMA main.journal_mode } db } {delete} +do_test walmode-4.17 { execsql { PRAGMA main.journal_mode } db2 } {delete} + +do_test walmode-4.17 { catchsql { PRAGMA main.journal_mode = wal } db } {1 {database is locked}} -do_test walmode-4.11 { +do_test walmode-4.18 { execsql { PRAGMA main.journal_mode } db } {delete} catch { db close } @@ -180,7 +182,6 @@ do_test walmode-5.1.1 { sqlite3 db :memory: execsql { PRAGMA main.journal_mode } } {memory} -breakpoint do_test walmode-5.1.2 { execsql { PRAGMA main.journal_mode = wal } } {memory} |