diff --git a/main.mk b/main.mk index cfd08211c1..214fd26329 100644 --- a/main.mk +++ b/main.mk @@ -640,10 +640,15 @@ test: testfixture$(EXE) sqlite3$(EXE) # threadtest runs a few thread-safety tests that are implemented in C. This # target is invoked by the releasetest.tcl script. # -threadtest3$(EXE): sqlite3.o $(TOP)/test/threadtest3.c \ - $(TOP)/test/tt3_checkpoint.c - $(TCCX) -O2 sqlite3.o $(TOP)/test/threadtest3.c \ - -o threadtest3$(EXE) $(THREADLIB) +THREADTEST3_SRC = $(TOP)/test/threadtest3.c \ + $(TOP)/test/tt3_checkpoint.c \ + $(TOP)/test/tt3_index.c \ + $(TOP)/test/tt3_vacuum.c \ + $(TOP)/test/tt3_stress.c \ + $(TOP)/test/tt3_lookaside1.c + +threadtest3$(EXE): sqlite3.o $(THREADTEST3_SRC) + $(TCCX) $(TOP)/test/threadtest3.c sqlite3.o -o $@ $(THREADLIB) threadtest: threadtest3$(EXE) ./threadtest3$(EXE) diff --git a/manifest b/manifest index 25939330c8..7b78e82923 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\sthe\sKeyInfo\scache\smutex\sfix\sfrom\strunk. -D 2014-12-09T14:54:26.273 +C Merge\sthreading\sfixes\sfrom\strunk\sinto\sthe\ssessions\sbranch. +D 2014-12-16T01:05:38.517 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 0869fe2a3b7853f048a945fd9cdf671a329b7351 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -169,7 +169,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 112ccda703db78f10e0b386723acab2044fd97ed +F main.mk 38ce97d3bcbaffe38869bd178a5f45124e4ee3ff F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea F mkopcodeh.awk c6b3fa301db6ef7ac916b14c60868aeaec1337b5 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 @@ -185,15 +185,15 @@ F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F src/alter.c ba266a779bc7ce10e52e59e7d3dc79fa342e8fdb F src/analyze.c 7a2986e6ea8247e5f21aca3d0b584598f58d84fe -F src/attach.c f4e94df2d1826feda65eb0939f7f6f5f923a0ad9 +F src/attach.c 7f6b3fafa2290b407e4a94dcf1afda7ec0fe394b F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240 F src/backup.c 7ddee9c7d505e07e959a575b18498f17c71e53ea F src/bitvec.c 19a4ba637bd85f8f63fc8c9bae5ade9fb05ec1cb F src/btmutex.c 49ca66250c7dfa844a4d4cb8272b87420d27d3a5 -F src/btree.c ea6692ce58bfba55b12c75d2947fec0906d1ef7a +F src/btree.c 92f745ccd18099973beb28e25fce80148545429e F src/btree.h e31a3a3ebdedb1caf9bda3ad5dbab3db9b780f6e F src/btreeInt.h 3363e18fd76f69a27a870b25221b2345b3fd4d21 -F src/build.c 67bb05b1077e0cdaccb2e36bfcbe7a5df9ed31e8 +F src/build.c 162d84e4833b03f9d07192ef06057b0226f6e543 F src/callback.c 7b44ce59674338ad48b0e84e7b72f935ea4f68b0 F src/complete.c c4ba6e0626bb94bc77a0861735f3382fcf7cc818 F src/ctime.c df19848891c8a553c80e6f5a035e768280952d1a @@ -211,8 +211,8 @@ F src/insert.c 4f6df86bbed2d7b59e4601730407876825dd7b71 F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e F src/lempar.c 7274c97d24bb46631e504332ccd3bd1b37841770 -F src/loadext.c de741e66e5ddc1598d904d7289239696e40ed994 -F src/main.c 6614381d96520d7c73d0fb2eb5291533a6b04d16 +F src/loadext.c 86bd4e2fccd520b748cba52492ab60c4a770f660 +F src/main.c ef3c25ce021d5ab6ffd29fcf993183bd31e2f9aa F src/malloc.c 740db54387204c9a2eb67c6d98e68b08e9ef4eab F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c faf615aafd8be74a71494dfa027c113ea5c6615f @@ -231,7 +231,7 @@ F src/os.h 3e57a24e2794a94d3cf2342c6d9a884888cd96bf F src/os_common.h 92815ed65f805560b66166e3583470ff94478f04 F src/os_setup.h c9d4553b5aaa6f73391448b265b89bed0b890faa F src/os_unix.c fb587121840f690101336879adfa6d0b2cd0e8c7 -F src/os_win.c a9e500dd963fb1f67d7860e58b5772abe6123862 +F src/os_win.c ecb04a0dad2fa6fa659931a9d8f0f3aca33f908a F src/os_win.h 09e751b20bbc107ffbd46e13555dc73576d88e21 F src/pager.c 7a5c5bc0e29b9b16834f5558a9d5d22bbae59a08 F src/pager.h d1eee3c3f741be247ce6d82752a178515fc8578b @@ -239,23 +239,23 @@ F src/parse.y 5dfead8aed90cb0c7c1115898ee2266804daff45 F src/pcache.c ace1b67632deeaa84859b4c16c27711dfb7db3d4 F src/pcache.h b44658c9c932d203510279439d891a2a83e12ba8 F src/pcache1.c facbdd3ecc09c8f750089d941305694301328e98 -F src/pragma.c d54cdd40b63d608f2d95b7482c710690e3593a73 -F src/prepare.c b7b7bf020bd4c962f7c8aed5a3c542c7dfe9f9c7 +F src/pragma.c c93be505649183b2d80082c2eef1a56879dabfe6 +F src/prepare.c 173a5a499138451b2561614ecb87d78f9f4644b9 F src/printf.c 9e75a6a0b55bf61cfff7d7e19d89834a1b938236 F src/random.c ba2679f80ec82c4190062d756f22d0c358180696 F src/resolve.c f6c46d3434439ab2084618d603e6d6dbeb0d6ada F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e F src/select.c f377fb8a5c73c10678ea74f3400f7913943e3d75 F src/shell.c 81e4f2b57396db0714bc73d1f95cf3970f5dcc10 -F src/sqlite.h.in 9e505658e72a84604b7571b6bc78d8a9bde0a9b7 +F src/sqlite.h.in fdd032d889da139009700d7d9b8d0d43c1900c90 F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h 17d487c3c91b0b8c584a32fbeb393f6f795eea7d -F src/sqliteInt.h aff67183ad2b5d29f71a3084e15d16cae96f622c +F src/sqliteInt.h 69c302a3b13d06ebaada7f85497106077df00a24 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d F src/status.c 81712116e826b0089bb221b018929536b2b5406f F src/table.c f142bba7903e93ca8d113a5b8877a108ad1a27dc -F src/tclsqlite.c 05be57620509060e85064b9495256c05d56e76b0 -F src/test1.c ebb8cd3c94a2ac8851b7b0b1349284e73a8b4c7a +F src/tclsqlite.c 95452a59e1afd0cbce10d5243c480b0d5e1e5f59 +F src/test1.c 56e33bf6b1827c6ca7520c189131ddd778fb2267 F src/test2.c 98049e51a17dc62606a99a9eb95ee477f9996712 F src/test3.c 1c0e5d6f080b8e33c1ce8b3078e7013fdbcd560c F src/test4.c 9b32d22f5f150abe23c1830e2057c4037c45b3df @@ -311,17 +311,17 @@ F src/vacuum.c 9b30ec729337dd012ed88d4c292922c8ef9cf00c F src/vdbe.c c02217423a807dc97c743f5fff493cd55dffa77d F src/vdbe.h b434bb75fbec973d18d49225a59833ae39ee2afc F src/vdbeInt.h dc69f0351bef56456fdba3e09d3387ba4f1b1520 -F src/vdbeapi.c 3d4d2a2b24055ce2cb029fa73067c56616264b51 +F src/vdbeapi.c 90aeb4b3f1dc86d6576a2ce2d5bad12761329bd5 F src/vdbeaux.c ccf6b7ca6c7361bdb71d12385b4cff70b395486c F src/vdbeblob.c cb7359c2d99df92c35cdaedc12af6d4f83854cb7 F src/vdbemem.c 96e41193b4affd9ebc0eea2fa628879dac88c744 -F src/vdbesort.c db015e20a77b25eca4d1e125815f4998a3ca1354 +F src/vdbesort.c c150803a3e98fbc68bd07772cbbd4328a0a7212d F src/vdbetrace.c 7e4222955e07dd707a2f360c0eb73452be1cb010 F src/vtab.c c08ec66f45919eaa726bf88aa53eb08379d607f9 F src/wal.c 847692349eb6e1fb8543dbc97e69ddbfa4cc7ea7 F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4 F src/walker.c c253b95b4ee44b21c406e2a1052636c31ea27804 -F src/where.c e914fdb9159bb36af4a673193bbda08aaf9e5a73 +F src/where.c d46de821bc604a4fd36fa3928c086950e91aafb1 F src/whereInt.h d3633e9b592103241b74b0ec76185f3e5b8b62e0 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 @@ -421,7 +421,7 @@ F test/collate7.test 8ec29d98f3ee4ccebce6e16ce3863fb6b8c7b868 F test/collate8.test df26649cfcbddf109c04122b340301616d3a88f6 F test/collate9.test 3adcc799229545940df2f25308dd1ad65869145a F test/collateA.test b8218ab90d1fa5c59dcf156efabb1b2599c580d6 -F test/colmeta.test 087c42997754b8c648819832241daf724f813322 +F test/colmeta.test 2c765ea61ee37bc43bbe6d6047f89004e6508eb1 F test/colname.test 08948a4809d22817e0e5de89c7c0a8bd90cb551b F test/conflict.test 841bcf7cabbfca39c577eb8411ea8601843b46a8 F test/conflict2.test 0d3af4fb534fa1bd020c79960bb56e4d52655f09 @@ -495,7 +495,9 @@ F test/e_update.test 312cb8f5ccfe41515a6bb092f8ea562a9bd54d52 F test/e_uri.test 5ae33760fb2039c61aa2d90886f1664664173585 F test/e_vacuum.test 5bfbdc21b65c0abf24398d0ba31dc88d93ca77a9 F test/e_wal.test 0967f0b8f1dfda871dc7b9b5574198f1f4f7d69a -F test/e_walckpt.test 18de8fca6b74f29bf7d24a2e267eec749b8fec50 +F test/e_walauto.test a1fa9d36c160cc4001a934d1e009aae597b440b7 +F test/e_walckpt.test 3116a98fa0dd9b2c9e493de7c59730adfe436746 +F test/e_walhook.test da3ea8b3483d1af72190337bda50155a91a4b664 F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea F test/enc2.test 83437a79ba1545a55fb549309175c683fb334473 F test/enc3.test 90683ad0e6ea587b9d5542ca93568af9a9858c40 @@ -797,7 +799,7 @@ F test/pcache.test b09104b03160aca0d968d99e8cd2c5b1921a993d F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025 F test/percentile.test b98fc868d71eb5619d42a1702e9ab91718cbed54 F test/permutations.test 5e60eb6ca8429453ab20525dc6ac93d9c41dac6e -F test/pragma.test 49ac8a73c0daa574824538fed28727d1259fe735 +F test/pragma.test aa16dedfe01c02c8895169012f7dfde9c163f0d5 F test/pragma2.test aea7b3d82c76034a2df2b38a13745172ddc0bc13 F test/printf.test ec9870c4dce8686a37818e0bf1aba6e6a1863552 F test/printf2.test b4acd4bf8734243257f01ddefa17c4fb090acc8a @@ -929,7 +931,8 @@ F test/thread2.test f35d2106452b77523b3a2b7d1dcde2e5ee8f9e46 F test/thread_common.tcl 334639cadcb9f912bf82aa73f49efd5282e6cadd F test/threadtest1.c 6029d9c5567db28e6dc908a0c63099c3ba6c383b F test/threadtest2.c ace893054fa134af3fc8d6e7cfecddb8e3acefb9 -F test/threadtest3.c fca8d360b470405ae3ed431b5cb4cdf031f85a74 +F test/threadtest3.c f8c6595664a4c5ef5f28d97a612386fe14dd1940 +F test/threadtest4.c c1e67136ceb6c7ec8184e56ac61db28f96bd2925 F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c F test/tkt-26ff0c2d1e.test 888324e751512972c6e0d1a09df740d8f5aaf660 F test/tkt-2a5629202f.test 0521bd25658428baa26665aa53ffed9367d33af2 @@ -1092,8 +1095,11 @@ F test/triggerB.test 56780c031b454abac2340dbb3b71ac5c56c3d7fe F test/triggerC.test a68980c5955d62ee24be6f97129d824f199f9a4c F test/triggerD.test 8e7f3921a92a5797d472732108109e44575fa650 F test/triggerE.test 355e9c5cbaed5cd039a60baad1fb2197caeb8e52 -F test/tt3_checkpoint.c 415eccce672d681b297485fc20f44cdf0eac93af -F test/tt3_index.c 2e7f3151a0ae522f031e8e2761ca2bda63f4d221 +F test/tt3_checkpoint.c 5e63ee65ed5f87176e25a996480cb02c6caec8b4 +F test/tt3_index.c 39eec10a35f57672225be4d182862152896dee4a +F test/tt3_lookaside1.c 0377e202c3c2a50d688cb65ba203afeda6fafeb9 +F test/tt3_stress.c c57d804716165811d979d4a719e05baccd79277f +F test/tt3_vacuum.c 1753f45917699c9c1f66b64c717a717c9379f776 F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84 F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a @@ -1244,7 +1250,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 5a73da6a3083926a3dafd28c0604122296dc9184 b05340fe3cd5f1676a55023228dc8d1a92de5936 -R 2816a79c9fe6d224099c23041d3622b2 +P f0940c73bd13cb49a3d229ce2517736324392cfb ae43539e62e76676a3daf561b629a1b9b4e2d2c9 +R 70e65d4afe346251f33a1427d3dba81d U drh -Z 5d345254f96163549bdbecca703eb3ee +Z b3f332559d7fcfb0fb904c707a0ec975 diff --git a/manifest.uuid b/manifest.uuid index 85c416a674..ffdfe86a52 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f0940c73bd13cb49a3d229ce2517736324392cfb \ No newline at end of file +9817a2864eebe2dc90ce505fe0faa8b069ff48ff \ No newline at end of file diff --git a/src/attach.c b/src/attach.c index cf52bb24b1..de8742938b 100644 --- a/src/attach.c +++ b/src/attach.c @@ -150,6 +150,7 @@ static void attachFunc( "attached databases must use the same text encoding as main database"); rc = SQLITE_ERROR; } + sqlite3BtreeEnter(aNew->pBt); pPager = sqlite3BtreePager(aNew->pBt); sqlite3PagerLockingMode(pPager, db->dfltLockMode); sqlite3BtreeSecureDelete(aNew->pBt, @@ -157,6 +158,7 @@ static void attachFunc( #ifndef SQLITE_OMIT_PAGER_PRAGMAS sqlite3BtreeSetPagerFlags(aNew->pBt, 3 | (db->flags & PAGER_FLAGS_MASK)); #endif + sqlite3BtreeLeave(aNew->pBt); } aNew->safety_level = 3; aNew->zName = sqlite3DbStrDup(db, zName); diff --git a/src/btree.c b/src/btree.c index 4283d00fd3..d24f6a876a 100644 --- a/src/btree.c +++ b/src/btree.c @@ -3913,7 +3913,7 @@ int sqlite3BtreeCloseCursor(BtCursor *pCur){ releasePage(pCur->apPage[i]); } unlockBtreeIfUnused(pBt); - sqlite3DbFree(pBtree->db, pCur->aOverflow); + sqlite3_free(pCur->aOverflow); /* sqlite3_free(pCur); */ sqlite3BtreeLeave(pBtree); } @@ -4208,6 +4208,7 @@ static int accessPayload( offset -= pCur->info.nLocal; } + if( rc==SQLITE_OK && amt>0 ){ const u32 ovflSize = pBt->usableSize - 4; /* Bytes content per ovfl page */ Pgno nextPage; @@ -4225,8 +4226,8 @@ static int accessPayload( if( eOp!=2 && (pCur->curFlags & BTCF_ValidOvfl)==0 ){ int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize; if( nOvfl>pCur->nOvflAlloc ){ - Pgno *aNew = (Pgno*)sqlite3DbRealloc( - pCur->pBtree->db, pCur->aOverflow, nOvfl*2*sizeof(Pgno) + Pgno *aNew = (Pgno*)sqlite3Realloc( + pCur->aOverflow, nOvfl*2*sizeof(Pgno) ); if( aNew==0 ){ rc = SQLITE_NOMEM; @@ -4273,6 +4274,7 @@ static int accessPayload( */ assert( eOp!=2 ); assert( pCur->curFlags & BTCF_ValidOvfl ); + assert( pCur->pBtree->db==pBt->db ); if( pCur->aOverflow[iIdx+1] ){ nextPage = pCur->aOverflow[iIdx+1]; }else{ @@ -8276,7 +8278,7 @@ int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){ if( pCur->iPage==0 ){ /* All pages of the b-tree have been visited. Return successfully. */ *pnEntry = nEntry; - return SQLITE_OK; + return moveToRoot(pCur); } moveToParent(pCur); }while ( pCur->aiIdx[pCur->iPage]>=pCur->apPage[pCur->iPage]->nCell ); diff --git a/src/build.c b/src/build.c index 0b4affc664..db954647cc 100644 --- a/src/build.c +++ b/src/build.c @@ -435,7 +435,6 @@ static void freeIndex(sqlite3 *db, Index *p){ #ifndef SQLITE_OMIT_ANALYZE sqlite3DeleteIndexSamples(db, p); #endif - if( db==0 || db->pnBytesFreed==0 ) sqlite3KeyInfoUnref(p->pKeyInfo); sqlite3ExprDelete(db, p->pPartIdxWhere); sqlite3DbFree(db, p->zColAff); if( p->isResized ) sqlite3DbFree(db, p->azColl); @@ -4190,40 +4189,31 @@ void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){ ** when it has finished using it. */ KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ + int i; + int nCol = pIdx->nColumn; + int nKey = pIdx->nKeyCol; + KeyInfo *pKey; if( pParse->nErr ) return 0; -#ifndef SQLITE_OMIT_SHARED_CACHE - if( pIdx->pKeyInfo && pIdx->pKeyInfo->db!=pParse->db ){ - sqlite3KeyInfoUnref(pIdx->pKeyInfo); - pIdx->pKeyInfo = 0; + if( pIdx->uniqNotNull ){ + pKey = sqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey); + }else{ + pKey = sqlite3KeyInfoAlloc(pParse->db, nCol, 0); } -#endif - if( pIdx->pKeyInfo==0 ){ - int i; - int nCol = pIdx->nColumn; - int nKey = pIdx->nKeyCol; - KeyInfo *pKey; - if( pIdx->uniqNotNull ){ - pKey = sqlite3KeyInfoAlloc(pParse->db, nKey, nCol-nKey); - }else{ - pKey = sqlite3KeyInfoAlloc(pParse->db, nCol, 0); + if( pKey ){ + assert( sqlite3KeyInfoIsWriteable(pKey) ); + for(i=0; iazColl[i]; + assert( zColl!=0 ); + pKey->aColl[i] = strcmp(zColl,"BINARY")==0 ? 0 : + sqlite3LocateCollSeq(pParse, zColl); + pKey->aSortOrder[i] = pIdx->aSortOrder[i]; } - if( pKey ){ - assert( sqlite3KeyInfoIsWriteable(pKey) ); - for(i=0; iazColl[i]; - assert( zColl!=0 ); - pKey->aColl[i] = strcmp(zColl,"BINARY")==0 ? 0 : - sqlite3LocateCollSeq(pParse, zColl); - pKey->aSortOrder[i] = pIdx->aSortOrder[i]; - } - if( pParse->nErr ){ - sqlite3KeyInfoUnref(pKey); - }else{ - pIdx->pKeyInfo = pKey; - } + if( pParse->nErr ){ + sqlite3KeyInfoUnref(pKey); + pKey = 0; } } - return sqlite3KeyInfoRef(pIdx->pKeyInfo); + return pKey; } #ifndef SQLITE_OMIT_CTE diff --git a/src/loadext.c b/src/loadext.c index 2a2afd8654..7b39ff1671 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -34,7 +34,6 @@ # define sqlite3_column_table_name16 0 # define sqlite3_column_origin_name 0 # define sqlite3_column_origin_name16 0 -# define sqlite3_table_column_metadata 0 #endif #ifdef SQLITE_OMIT_AUTHORIZATION diff --git a/src/main.c b/src/main.c index 02d9ad7673..9a819b90d8 100644 --- a/src/main.c +++ b/src/main.c @@ -1032,16 +1032,6 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ for(j=0; jnDb; j++){ struct Db *pDb = &db->aDb[j]; if( pDb->pBt ){ - if( pDb->pSchema ){ - /* Must clear the KeyInfo cache. See ticket [e4a18565a36884b00edf] */ - sqlite3BtreeEnter(pDb->pBt); - for(i=sqliteHashFirst(&pDb->pSchema->idxHash); i; i=sqliteHashNext(i)){ - Index *pIdx = sqliteHashData(i); - sqlite3KeyInfoUnref(pIdx->pKeyInfo); - pIdx->pKeyInfo = 0; - } - sqlite3BtreeLeave(pDb->pBt); - } sqlite3BtreeClose(pDb->pBt); pDb->pBt = 0; if( j!=1 ){ @@ -2190,32 +2180,6 @@ const char *sqlite3_errstr(int rc){ return sqlite3ErrStr(rc); } -/* -** Invalidate all cached KeyInfo objects for database connection "db" -*/ -static void invalidateCachedKeyInfo(sqlite3 *db){ - Db *pDb; /* A single database */ - int iDb; /* The database index number */ - HashElem *k; /* For looping over tables in pDb */ - Table *pTab; /* A table in the database */ - Index *pIdx; /* Each index */ - - for(iDb=0, pDb=db->aDb; iDbnDb; iDb++, pDb++){ - if( pDb->pBt==0 ) continue; - sqlite3BtreeEnter(pDb->pBt); - for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){ - pTab = (Table*)sqliteHashData(k); - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->pKeyInfo && pIdx->pKeyInfo->db==db ){ - sqlite3KeyInfoUnref(pIdx->pKeyInfo); - pIdx->pKeyInfo = 0; - } - } - } - sqlite3BtreeLeave(pDb->pBt); - } -} - /* ** Create a new collating function for database "db". The name is zName ** and the encoding is enc. @@ -2259,7 +2223,6 @@ static int createCollation( return SQLITE_BUSY; } sqlite3ExpirePreparedStatements(db); - invalidateCachedKeyInfo(db); /* If collation sequence pColl was created directly by a call to ** sqlite3_create_collation, and not generated by synthCollSeq(), @@ -2763,6 +2726,9 @@ static int openDatabase( #endif #if defined(SQLITE_DEFAULT_FOREIGN_KEYS) && SQLITE_DEFAULT_FOREIGN_KEYS | SQLITE_ForeignKeys +#endif +#if defined(SQLITE_REVERSE_UNORDERED_SELECTS) + | SQLITE_ReverseOrder #endif ; sqlite3HashInit(&db->aCollSeq); @@ -2813,6 +2779,7 @@ static int openDatabase( } sqlite3BtreeEnter(db->aDb[0].pBt); db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt); + if( !db->mallocFailed ) ENC(db) = SCHEMA_ENC(db); sqlite3BtreeLeave(db->aDb[0].pBt); db->aDb[1].pSchema = sqlite3SchemaGet(db, 0); @@ -2971,7 +2938,7 @@ int sqlite3_open16( SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); assert( *ppDb || rc==SQLITE_NOMEM ); if( rc==SQLITE_OK && !DbHasProperty(*ppDb, 0, DB_SchemaLoaded) ){ - ENC(*ppDb) = SQLITE_UTF16NATIVE; + SCHEMA_ENC(*ppDb) = ENC(*ppDb) = SQLITE_UTF16NATIVE; } }else{ rc = SQLITE_NOMEM; @@ -3167,7 +3134,6 @@ void sqlite3_thread_cleanup(void){ ** Return meta information about a specific column of a database table. ** See comment in sqlite3.h (sqlite.h.in) for details. */ -#ifdef SQLITE_ENABLE_COLUMN_METADATA int sqlite3_table_column_metadata( sqlite3 *db, /* Connection handle */ const char *zDbName, /* Database name or NULL */ @@ -3207,11 +3173,8 @@ int sqlite3_table_column_metadata( } /* Find the column for which info is requested */ - if( sqlite3IsRowid(zColumnName) ){ - iCol = pTab->iPKey; - if( iCol>=0 ){ - pCol = &pTab->aCol[iCol]; - } + if( zColumnName==0 ){ + /* Query for existance of table only */ }else{ for(iCol=0; iColnCol; iCol++){ pCol = &pTab->aCol[iCol]; @@ -3220,8 +3183,13 @@ int sqlite3_table_column_metadata( } } if( iCol==pTab->nCol ){ - pTab = 0; - goto error_out; + if( HasRowid(pTab) && sqlite3IsRowid(zColumnName) ){ + iCol = pTab->iPKey; + pCol = iCol>=0 ? &pTab->aCol[iCol] : 0; + }else{ + pTab = 0; + goto error_out; + } } } @@ -3274,7 +3242,6 @@ error_out: sqlite3_mutex_leave(db->mutex); return rc; } -#endif /* ** Sleep for a little while. Return the amount of time slept. diff --git a/src/os_win.c b/src/os_win.c index 2a7681c73f..80ce97a6cd 100644 --- a/src/os_win.c +++ b/src/os_win.c @@ -1203,8 +1203,8 @@ int sqlite3_win32_reset_heap(){ int rc; MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */ MUTEX_LOGIC( sqlite3_mutex *pMem; ) /* The memsys static mutex */ - MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) - MUTEX_LOGIC( pMem = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); ) + MUTEX_LOGIC( pMaster = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MASTER); ) + MUTEX_LOGIC( pMem = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM); ) sqlite3_mutex_enter(pMaster); sqlite3_mutex_enter(pMem); winMemAssertMagic(); diff --git a/src/pragma.c b/src/pragma.c index ab9a283629..837a15102d 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -2080,7 +2080,8 @@ void sqlite3Pragma( ){ for(pEnc=&encnames[0]; pEnc->zName; pEnc++){ if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){ - ENC(pParse->db) = pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE; + SCHEMA_ENC(db) = ENC(db) = + pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE; break; } } diff --git a/src/prepare.c b/src/prepare.c index ca9c64b441..97be900d68 100644 --- a/src/prepare.c +++ b/src/prepare.c @@ -394,9 +394,11 @@ int sqlite3Init(sqlite3 *db, char **pzErrMsg){ int commit_internal = !(db->flags&SQLITE_InternChanges); assert( sqlite3_mutex_held(db->mutex) ); + assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) ); assert( db->init.busy==0 ); rc = SQLITE_OK; db->init.busy = 1; + ENC(db) = SCHEMA_ENC(db); for(i=0; rc==SQLITE_OK && inDb; i++){ if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue; rc = sqlite3InitOne(db, i, pzErrMsg); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index afa4d89eb9..a0c111c93b 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -196,7 +196,7 @@ const char *sqlite3_compileoption_get(int N); ** SQLITE_THREADSAFE=1 or =2 then mutexes are enabled by default but ** can be fully or partially disabled using a call to [sqlite3_config()] ** with the verbs [SQLITE_CONFIG_SINGLETHREAD], [SQLITE_CONFIG_MULTITHREAD], -** or [SQLITE_CONFIG_MUTEX]. ^(The return value of the +** or [SQLITE_CONFIG_SERIALIZED]. ^(The return value of the ** sqlite3_threadsafe() function shows only the compile-time setting of ** thread safety, not any run-time changes to that setting made by ** sqlite3_config(). In other words, the return value from sqlite3_threadsafe() @@ -5152,20 +5152,27 @@ SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); /* ** CAPI3REF: Extract Metadata About A Column Of A Table ** -** ^This routine returns metadata about a specific column of a specific -** database table accessible using the [database connection] handle -** passed as the first function argument. +** ^(The sqlite3_table_column_metadata(X,D,T,C,....) routine returns +** information about column C of table T in database D +** on [database connection] X.)^ ^The sqlite3_table_column_metadata() +** interface returns SQLITE_OK and fills in the non-NULL pointers in +** the final five arguments with appropriate values if the specified +** column exists. ^The sqlite3_table_column_metadata() interface returns +** SQLITE_ERROR and if the specified column does not exist. +** ^If the column-name parameter to sqlite3_table_column_metadata() is a +** NULL pointer, then this routine simply checks for the existance of the +** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it +** does not. ** ** ^The column is identified by the second, third and fourth parameters to -** this function. ^The second parameter is either the name of the database +** this function. ^(The second parameter is either the name of the database ** (i.e. "main", "temp", or an attached database) containing the specified -** table or NULL. ^If it is NULL, then all attached databases are searched +** table or NULL.)^ ^If it is NULL, then all attached databases are searched ** for the table using the same algorithm used by the database engine to ** resolve unqualified table references. ** ** ^The third and fourth parameters to this function are the table and column -** name of the desired column, respectively. Neither of these parameters -** may be NULL. +** name of the desired column, respectively. ** ** ^Metadata is returned by writing to the memory locations passed as the 5th ** and subsequent parameters to this function. ^Any of these arguments may be @@ -5184,16 +5191,17 @@ SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); ** )^ ** ** ^The memory pointed to by the character pointers returned for the -** declaration type and collation sequence is valid only until the next +** declaration type and collation sequence is valid until the next ** call to any SQLite API function. ** ** ^If the specified table is actually a view, an [error code] is returned. ** -** ^If the specified column is "rowid", "oid" or "_rowid_" and an +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** is not a [WITHOUT ROWID] table and an ** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output ** parameters are set for the explicitly declared column. ^(If there is no -** explicitly declared [INTEGER PRIMARY KEY] column, then the output -** parameters are set as follows: +** [INTEGER PRIMARY KEY] column, then the outputs +** for the [rowid] are set as follows: ** **
 **     data type: "INTEGER"
@@ -5203,13 +5211,9 @@ SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
 **     auto increment: 0
 ** 
)^ ** -** ^(This function may load one or more schemas from database files. If an -** error occurs during this process, or if the requested table or column -** cannot be found, an [error code] is returned and an error message left -** in the [database connection] (to be retrieved using sqlite3_errmsg()).)^ -** -** ^This API is only available if the library was compiled with the -** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol defined. +** ^This function causes all database schemas to be read from disk and +** parsed, if that has not already been done, and returns an error if +** any errors are encountered while loading the schema. */ int sqlite3_table_column_metadata( sqlite3 *db, /* Connection handle */ @@ -7176,12 +7180,10 @@ void sqlite3_log(int iErrCode, const char *zFormat, ...); ** CAPI3REF: Write-Ahead Log Commit Hook ** ** ^The [sqlite3_wal_hook()] function is used to register a callback that -** will be invoked each time a database connection commits data to a -** [write-ahead log] (i.e. whenever a transaction is committed in -** [journal_mode | journal_mode=WAL mode]). +** is invoked each time data is committed to a database in wal mode. ** -** ^The callback is invoked by SQLite after the commit has taken place and -** the associated write-lock on the database released, so the implementation +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation ** may read, write or [checkpoint] the database as required. ** ** ^The first parameter passed to the callback function when it is invoked diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 2b300f8e35..762c41be16 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1060,6 +1060,7 @@ struct sqlite3 { int errCode; /* Most recent error code (SQLITE_*) */ int errMask; /* & result codes with this before returning */ u16 dbOptFlags; /* Flags to enable/disable optimizations */ + u8 enc; /* Text encoding */ u8 autoCommit; /* The auto-commit flag. */ u8 temp_store; /* 1: file 2: memory 0: default */ u8 mallocFailed; /* True if we have seen a malloc failure */ @@ -1168,7 +1169,8 @@ struct sqlite3 { /* ** A macro to discover the encoding of a database. */ -#define ENC(db) ((db)->aDb[0].pSchema->enc) +#define SCHEMA_ENC(db) ((db)->aDb[0].pSchema->enc) +#define ENC(db) ((db)->enc) /* ** Possible values for the sqlite3.flags. @@ -1792,7 +1794,6 @@ struct Index { u8 *aSortOrder; /* for each column: True==DESC, False==ASC */ char **azColl; /* Array of collation sequence names for index */ Expr *pPartIdxWhere; /* WHERE clause for partial indices */ - KeyInfo *pKeyInfo; /* A KeyInfo object suitable for this index */ int tnum; /* DB Page containing root of this index */ LogEst szIdxRow; /* Estimated average row size in bytes */ u16 nKeyCol; /* Number of columns forming the key */ diff --git a/src/tclsqlite.c b/src/tclsqlite.c index e4383335b4..6c67334c17 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -639,6 +639,7 @@ static int DbWalHandler( Tcl_Interp *interp = pDb->interp; assert(pDb->pWalHook); + assert( db==pDb->db ); p = Tcl_DuplicateObj(pDb->pWalHook); Tcl_IncrRefCount(p); Tcl_ListObjAppendElement(interp, p, Tcl_NewStringObj(zDb, -1)); diff --git a/src/test1.c b/src/test1.c index 14ccacd863..40acac8360 100644 --- a/src/test1.c +++ b/src/test1.c @@ -1569,7 +1569,6 @@ static int test_libversion_number( ** Usage: sqlite3_table_column_metadata DB dbname tblname colname ** */ -#ifdef SQLITE_ENABLE_COLUMN_METADATA static int test_table_column_metadata( ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ @@ -1589,14 +1588,14 @@ static int test_table_column_metadata( int primarykey; int autoincrement; - if( objc!=5 ){ + if( objc!=5 && objc!=4 ){ Tcl_WrongNumArgs(interp, 1, objv, "DB dbname tblname colname"); return TCL_ERROR; } if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; zDb = Tcl_GetString(objv[2]); zTbl = Tcl_GetString(objv[3]); - zCol = Tcl_GetString(objv[4]); + zCol = objc==5 ? Tcl_GetString(objv[4]) : 0; if( strlen(zDb)==0 ) zDb = 0; @@ -1618,7 +1617,6 @@ static int test_table_column_metadata( return TCL_OK; } -#endif #ifndef SQLITE_OMIT_INCRBLOB @@ -5714,6 +5712,7 @@ static int test_wal_checkpoint_v2( rc = sqlite3_wal_checkpoint_v2(db, zDb, eMode, &nLog, &nCkpt); if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){ const char *zErrCode = sqlite3ErrName(rc); + Tcl_ResetResult(interp); Tcl_AppendResult(interp, zErrCode, " - ", (char *)sqlite3_errmsg(db), 0); return TCL_ERROR; } @@ -5727,6 +5726,43 @@ static int test_wal_checkpoint_v2( return TCL_OK; } +/* +** tclcmd: sqlite3_wal_autocheckpoint db VALUE +*/ +static int test_wal_autocheckpoint( + ClientData clientData, /* Unused */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + sqlite3 *db; + int rc; + int iVal; + + + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "DB VALUE"); + return TCL_ERROR; + } + + if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) + || Tcl_GetIntFromObj(0, objv[2], &iVal) + ){ + return TCL_ERROR; + } + + rc = sqlite3_wal_autocheckpoint(db, iVal); + Tcl_ResetResult(interp); + if( rc!=SQLITE_OK ){ + const char *zErrCode = sqlite3ErrName(rc); + Tcl_SetObjResult(interp, Tcl_NewStringObj(zErrCode, -1)); + return TCL_ERROR; + } + + return TCL_OK; +} + + /* ** tclcmd: test_sqlite3_log ?SCRIPT? */ @@ -6778,9 +6814,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "sqlite3_shared_cache_report", sqlite3BtreeSharedCacheReport, 0}, #endif { "sqlite3_libversion_number", test_libversion_number, 0 }, -#ifdef SQLITE_ENABLE_COLUMN_METADATA { "sqlite3_table_column_metadata", test_table_column_metadata, 0 }, -#endif #ifndef SQLITE_OMIT_INCRBLOB { "sqlite3_blob_reopen", test_blob_reopen, 0 }, #endif @@ -6790,6 +6824,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ #endif { "sqlite3_wal_checkpoint", test_wal_checkpoint, 0 }, { "sqlite3_wal_checkpoint_v2",test_wal_checkpoint_v2, 0 }, + { "sqlite3_wal_autocheckpoint",test_wal_autocheckpoint, 0 }, { "test_sqlite3_log", test_sqlite3_log, 0 }, #ifndef SQLITE_OMIT_EXPLAIN { "print_explain_query_plan", test_print_eqp, 0 }, diff --git a/src/vdbeapi.c b/src/vdbeapi.c index c7c14d9c31..0797901dac 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -400,7 +400,10 @@ static int doWalCallbacks(sqlite3 *db){ for(i=0; inDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ - int nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt)); + int nEntry; + sqlite3BtreeEnter(pBt); + nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt)); + sqlite3BtreeLeave(pBt); if( db->xWalCallback && nEntry>0 && rc==SQLITE_OK ){ rc = db->xWalCallback(db->pWalArg, db, db->aDb[i].zName, nEntry); } @@ -580,7 +583,6 @@ int sqlite3_step(sqlite3_stmt *pStmt){ ** sqlite3_errmsg() and sqlite3_errcode(). */ const char *zErr = (const char *)sqlite3_value_text(db->pErr); - assert( zErr!=0 || db->mallocFailed ); sqlite3DbFree(db, v->zErrMsg); if( !db->mallocFailed ){ v->zErrMsg = sqlite3DbStrDup(db, zErr); diff --git a/src/vdbesort.c b/src/vdbesort.c index 29ec40c0d1..78ecc1efb9 100644 --- a/src/vdbesort.c +++ b/src/vdbesort.c @@ -451,7 +451,7 @@ struct SorterRecord { /* The minimum PMA size is set to this value multiplied by the database ** page size in bytes. */ #ifndef SQLITE_SORTER_PMASZ -# define SQLITE_SORTER_PMASZ 250 +# define SQLITE_SORTER_PMASZ 10 #endif /* Maximum number of PMAs that a single MergeEngine can merge */ diff --git a/src/where.c b/src/where.c index ee42534f34..183a8cb667 100644 --- a/src/where.c +++ b/src/where.c @@ -3941,7 +3941,6 @@ static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){ p->u.vtab.idxStr = 0; }else if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 && p->u.btree.pIndex!=0 ){ sqlite3DbFree(db, p->u.btree.pIndex->zColAff); - sqlite3KeyInfoUnref(p->u.btree.pIndex->pKeyInfo); sqlite3DbFree(db, p->u.btree.pIndex); p->u.btree.pIndex = 0; } diff --git a/test/colmeta.test b/test/colmeta.test index 3939f8228a..21b0e0f38a 100644 --- a/test/colmeta.test +++ b/test/colmeta.test @@ -17,17 +17,14 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl -ifcapable !columnmetadata { - finish_test - return -} - # Set up a schema in the main and temp test databases. do_test colmeta-0 { execsql { CREATE TABLE abc(a, b, c); CREATE TABLE abc2(a PRIMARY KEY COLLATE NOCASE, b VARCHAR(32), c); CREATE TABLE abc3(a NOT NULL, b INTEGER PRIMARY KEY, c); + CREATE TABLE abc5(w,x,y,z,PRIMARY KEY(x,z)) WITHOUT ROWID; + CREATE TABLE abc6(rowid TEXT COLLATE rtrim, oid REAL, _rowid_ BLOB); } ifcapable autoinc { execsql { @@ -57,19 +54,27 @@ set tests { 13 {main abc rowid} {0 {INTEGER BINARY 0 1 0}} 14 {main abc3 rowid} {0 {INTEGER BINARY 0 1 0}} 16 {main abc d} {1 {no such table column: abc.d}} + 20 {main abc5 w} {0 {{} BINARY 0 0 0}} + 21 {main abc5 x} {0 {{} BINARY 1 1 0}} + 22 {main abc5 y} {0 {{} BINARY 0 0 0}} + 23 {main abc5 z} {0 {{} BINARY 1 1 0}} + 24 {main abc5 rowid} {1 {no such table column: abc5.rowid}} + 30 {main abc6 rowid} {0 {TEXT rtrim 0 0 0}} + 31 {main abc6 oid} {0 {REAL BINARY 0 0 0}} + 32 {main abc6 _rowid_} {0 {BLOB BINARY 0 0 0}} } -ifcapable view { +ifcapable autoinc { set tests [concat $tests { - 8 {{} abc4 b} {0 {INTEGER BINARY 0 1 1}} - 15 {main abc4 rowid} {0 {INTEGER BINARY 0 1 1}} + 100 {{} abc4 b} {0 {INTEGER BINARY 0 1 1}} + 101 {main abc4 rowid} {0 {INTEGER BINARY 0 1 1}} }] } ifcapable view { set tests [concat $tests { - 9 {{} v1 a} {1 {no such table column: v1.a}} - 10 {main v1 b} {1 {no such table column: v1.b}} - 11 {main v1 badname} {1 {no such table column: v1.badname}} - 12 {main v1 rowid} {1 {no such table column: v1.rowid}} + 200 {{} v1 a} {1 {no such table column: v1.a}} + 201 {main v1 b} {1 {no such table column: v1.b}} + 202 {main v1 badname} {1 {no such table column: v1.badname}} + 203 {main v1 rowid} {1 {no such table column: v1.rowid}} }] } @@ -91,4 +96,15 @@ foreach {tn params results} $tests { } $results } +# Calling sqlite3_table_column_metadata with a NULL column name merely +# checks for the existance of the table. +# +do_test colmeta-300 { + catch {sqlite3_table_column_metadata $::DB main xyzzy} res +} {1} +do_test colmeta-301 { + catch {sqlite3_table_column_metadata $::DB main abc} res +} {0} + + finish_test diff --git a/test/e_walauto.test b/test/e_walauto.test new file mode 100644 index 0000000000..fc394e9053 --- /dev/null +++ b/test/e_walauto.test @@ -0,0 +1,201 @@ +# 2014 December 04 +# +# 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/wal_common.tcl +set testprefix e_walauto + + +proc read_nbackfill {} { + seek $::shmfd 96 + binary scan [read $::shmfd 4] i nBackfill + set nBackfill +} +proc read_mxframe {} { + seek $::shmfd 16 + binary scan [read $::shmfd 4] i mxFrame + set mxFrame +} + +# Assuming that the main db for database handle +# +proc do_autocommit_threshold_test {tn value} { + + set nBackfillSaved [read_nbackfill] + while {1} { + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + if {[read_mxframe] >= $value} break + } + + set nBackfillNew [read_nbackfill] + uplevel [list do_test $tn "expr $nBackfillNew > $nBackfillSaved" 1] +} + +# EVIDENCE-OF: R-30135-06439 The wal_autocheckpoint pragma can be used +# to invoke this interface from SQL. +# +# All tests in this file are run twice - once using the +# sqlite3_wal_autocheckpoint() API, and once using "PRAGMA +# wal_autocheckpoint". +# +foreach {tn code} { + 1 { + proc autocheckpoint {db value} { + uplevel [list $db eval "PRAGMA wal_autocheckpoint = $value"] + } + } + + 2 { + proc autocheckpoint {db value} { + uplevel [list sqlite3_wal_autocheckpoint $db $value] + return $value + } + } +} { + + eval $code + + reset_db + do_execsql_test 1.$tn.0 { PRAGMA journal_mode = WAL } {wal} + do_execsql_test 1.$tn.1 { CREATE TABLE t1(a, b) } + set shmfd [open "test.db-shm"] + + # EVIDENCE-OF: R-41531-51083 Every new database connection defaults to + # having the auto-checkpoint enabled with a threshold of 1000 or + # SQLITE_DEFAULT_WAL_AUTOCHECKPOINT pages. + # + do_autocommit_threshold_test 1.$tn.2 1000 + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + do_autocommit_threshold_test 1.$tn.3 1000 + + # EVIDENCE-OF: R-38128-34102 The sqlite3_wal_autocheckpoint(D,N) is a + # wrapper around sqlite3_wal_hook() that causes any database on database + # connection D to automatically checkpoint after committing a + # transaction if there are N or more frames in the write-ahead log file. + # + do_test 1.$tn.4 { + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + autocheckpoint db 100 + } {100} + do_autocommit_threshold_test 1.$tn.5 100 + + do_test 1.$tn.6 { + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + autocheckpoint db 500 + } {500} + do_autocommit_threshold_test 1.$tn.7 500 + + # EVIDENCE-OF: R-26993-43540 Passing zero or a negative value as the + # nFrame parameter disables automatic checkpoints entirely. + # + do_test 1.$tn.7 { + autocheckpoint db 0 ;# Set to zero + for {set i 0} {$i < 10000} {incr i} { + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + } + expr {[file size test.db-wal] > (5 * 1024 * 1024)} + } 1 + do_test 1.$tn.8 { + sqlite3_wal_checkpoint_v2 db truncate + file size test.db-wal + } 0 + do_test 1.$tn.9 { + autocheckpoint db -4 ;# Set to a negative value + for {set i 0} {$i < 10000} {incr i} { + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + } + expr {[file size test.db-wal] > (5 * 1024 * 1024)} + } 1 + + # EVIDENCE-OF: R-10203-42688 The callback registered by this function + # replaces any existing callback registered using sqlite3_wal_hook(). + # + set ::wal_hook_callback 0 + proc wal_hook_callback {args} { incr ::wal_hook_callback ; return 0 } + do_test 1.$tn.10.1 { + db wal_hook wal_hook_callback + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + set ::wal_hook_callback + } 2 + do_test 1.$tn.10.2 { + autocheckpoint db 100 + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + set ::wal_hook_callback + } 2 + + # EVIDENCE-OF: R-17497-43474 Likewise, registering a callback using + # sqlite3_wal_hook() disables the automatic checkpoint mechanism + # configured by this function. + do_test 1.$tn.11.1 { + sqlite3_wal_checkpoint_v2 db truncate + file size test.db-wal + } 0 + do_test 1.$tn.11.2 { + autocheckpoint db 100 + for {set i 0} {$i < 1000} {incr i} { + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + } + expr {[file size test.db-wal] < (1 * 1024 * 1024)} + } 1 + do_test 1.$tn.11.3 { + db wal_hook wal_hook_callback + for {set i 0} {$i < 1000} {incr i} { + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + } + expr {[file size test.db-wal] < (1 * 1024 * 1024)} + } 0 + + # EVIDENCE-OF: R-33080-59193 Checkpoints initiated by this mechanism + # are PASSIVE. + # + set ::busy_callback_count 0 + proc busy_callback {args} { + puts Hello + incr ::busy_callback_count + return 0 + } + do_test 1.$tn.12.1 { + sqlite3_wal_checkpoint_v2 db truncate + autocheckpoint db 100 + db busy busy_callback + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + } {} + do_test 1.$tn.12.2 { + sqlite3 db2 test.db + db2 eval { BEGIN; SELECT * FROM t1 LIMIT 10; } + read_nbackfill + } {0} + do_test 1.$tn.12.3 { + for {set i 0} {$i < 1000} {incr i} { + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + } + read_nbackfill + } {2} + do_test 1.$tn.12.4 { + set ::busy_callback_count + } {0} + db2 close + + do_test 1.$tn.12.5 { + db eval { INSERT INTO t1 VALUES(randomblob(100), randomblob(100)) } + read_nbackfill + } {1559} + + db close + close $shmfd +} + +finish_test diff --git a/test/e_walckpt.test b/test/e_walckpt.test index f0edc46bee..e6b6566bed 100644 --- a/test/e_walckpt.test +++ b/test/e_walckpt.test @@ -42,6 +42,38 @@ proc compare_db_hashes {} { set ret } +#------------------------------------------------------------------------- +# All calls to the [sqlite3_wal_checkpoint_v2] command made within this +# file use this wrapper. It's sole purpose is to throw an error if the +# following requirement is violated: +# +# EVIDENCE-OF: R-60567-47780 Unless it returns SQLITE_MISUSE, the +# sqlite3_wal_checkpoint_v2() interface sets the error information that +# is queried by sqlite3_errcode() and sqlite3_errmsg(). +# +proc wal_checkpoint_v2 {db args} { + set rc [catch { + uplevel sqlite3_wal_checkpoint_v2 $db $args + } msg] + + set errcode "SQLITE_OK" + if {$rc} { + set errcode [lindex [split $msg " "] 0] + } elseif { [lindex $msg 0] } { + set errcode "SQLITE_BUSY" + } + + if {$errcode != "SQLITE_MISUSE" && [sqlite3_errcode $db] != $errcode} { + error "sqlite3_errcode mismatch! (1) $errcode!=[sqlite3_errcode $db]" + } + + if {$rc==0} { + return $msg + } else { + error $msg + } +} + # The following tests are run 3 times, each using a different method of # invoking a checkpoint: @@ -63,7 +95,7 @@ proc compare_db_hashes {} { foreach {tn script} { 1 { proc checkpoint {db mode args} { - eval sqlite3_wal_checkpoint_v2 [list $db] [list $mode] $args + eval wal_checkpoint_v2 [list $db] [list $mode] $args } } @@ -90,7 +122,7 @@ foreach {tn script} { error "$rc - [sqlite3_errmsg $db]" } } else { - eval sqlite3_wal_checkpoint_v2 [list $db] [list $mode] $args + eval wal_checkpoint_v2 [list $db] [list $mode] $args } } } @@ -270,36 +302,46 @@ foreach {tn script} { } 2 { - # Close first the reader, then later the writer. + # Close first the reader, then later the writer. Give up before + # closing the [db6] reader. if {$n==5} { catch {db2 eval commit} } if {$n==10} { catch {db3 eval commit} } + if {$n==15} { return 1 } return 0 } 3 { - # Close first the writer, then later the reader. + # Close first the writer, then later the reader. And finally the + # [db6] reader. if {$n==5} { catch {db2 eval commit} } if {$n==10} { catch {db3 eval commit} } + if {$n==15} { catch {db6 eval commit} } return 0 } } } foreach {mode busy_handler_mode} { - passive 1 - full 1 - full 2 - full 3 + passive 1 + full 1 full 2 full 3 + restart 1 restart 2 restart 3 + truncate 1 truncate 2 truncate 3 } { + set tp "$tn.$mode.$busy_handler_mode" set ::sync_counter 0 + # Set up a callback function for xSync and xWrite calls made during + # the checkpoint. + # + set ::checkpoint_ongoing 0 proc tvfs_callback {method args} { + if {$::checkpoint_ongoing==0} return + set tail [file tail [lindex $args 0]] if {$method == "xSync" && $tail == "test.db"} { incr ::sync_counter } - if {$method == "xWrite" && $tail=="test.db"} { if {$::write_ok < 0} { set ::write_ok [expr ![catch {db5 eval { BEGIN IMMEDIATE }}]] @@ -308,6 +350,14 @@ foreach {tn script} { if {$::read_ok < 0} { set ::read_ok [expr ![catch {db5 eval { SELECT * FROM t1 }}]] } + + # If one has not already been opened, open a read-transaction using + # connection [db6] + catch { db6 eval { BEGIN ; SELECT * FROM sqlite_master } } msg + } + if {$method == "xShmLock" } { + set details [lindex $args 2] + if {$details == "0 1 lock exclusive"} { set ::seen_writer_lock 1 } } } @@ -318,7 +368,7 @@ foreach {tn script} { #tvfs filter xSync tvfs script tvfs_callback - do_execsql_test $tn.4.$mode.0 { + do_execsql_test $tp.0 { CREATE TABLE t1(a, b); CREATE TABLE t2(a, b); PRAGMA journal_mode = wal; @@ -328,7 +378,7 @@ foreach {tn script} { } {wal} # Open a reader on the current database snapshot. - do_test $tn.4.$mode.1 { + do_test $tp.1 { sqlite3 db2 test.db -vfs tvfs execsql { BEGIN; @@ -338,7 +388,7 @@ foreach {tn script} { # Open a writer. Write a transaction. Then begin, but do not commit, # a second transaction. - do_test $tn.4.$mode.2 { + do_test $tp.2 { sqlite3 db3 test.db -vfs tvfs execsql { INSERT INTO t2 VALUES(7, 8); @@ -349,6 +399,7 @@ foreach {tn script} { } {1 2 3 4 5 6 7 8 9 10} sqlite3 db5 test.db -vfs tvfs + sqlite3 db6 test.db -vfs tvfs # Register a busy-handler with connection [db]. # @@ -357,11 +408,15 @@ foreach {tn script} { set ::busy_handler_counter 0 set ::read_ok -1 set ::write_ok -1 + set ::seen_writer_lock 0 - do_test $tn.4.$mode.3 { + set ::checkpoint_ongoing 1 + do_test $tp.3 { checkpoint db $mode main set {} {} } {} + set ::checkpoint_ongoing 0 + set ::did_restart_blocking [expr {[catch {db6 eval commit}]}] if { $mode=="passive" } { # EVIDENCE-OF: R-16333-64433 Checkpoint as many frames as possible @@ -381,15 +436,15 @@ foreach {tn script} { # EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked # in the SQLITE_CHECKPOINT_PASSIVE mode. # - # It's not. Test case "$tn.4.$mode.6". + # It's not. Test case "$tp.6". # - do_test $tn.4.$mode.4 { + do_test $tp.4 { forcecopy test.db abc.db sqlite3 db4 abc.db db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 } } {1 2 3 4 5 6} - do_test $tn.4.$mode.5 { set ::sync_counter } 0 - do_test $tn.4.$mode.6 { set ::busy_handler_counter } 0 + do_test $tp.5 { set ::sync_counter } 0 + do_test $tp.6 { set ::busy_handler_counter } 0 db4 close db2 eval COMMIT @@ -405,26 +460,53 @@ foreach {tn script} { # Also, because the checkpoint finishes this time, the db is synced. # Which is part of R-16333-64433 above. # - do_test $tn.4.$mode.7 { + set ::checkpoint_ongoing 1 + do_test $tp.7 { checkpoint db $mode main forcecopy test.db abc.db sqlite3 db4 abc.db db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 } } {1 2 3 4 5 6 7 8 9 10} - do_test $tn.4.$mode.6 { set ::sync_counter } 1 - do_test $tn.4.$mode.7 { set ::busy_handler_counter } 0 + set ::checkpoint_ongoing 0 + do_test $tp.7 { set ::sync_counter } 1 + do_test $tp.8 { set ::busy_handler_counter } 0 db4 close } - if { $mode=="full" } { + if { $mode=="full" || $mode=="restart" || $mode=="truncate" } { + + # EVIDENCE-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and + # TRUNCATE modes also obtain the exclusive "writer" lock on the + # database file. + # + # Or at least attempts to obtain. + # + do_test $tp.9 { + set ::seen_writer_lock + } {1} + if {$busy_handler_mode==2 || $busy_handler_mode==3} { # EVIDENCE-OF: R-59171-47567 This mode blocks (it invokes the # busy-handler callback) until there is no database writer and all # readers are reading from the most recent database snapshot. # - # Show that both the reader and writer have finished: + # The test below shows that both the reader and writer have + # finished: # - do_test $tn.4.$mode.7 { + # Also restated by the following two. That both busy_handler_mode + # values 2 and 3 work show that both of the following are true - as + # they release the reader and writer transactions in different + # orders. + # + # EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained + # immediately, and a busy-handler is configured, it is invoked and the + # writer lock retried until either the busy-handler returns 0 or the + # lock is successfully obtained. + # + # EVIDENCE-OF: R-48107-00250 The busy-handler is also invoked while + # waiting for database readers as described above. + # + do_test $tp.7 { list [catchsql COMMIT db2] [catchsql COMMIT db3] } [list \ {1 {cannot commit - no transaction is active}} \ @@ -434,27 +516,79 @@ foreach {tn script} { # EVIDENCE-OF: R-29177-48281 It then checkpoints all frames in the log # file and syncs the database file. # - do_test $tn.4.$mode.8 { + do_test $tp.8 { forcecopy test.db abc.db sqlite3 db4 abc.db db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 } } {1 2 3 4 5 6 7 8 9 10} - do_test $tn.4.$mode.9 { set ::sync_counter } 1 + do_test $tp.9 { set ::sync_counter } 1 db4 close # EVIDENCE-OF: R-51867-44713 This mode blocks new database writers # while it is pending, but new database readers are allowed to continue # unimpeded. - do_test $tn.4.$mode.10 { + # + # EVIDENCE-OF: R-47276-58266 Like SQLITE_CHECKPOINT_FULL, this mode + # blocks new database writer attempts while it is pending, but does not + # impede readers. + # + # The first of the above two refers to "full" mode. The second + # to "restart". + # + do_test $tp.10.1 { list $::write_ok $::read_ok } {0 1} + # EVIDENCE-OF: R-12410-31217 This mode works the same way as + # SQLITE_CHECKPOINT_FULL with the addition that after checkpointing the + # log file it blocks (calls the busy-handler callback) until all + # readers are reading from the database file only. + # + # The stuff above passed, so the first part of this requirement + # is met. The second part is tested below. If the checkpoint mode + # was "restart" or "truncate", then the busy-handler will have + # been called to block on wal-file readers. + # + do_test $tp.11 { + set ::did_restart_blocking + } [expr {($mode=="restart"||$mode=="truncate")&&$busy_handler_mode==3}] + + # EVIDENCE-OF: R-44699-57140 This mode works the same way as + # SQLITE_CHECKPOINT_RESTART with the addition that it also truncates + # the log file to zero bytes just prior to a successful return. + if {$mode=="truncate" && $busy_handler_mode==3} { + do_test $tp.12 { + file size test.db-wal + } 0 + } + } elseif {$busy_handler_mode==1} { + + # EVIDENCE-OF: R-34519-06271 SQLITE_BUSY is returned in this case. + if {$tn!=2} { + # ($tn==2) is the loop that uses "PRAGMA wal_checkpoint" + do_test $tp.13 { sqlite3_errcode db } {SQLITE_BUSY} + } + + # EVIDENCE-OF: R-49155-63541 If the busy-handler returns 0 before the + # writer lock is obtained or while waiting for database readers, the + # checkpoint operation proceeds from that point in the same way as + # SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible + # without blocking any further. + do_test $tp.14 { + forcecopy test.db abc.db + sqlite3 db4 abc.db + db4 eval { SELECT * FROM t1 UNION ALL SELECT * FROM t2 } + } {1 2 3 4 5 6} + do_test $tp.15 { set ::sync_counter } 0 + do_test $tp.16 { set ::busy_handler_counter } 1 + db4 close } } db2 close db3 close db5 close + db6 close } db close @@ -480,9 +614,139 @@ foreach {tn mode res} { 8 1000000 {1 {SQLITE_MISUSE - not an error}} } { do_test 4.$tn { - list [catch "sqlite3_wal_checkpoint_v2 db $mode" msg] $msg + list [catch "wal_checkpoint_v2 db $mode" msg] $msg } $res } +db close + +foreach tn {1 2 3} { + forcedelete test.db test.db2 test.db3 + testvfs tvfs + + sqlite3 db test.db -vfs tvfs + execsql { + ATTACH 'test.db2' AS aux2; + ATTACH 'test.db3' AS aux3; + PRAGMA main.journal_mode = WAL; + PRAGMA aux2.journal_mode = WAL; + PRAGMA aux3.journal_mode = WAL; + + CREATE TABLE main.t1(x,y); + CREATE TABLE aux2.t2(x,y); + CREATE TABLE aux3.t3(x,y); + + INSERT INTO t1 VALUES('a', 'b'); + INSERT INTO t2 VALUES('a', 'b'); + INSERT INTO t3 VALUES('a', 'b'); + } + sqlite3 db2 test.db2 -vfs tvfs + + switch -- $tn { + 1 { + # EVIDENCE-OF: R-41299-52117 If no error (SQLITE_BUSY or otherwise) is + # encountered while processing the attached databases, SQLITE_OK is + # returned. + do_test 5.$tn.1 { + lindex [wal_checkpoint_v2 db truncate] 0 + } {0} ;# 0 -> SQLITE_OK + do_test 5.$tn.2 { + list [expr [file size test.db-wal]==0] \ + [expr [file size test.db2-wal]==0] \ + [expr [file size test.db3-wal]==0] + } {1 1 1} + } + + 2 { + # EVIDENCE-OF: R-38578-34175 If an SQLITE_BUSY error is encountered when + # processing one or more of the attached WAL databases, the operation is + # still attempted on any remaining attached databases and SQLITE_BUSY is + # returned at the end. + db2 eval { BEGIN; INSERT INTO t2 VALUES('d', 'e'); } + do_test 5.$tn.1 { + lindex [wal_checkpoint_v2 db truncate] 0 + } {1} ;# 1 -> SQLITE_BUSY + do_test 5.$tn.2 { + list [expr [file size test.db-wal]==0] \ + [expr [file size test.db2-wal]==0] \ + [expr [file size test.db3-wal]==0] + } {1 0 1} + db2 eval ROLLBACK + } + + 3 { + # EVIDENCE-OF: R-38049-07913 If any other error occurs while processing + # an attached database, processing is abandoned and the error code is + # returned to the caller immediately. + tvfs filter xWrite + tvfs script inject_ioerr + proc inject_ioerr {method file args} { + if {[file tail $file]=="test.db2"} { + return "SQLITE_IOERR" + } + return 0 + } + do_test 5.$tn.1 { + list [catch { wal_checkpoint_v2 db truncate } msg] $msg + } {1 {SQLITE_IOERR - disk I/O error}} + do_test 5.$tn.2 { + list [expr [file size test.db-wal]==0] \ + [expr [file size test.db2-wal]==0] \ + [expr [file size test.db3-wal]==0] + } {1 0 0} + tvfs script "" + } + } + + db close + db2 close +} + +reset_db +sqlite3 db2 test.db + +do_test 6.1 { + execsql { + PRAGMA journal_mode = WAL; + CREATE TABLE t1(a, b); + INSERT INTO t1 VALUES(1, 2); + } + file size test.db-wal +} [wal_file_size 3 1024] + +do_test 6.2 { + db2 eval { BEGIN; SELECT * FROM t1; } + db eval { INSERT INTO t1 VALUES(3, 4) } + file size test.db-wal +} [wal_file_size 4 1024] + +# At this point the log file contains 4 frames. 3 of which it should +# be possible to checkpoint. +# +# EVIDENCE-OF: R-16642-42503 If pnLog is not NULL, then *pnLog is set to +# the total number of frames in the log file or to -1 if the checkpoint +# could not run because of an error or because the database is not in +# WAL mode. +# +# EVIDENCE-OF: R-10514-25250 If pnCkpt is not NULL,then *pnCkpt is set +# to the total number of checkpointed frames in the log file (including +# any that were already checkpointed before the function was called) or +# to -1 if the checkpoint could not run due to an error or because the +# database is not in WAL mode. +# +do_test 6.4 { + lrange [wal_checkpoint_v2 db passive] 1 2 +} {4 3} + +# EVIDENCE-OF: R-37257-17813 Note that upon successful completion of an +# SQLITE_CHECKPOINT_TRUNCATE, the log file will have been truncated to +# zero bytes and so both *pnLog and *pnCkpt will be set to zero. +# +do_test 6.5 { + db2 eval COMMIT + wal_checkpoint_v2 db truncate +} {0 0 0} + finish_test + diff --git a/test/e_walhook.test b/test/e_walhook.test new file mode 100644 index 0000000000..c8c8819493 --- /dev/null +++ b/test/e_walhook.test @@ -0,0 +1,200 @@ +# 2014 December 04 +# +# 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/wal_common.tcl +set testprefix e_walhook + + +# EVIDENCE-OF: R-00752-43975 The sqlite3_wal_hook() function is used to +# register a callback that is invoked each time data is committed to a +# database in wal mode. +# +# 1.1: shows that the wal-hook is not invoked in rollback mode. +# 1.2: but is invoked in wal mode. +# +set ::wal_hook_count 0 +proc my_wal_hook {args} { + incr ::wal_hook_count + return 0 +} + +do_test 1.1.1 { + db wal_hook my_wal_hook + execsql { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(1); + } + set ::wal_hook_count +} 0 +do_test 1.1.2 { + execsql { PRAGMA journal_mode = wal } + set ::wal_hook_count +} 0 + +do_test 1.3 { + execsql { INSERT INTO t1 VALUES(2) } + set wal_hook_count +} 1 + +do_test 1.4 { + execsql { + BEGIN; + INSERT INTO t1 VALUES(3); + INSERT INTO t1 VALUES(4); + COMMIT; + } + set wal_hook_count +} 2 + +# EVIDENCE-OF: R-65366-15139 The callback is invoked by SQLite after the +# commit has taken place and the associated write-lock on the database +# released +# +set ::read_ok 0 +proc my_wal_hook {args} { + sqlite3 db2 test.db + if {[db2 eval { SELECT * FROM t1 }] == "1 2 3 4 5"} { + set ::read_ok 1 + } + db2 close +} +do_test 2.1 { + execsql { INSERT INTO t1 VALUES(5) } + set ::read_ok +} 1 + +# EVIDENCE-OF: R-44294-52863 The third parameter is the name of the +# database that was written to - either "main" or the name of an +# ATTACH-ed database. +# +# EVIDENCE-OF: R-18913-19355 The fourth parameter is the number of pages +# currently in the write-ahead log file, including those that were just +# committed. +# +set ::wal_hook_args [list] +proc my_wal_hook {dbname nEntry} { + set ::wal_hook_args [list $dbname $nEntry] +} +forcedelete test.db2 +do_test 3.0 { + execsql { + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.t2(x); + PRAGMA aux.journal_mode = wal; + } +} {wal} + +# Database "aux" +do_test 3.1.1 { + set wal_hook_args [list] + execsql { INSERT INTO t2 VALUES('a') } +} {} +do_test 3.1.2 { + set wal_hook_args +} [list aux [wal_frame_count test.db2-wal 1024]] + +# Database "main" +do_test 3.2.1 { + set wal_hook_args [list] + execsql { INSERT INTO t1 VALUES(6) } +} {} +do_test 3.1.2 { + set wal_hook_args +} [list main [wal_frame_count test.db-wal 1024]] + +# EVIDENCE-OF: R-14034-00929 If an error code is returned, that error +# will propagate back up through the SQLite code base to cause the +# statement that provoked the callback to report an error, though the +# commit will have still occurred. +# +proc my_wal_hook {args} { return 1 ;# SQLITE_ERROR } +do_catchsql_test 4.1 { + INSERT INTO t1 VALUES(7) +} {1 {SQL logic error or missing database}} + +proc my_wal_hook {args} { return 5 ;# SQLITE_BUSY } +do_catchsql_test 4.2 { + INSERT INTO t1 VALUES(8) +} {1 {database is locked}} + +proc my_wal_hook {args} { return 14 ;# SQLITE_CANTOPEN } +do_catchsql_test 4.3 { + INSERT INTO t1 VALUES(9) +} {1 {unable to open database file}} + +do_execsql_test 4.4 { + SELECT * FROM t1 +} {1 2 3 4 5 6 7 8 9} + +# EVIDENCE-OF: R-10466-53920 Calling sqlite3_wal_hook() replaces any +# previously registered write-ahead log callback. +set ::old_wal_hook 0 +proc my_old_wal_hook {args} { + incr ::old_wal_hook + return 0 +} +db wal_hook my_old_wal_hook +do_test 5.1 { + execsql { INSERT INTO t1 VALUES(10) } + set ::old_wal_hook +} {1} + +# Replace old_wal_hook. Observe that it is not invoked after it has +# been replaced. +proc my_new_wal_hook {args} { return 0 } +db wal_hook my_new_wal_hook +do_test 5.2 { + execsql { INSERT INTO t1 VALUES(11) } + set ::old_wal_hook +} {1} + + + +# EVIDENCE-OF: R-42842-27162 Note that the sqlite3_wal_autocheckpoint() +# interface and the wal_autocheckpoint pragma both invoke +# sqlite3_wal_hook() and will those overwrite any prior +# sqlite3_wal_hook() settings. +# +set ::old_wal_hook 0 +proc my_old_wal_hook {args} { incr ::old_wal_hook ; return 0 } +db wal_hook my_old_wal_hook +do_test 6.1.1 { + execsql { INSERT INTO t1 VALUES(12) } + set ::old_wal_hook +} {1} +do_test 6.1.2 { + execsql { PRAGMA wal_autocheckpoint = 1000 } + execsql { INSERT INTO t1 VALUES(12) } + set ::old_wal_hook +} {1} + +# EVIDENCE-OF: R-52629-38967 The first parameter passed to the callback +# function when it is invoked is a copy of the third parameter passed to +# sqlite3_wal_hook() when registering the callback. +# +# This is tricky to test using the tcl interface. However, the +# mechanism used to invoke the tcl script registered as a wal-hook +# depends on the context pointer being correctly passed through. And +# since multiple different wal-hook scripts have been successfully +# invoked by this test script, consider this tested. +# +# EVIDENCE-OF: R-23378-42536 The second is a copy of the database +# handle. +# +# There is an assert() in the C wal-hook used by tclsqlite.c to +# prove this. And that hook has been invoked multiple times when +# running this script. So consider this requirement tested as well. +# + +finish_test diff --git a/test/pragma.test b/test/pragma.test index e660ab0fe7..09b9b6c14f 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -454,10 +454,34 @@ do_execsql_test pragma-3.21 { do_execsql_test pragma-3.22 { PRAGMA integrity_check(2); } {{non-unique entry in index t1a} {NULL value in t1x.a}} -do_execsql_test pragma-3.21 { +do_execsql_test pragma-3.23 { PRAGMA integrity_check(1); } {{non-unique entry in index t1a}} +# PRAGMA integrity check (or more specifically the sqlite3BtreeCount() +# interface) used to leave index cursors in an inconsistent state +# which could result in an assertion fault in sqlite3BtreeKey() +# called from saveCursorPosition() if content is removed from the +# index while the integrity_check is still running. This test verifies +# that problem has been fixed. +# +do_test pragma-3.30 { + db close + delete_file test.db + sqlite3 db test.db + db eval { + CREATE TABLE t1(a,b,c); + WITH RECURSIVE + c(i) AS (VALUES(1) UNION ALL SELECT i+1 FROM c WHERE i<100) + INSERT INTO t1(a,b,c) SELECT i, printf('xyz%08x',i), 2000-i FROM c; + CREATE INDEX t1a ON t1(a); + CREATE INDEX t1bc ON t1(b,c); + } + db eval {PRAGMA integrity_check} { + db eval {DELETE FROM t1} + } +} {} + # Test modifying the cache_size of an attached database. ifcapable pager_pragmas&&attach { do_test pragma-4.1 { diff --git a/test/threadtest3.c b/test/threadtest3.c index 084ca022a9..ff8add5bf6 100644 --- a/test/threadtest3.c +++ b/test/threadtest3.c @@ -47,10 +47,13 @@ #define execsql_i64(x,y,...) (SEL(x), execsql_i64_x(x,y,__VA_ARGS__)) #define execsql_text(x,y,z,...) (SEL(x), execsql_text_x(x,y,z,__VA_ARGS__)) #define execsql(x,y,...) (SEL(x), (void)execsql_i64_x(x,y,__VA_ARGS__)) +#define sql_script_printf(x,y,z,...) ( \ + SEL(x), sql_script_printf_x(x,y,z,__VA_ARGS__) \ +) /* Thread functions */ -#define launch_thread(w,x,y,z) (SEL(w), launch_thread_x(w,x,y,z)) -#define join_all_threads(y,z) (SEL(y), join_all_threads_x(y,z)) +#define launch_thread(w,x,y,z) (SEL(w), launch_thread_x(w,x,y,z)) +#define join_all_threads(y,z) (SEL(y), join_all_threads_x(y,z)) /* Timer functions */ #define setstoptime(y,z) (SEL(y), setstoptime_x(y,z)) @@ -64,6 +67,9 @@ #define filesize(y,z) (SEL(y), filesize_x(y,z)) #define filecopy(x,y,z) (SEL(x), filecopy_x(x,y,z)) +#define PTR2INT(x) ((int)((intptr_t)x)) +#define INT2PTR(x) ((void*)((intptr_t)x)) + /* ** End of test code/infrastructure interface macros. *************************************************************************/ @@ -115,7 +121,10 @@ struct MD5Context { int isInit; uint32 buf[4]; uint32 bits[2]; - unsigned char in[64]; + union { + unsigned char in[64]; + uint32 in32[16]; + } u; }; typedef struct MD5Context MD5Context; @@ -264,7 +273,7 @@ void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len){ /* Handle any leading odd-sized chunks */ if ( t ) { - unsigned char *p = (unsigned char *)ctx->in + t; + unsigned char *p = (unsigned char *)ctx->u.in + t; t = 64-t; if (len < t) { @@ -272,8 +281,8 @@ void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len){ return; } memcpy(p, buf, t); - byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (uint32 *)ctx->in); + byteReverse(ctx->u.in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->u.in); buf += t; len -= t; } @@ -281,16 +290,16 @@ void MD5Update(MD5Context *ctx, const unsigned char *buf, unsigned int len){ /* Process data in 64-byte chunks */ while (len >= 64) { - memcpy(ctx->in, buf, 64); - byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (uint32 *)ctx->in); + memcpy(ctx->u.in, buf, 64); + byteReverse(ctx->u.in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->u.in); buf += 64; len -= 64; } /* Handle any remaining bytes of data. */ - memcpy(ctx->in, buf, len); + memcpy(ctx->u.in, buf, len); } /* @@ -306,7 +315,7 @@ static void MD5Final(unsigned char digest[16], MD5Context *ctx){ /* Set the first char of padding to 0x80. This is safe since there is always at least one byte free */ - p = ctx->in + count; + p = ctx->u.in + count; *p++ = 0x80; /* Bytes of padding needed to make 64 bytes */ @@ -316,25 +325,25 @@ static void MD5Final(unsigned char digest[16], MD5Context *ctx){ if (count < 8) { /* Two lots of padding: Pad the first block to 64 bytes */ memset(p, 0, count); - byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (uint32 *)ctx->in); + byteReverse(ctx->u.in, 16); + MD5Transform(ctx->buf, (uint32 *)ctx->u.in); /* Now fill the next block with 56 bytes */ - memset(ctx->in, 0, 56); + memset(ctx->u.in, 0, 56); } else { /* Pad block to 56 bytes */ memset(p, 0, count-8); } - byteReverse(ctx->in, 14); + byteReverse(ctx->u.in, 14); /* Append length in bits and transform */ - ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0]; - ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1]; + ctx->u.in32[14] = ctx->bits[0]; + ctx->u.in32[15] = ctx->bits[1]; - MD5Transform(ctx->buf, (uint32 *)ctx->in); + MD5Transform(ctx->buf, (uint32 *)ctx->u.in); byteReverse((unsigned char *)ctx->buf, 4); memcpy(digest, ctx->buf, 16); - memset(ctx, 0, sizeof(ctx)); /* In case it is sensitive */ + memset(ctx, 0, sizeof(*ctx)); /* In case it is sensitive */ } /* @@ -398,9 +407,6 @@ typedef struct Thread Thread; /* Total number of errors in this process so far. */ static int nGlobalErr = 0; -/* Set to true to run in "process" instead of "thread" mode. */ -static int bProcessMode = 0; - struct Error { int rc; int iLine; @@ -421,10 +427,10 @@ struct Statement { struct Thread { int iTid; /* Thread number within test */ - int iArg; /* Integer argument passed by caller */ + void* pArg; /* Pointer argument passed by caller */ pthread_t tid; /* Thread id */ - char *(*xProc)(int, int); /* Thread main proc */ + char *(*xProc)(int, void*); /* Thread main proc */ Thread *pNext; /* Next in this list of threads */ }; @@ -506,8 +512,9 @@ static void opendb_x( ){ if( pErr->rc==SQLITE_OK ){ int rc; + int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI; if( bDelete ) unlink(zFile); - rc = sqlite3_open(zFile, &pDb->db); + rc = sqlite3_open_v2(zFile, &pDb->db, flags, 0); if( rc ){ sqlite_error(pErr, pDb, "open"); sqlite3_close(pDb->db); @@ -556,6 +563,22 @@ static void sql_script_x( } } +static void sql_script_printf_x( + Error *pErr, /* IN/OUT: Error code */ + Sqlite *pDb, /* Database handle */ + const char *zFormat, /* SQL printf format string */ + ... /* Printf args */ +){ + va_list ap; /* ... printf arguments */ + va_start(ap, zFormat); + if( pErr->rc==SQLITE_OK ){ + char *zSql = sqlite3_vmprintf(zFormat, ap); + pErr->rc = sqlite3_exec(pDb->db, zSql, 0, 0, &pErr->zErr); + sqlite3_free(zSql); + } + va_end(ap); +} + static Statement *getSqlStatement( Error *pErr, /* IN/OUT: Error code */ Sqlite *pDb, /* Database handle */ @@ -624,11 +647,9 @@ static i64 execsql_i64_x( if( pErr->rc==SQLITE_OK ){ sqlite3_stmt *pStmt; /* SQL statement to execute */ va_list ap; /* ... arguments */ - int i; /* Used to iterate through parameters */ va_start(ap, pDb); pStmt = getAndBindSqlStatement(pErr, pDb, ap); if( pStmt ){ - int rc; int first = 1; while( SQLITE_ROW==sqlite3_step(pStmt) ){ if( first && sqlite3_column_count(pStmt)>0 ){ @@ -663,11 +684,9 @@ static char * execsql_text_x( if( pErr->rc==SQLITE_OK ){ sqlite3_stmt *pStmt; /* SQL statement to execute */ va_list ap; /* ... arguments */ - int i; /* Used to iterate through parameters */ va_start(ap, iSlot); pStmt = getAndBindSqlStatement(pErr, pDb, ap); if( pStmt ){ - int rc; int first = 1; while( SQLITE_ROW==sqlite3_step(pStmt) ){ if( first && sqlite3_column_count(pStmt)>0 ){ @@ -693,14 +712,13 @@ static void integrity_check_x( ){ if( pErr->rc==SQLITE_OK ){ Statement *pStatement; /* Statement to execute */ - int rc; /* Return code */ char *zErr = 0; /* Integrity check error */ pStatement = getSqlStatement(pErr, pDb, "PRAGMA integrity_check"); if( pStatement ){ sqlite3_stmt *pStmt = pStatement->pStmt; while( SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *z = sqlite3_column_text(pStmt, 0); + const char *z = (const char*)sqlite3_column_text(pStmt, 0); if( strcmp(z, "ok") ){ if( zErr==0 ){ zErr = sqlite3_mprintf("%s", z); @@ -721,14 +739,14 @@ static void integrity_check_x( static void *launch_thread_main(void *pArg){ Thread *p = (Thread *)pArg; - return (void *)p->xProc(p->iTid, p->iArg); + return (void *)p->xProc(p->iTid, p->pArg); } static void launch_thread_x( Error *pErr, /* IN/OUT: Error code */ Threadset *pThreads, /* Thread set */ - char *(*xProc)(int, int), /* Proc to run */ - int iArg /* Argument passed to thread proc */ + char *(*xProc)(int, void*), /* Proc to run */ + void *pArg /* Argument passed to thread proc */ ){ if( pErr->rc==SQLITE_OK ){ int iTid = ++pThreads->iMaxTid; @@ -738,7 +756,7 @@ static void launch_thread_x( p = (Thread *)sqlite3_malloc(sizeof(Thread)); memset(p, 0, sizeof(Thread)); p->iTid = iTid; - p->iArg = iArg; + p->pArg = pArg; p->xProc = xProc; rc = pthread_create(&p->tid, NULL, launch_thread_main, (void *)p); @@ -895,7 +913,7 @@ static int timetostop_x( #define WALTHREAD1_NTHREAD 10 #define WALTHREAD3_NTHREAD 6 -static char *walthread1_thread(int iTid, int iArg){ +static char *walthread1_thread(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ int nIter = 0; /* Iterations so far */ @@ -934,7 +952,7 @@ static char *walthread1_thread(int iTid, int iArg){ return sqlite3_mprintf("%d iterations", nIter); } -static char *walthread1_ckpt_thread(int iTid, int iArg){ +static char *walthread1_ckpt_thread(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ int nCkpt = 0; /* Checkpoints so far */ @@ -977,10 +995,11 @@ static void walthread1(int nMs){ print_and_free_err(&err); } -static char *walthread2_thread(int iTid, int iArg){ +static char *walthread2_thread(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ int anTrans[2] = {0, 0}; /* Number of WAL and Rollback transactions */ + int iArg = PTR2INT(pArg); const char *zJournal = "PRAGMA journal_mode = WAL"; if( iArg ){ zJournal = "PRAGMA journal_mode = DELETE"; } @@ -1026,17 +1045,18 @@ static void walthread2(int nMs){ setstoptime(&err, nMs); launch_thread(&err, &threads, walthread2_thread, 0); launch_thread(&err, &threads, walthread2_thread, 0); - launch_thread(&err, &threads, walthread2_thread, 1); - launch_thread(&err, &threads, walthread2_thread, 1); + launch_thread(&err, &threads, walthread2_thread, (void*)1); + launch_thread(&err, &threads, walthread2_thread, (void*)1); join_all_threads(&err, &threads); print_and_free_err(&err); } -static char *walthread3_thread(int iTid, int iArg){ +static char *walthread3_thread(int iTid, void *pArg){ Error err = {0}; /* Error code and message */ Sqlite db = {0}; /* SQLite database connection */ i64 iNextWrite; /* Next value this thread will write */ + int iArg = PTR2INT(pArg); opendb(&err, &db, "test.db", 0); sql_script(&err, &db, "PRAGMA wal_autocheckpoint = 10"); @@ -1087,14 +1107,14 @@ static void walthread3(int nMs){ setstoptime(&err, nMs); for(i=0; i2 ) goto usage; - if( argc==2 ){ - zTest = argv[1]; - nTest = strlen(zTest); - if( zTest[nTest-1]=='*' ){ - nTest--; - bPrefix = 1; - } - } + sqlite3_config(SQLITE_CONFIG_MULTITHREAD); sqlite3_config(SQLITE_CONFIG_MULTITHREAD); for(i=0; i1 ){ + int iArg; + for(iArg=1; iArg0 ? 255 : 0); usage: - printf("Usage: %s [testname|testprefix*]\n", argv[0]); + printf("Usage: %s [testname|testprefix*]...\n", argv[0]); printf("Available tests are:\n"); for(i=0; i +#include +#include +#include +#include +#include +#include + +/* +** An instance of the following structure is passed into each worker +** thread. +*/ +typedef struct WorkerInfo WorkerInfo; +struct WorkerInfo { + int tid; /* Thread ID */ + int nWorker; /* Total number of workers */ + unsigned wkrFlags; /* Flags */ + sqlite3 *mainDb; /* Database connection of the main thread */ + sqlite3 *db; /* Database connection of this thread */ + int nErr; /* Number of errors seen by this thread */ + int nTest; /* Number of tests run by this thread */ + char *zMsg; /* Message returned by this thread */ + pthread_t id; /* Thread id */ + pthread_mutex_t *pWrMutex; /* Hold this mutex while writing */ +}; + +/* +** Allowed values for WorkerInfo.wkrFlags +*/ +#define TT4_SERIALIZED 0x0000001 /* The --serialized option is used */ +#define TT4_WAL 0x0000002 /* WAL mode in use */ +#define TT4_TRACE 0x0000004 /* Trace activity */ + + +/* +** Report an OOM error and die if the argument is NULL +*/ +static void check_oom(void *x){ + if( x==0 ){ + fprintf(stderr, "out of memory\n"); + exit(1); + } +} + +/* +** Allocate memory. If the allocation fails, print an error message and +** kill the process. +*/ +static void *safe_malloc(int sz){ + void *x = sqlite3_malloc(sz>0?sz:1); + check_oom(x); + return x; +} + +/* +** Print a trace message for a worker +*/ +static void worker_trace(WorkerInfo *p, const char *zFormat, ...){ + va_list ap; + char *zMsg; + if( (p->wkrFlags & TT4_TRACE)==0 ) return; + va_start(ap, zFormat); + zMsg = sqlite3_vmprintf(zFormat, ap); + check_oom(zMsg); + va_end(ap); + fprintf(stderr, "TRACE(%02d): %s\n", p->tid, zMsg); + sqlite3_free(zMsg); +} + +/* +** Prepare a single SQL query +*/ +static sqlite3_stmt *prep_sql(sqlite3 *db, const char *zFormat, ...){ + va_list ap; + char *zSql; + int rc; + sqlite3_stmt *pStmt = 0; + + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + fprintf(stderr, "SQL error (%d,%d): %s\nWhile preparing: [%s]\n", + rc, sqlite3_extended_errcode(db), sqlite3_errmsg(db), zSql); + exit(1); + } + sqlite3_free(zSql); + return pStmt; +} + +/* +** Run a SQL statements. Panic if unable. +*/ +static void run_sql(WorkerInfo *p, const char *zFormat, ...){ + va_list ap; + char *zSql; + int rc; + sqlite3_stmt *pStmt = 0; + int nRetry = 0; + + va_start(ap, zFormat); + zSql = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + check_oom(zSql); + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + fprintf(stderr, "SQL error (%d,%d): %s\nWhile preparing: [%s]\n", + rc, sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zSql); + exit(1); + } + worker_trace(p, "running [%s]", zSql); + while( (rc = sqlite3_step(pStmt))!=SQLITE_DONE ){ + if( (rc&0xff)==SQLITE_BUSY || (rc&0xff)==SQLITE_LOCKED ){ + sqlite3_reset(pStmt); + nRetry++; + if( nRetry<10 ){ + worker_trace(p, "retry %d for [%s]", nRetry, zSql); + sched_yield(); + continue; + }else{ + fprintf(stderr, "Deadlock in thread %d while running [%s]\n", + p->tid, zSql); + exit(1); + } + } + if( rc!=SQLITE_ROW ){ + fprintf(stderr, "SQL error (%d,%d): %s\nWhile running [%s]\n", + rc, sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db), zSql); + exit(1); + } + } + sqlite3_free(zSql); + sqlite3_finalize(pStmt); +} + + +/* +** Open the database connection for WorkerInfo. The order in which +** the files are opened is a function of the tid value. +*/ +static void worker_open_connection(WorkerInfo *p, int iCnt){ + char *zFile; + int x; + int rc; + static const unsigned char aOrder[6][3] = { + { 1, 2, 3}, + { 1, 3, 2}, + { 2, 1, 3}, + { 2, 3, 1}, + { 3, 1, 2}, + { 3, 2, 1} + }; + x = (p->tid + iCnt) % 6; + zFile = sqlite3_mprintf("tt4-test%d.db", aOrder[x][0]); + check_oom(zFile); + worker_trace(p, "open %s", zFile); + rc = sqlite3_open_v2(zFile, &p->db, + SQLITE_OPEN_READWRITE|SQLITE_OPEN_SHAREDCACHE, 0); + if( rc!=SQLITE_OK ){ + fprintf(stderr, "sqlite_open_v2(%s) failed on thread %d\n", + zFile, p->tid); + exit(1); + } + sqlite3_free(zFile); + run_sql(p, "PRAGMA read_uncommitted=ON;"); + sqlite3_busy_timeout(p->db, 10000); + run_sql(p, "PRAGMA synchronous=OFF;"); + run_sql(p, "ATTACH 'tt4-test%d.db' AS aux1", aOrder[x][1]); + run_sql(p, "ATTACH 'tt4-test%d.db' AS aux2", aOrder[x][2]); +} + +/* +** Close the worker database connection +*/ +static void worker_close_connection(WorkerInfo *p){ + if( p->db ){ + worker_trace(p, "close"); + sqlite3_close(p->db); + p->db = 0; + } +} + +/* +** Delete all content in the three databases associated with a +** single thread. Make this happen all in a single transaction if +** inTrans is true, or separately for each database if inTrans is +** false. +*/ +static void worker_delete_all_content(WorkerInfo *p, int inTrans){ + if( inTrans ){ + pthread_mutex_lock(p->pWrMutex); + run_sql(p, "BEGIN"); + run_sql(p, "DELETE FROM t1 WHERE tid=%d", p->tid); + run_sql(p, "DELETE FROM t2 WHERE tid=%d", p->tid); + run_sql(p, "DELETE FROM t3 WHERE tid=%d", p->tid); + run_sql(p, "COMMIT"); + pthread_mutex_unlock(p->pWrMutex); + p->nTest++; + }else{ + pthread_mutex_lock(p->pWrMutex); + run_sql(p, "DELETE FROM t1 WHERE tid=%d", p->tid); + pthread_mutex_unlock(p->pWrMutex); + p->nTest++; + pthread_mutex_lock(p->pWrMutex); + run_sql(p, "DELETE FROM t2 WHERE tid=%d", p->tid); + pthread_mutex_unlock(p->pWrMutex); + p->nTest++; + pthread_mutex_lock(p->pWrMutex); + run_sql(p, "DELETE FROM t3 WHERE tid=%d", p->tid); + pthread_mutex_unlock(p->pWrMutex); + p->nTest++; + } +} + +/* +** Create rows mn through mx in table iTab for the given worker +*/ +static void worker_add_content(WorkerInfo *p, int mn, int mx, int iTab){ + char *zTabDef; + switch( iTab ){ + case 1: zTabDef = "t1(tid,sp,a,b,c)"; break; + case 2: zTabDef = "t2(tid,sp,d,e,f)"; break; + case 3: zTabDef = "t3(tid,sp,x,y,z)"; break; + } + pthread_mutex_lock(p->pWrMutex); + run_sql(p, + "WITH RECURSIVE\n" + " c(i) AS (VALUES(%d) UNION ALL SELECT i+1 FROM c WHERE i<%d)\n" + "INSERT INTO %s SELECT %d, zeroblob(3000), i, printf('%%d',i), i FROM c;", + mn, mx, zTabDef, p->tid + ); + pthread_mutex_unlock(p->pWrMutex); + p->nTest++; +} + +/* +** Set an error message on a worker +*/ +static void worker_error(WorkerInfo *p, const char *zFormat, ...){ + va_list ap; + p->nErr++; + sqlite3_free(p->zMsg); + va_start(ap, zFormat); + p->zMsg = sqlite3_vmprintf(zFormat, ap); + va_end(ap); +} + +/* +** Each thread runs the following function. +*/ +static void *worker_thread(void *pArg){ + WorkerInfo *p = (WorkerInfo*)pArg; + int iOuter; + int i; + int rc; + sqlite3_stmt *pStmt; + + printf("worker %d startup\n", p->tid); fflush(stdout); + for(iOuter=1; iOuter<=p->nWorker; iOuter++){ + worker_open_connection(p, iOuter); + for(i=0; i<4; i++){ + worker_add_content(p, i*100+1, (i+1)*100, (p->tid+iOuter)%3 + 1); + worker_add_content(p, i*100+1, (i+1)*100, (p->tid+iOuter+1)%3 + 1); + worker_add_content(p, i*100+1, (i+1)*100, (p->tid+iOuter+2)%3 + 1); + } + + pStmt = prep_sql(p->db, "SELECT count(a) FROM t1 WHERE tid=%d", p->tid); + worker_trace(p, "query [%s]", sqlite3_sql(pStmt)); + rc = sqlite3_step(pStmt); + if( rc!=SQLITE_ROW ){ + worker_error(p, "Failed to step: %s", sqlite3_sql(pStmt)); + }else if( sqlite3_column_int(pStmt, 0)!=400 ){ + worker_error(p, "Wrong result: %d", sqlite3_column_int(pStmt,0)); + } + sqlite3_finalize(pStmt); + if( p->nErr ) break; + + if( ((iOuter+p->tid)%3)==0 ){ + sqlite3_db_release_memory(p->db); + p->nTest++; + } + + pthread_mutex_lock(p->pWrMutex); + run_sql(p, "BEGIN;"); + run_sql(p, "UPDATE t1 SET c=NULL WHERE a=55"); + run_sql(p, "UPDATE t2 SET f=NULL WHERE d=42"); + run_sql(p, "UPDATE t3 SET z=NULL WHERE x=31"); + run_sql(p, "ROLLBACK;"); + p->nTest++; + pthread_mutex_unlock(p->pWrMutex); + + + if( iOuter==p->tid ){ + pthread_mutex_lock(p->pWrMutex); + run_sql(p, "VACUUM"); + pthread_mutex_unlock(p->pWrMutex); + } + + pStmt = prep_sql(p->db, + "SELECT t1.rowid, t2.rowid, t3.rowid" + " FROM t1, t2, t3" + " WHERE t1.tid=%d AND t2.tid=%d AND t3.tid=%d" + " AND t1.a<>t2.d AND t2.d<>t3.x" + " ORDER BY 1, 2, 3" + ,p->tid, p->tid, p->tid); + worker_trace(p, "query [%s]", sqlite3_sql(pStmt)); + for(i=0; inWorker; i++){ + rc = sqlite3_step(pStmt); + if( rc!=SQLITE_ROW ){ + worker_error(p, "Failed to step: %s", sqlite3_sql(pStmt)); + break; + } + sched_yield(); + } + sqlite3_finalize(pStmt); + if( p->nErr ) break; + + worker_delete_all_content(p, (p->tid+iOuter)%2); + worker_close_connection(p); + p->db = 0; + } + worker_close_connection(p); + printf("worker %d finished\n", p->tid); fflush(stdout); + return 0; +} + +int main(int argc, char **argv){ + int nWorker = 0; /* Number of worker threads */ + int i; /* Loop counter */ + WorkerInfo *aInfo; /* Information for each worker */ + unsigned wkrFlags = 0; /* Default worker flags */ + int nErr = 0; /* Number of errors */ + int nTest = 0; /* Number of tests */ + int rc; /* Return code */ + sqlite3 *db = 0; /* Main database connection */ + pthread_mutex_t wrMutex; /* The write serialization mutex */ + WorkerInfo infoTop; /* WorkerInfo for the main thread */ + WorkerInfo *p; /* Pointer to infoTop */ + + sqlite3_config(SQLITE_CONFIG_MULTITHREAD); + for(i=1; i='1' && z[0]<='9' && nWorker==0 ){ + nWorker = atoi(z); + if( nWorker<2 ){ + fprintf(stderr, "minimum of 2 threads\n"); + exit(1); + } + }else{ + fprintf(stderr, "extra command-line argument: \"%s\"\n", argv[i]); + exit(1); + } + } + if( nWorker==0 ){ + fprintf(stderr, + "usage: %s ?OPTIONS? N\n" + "N is the number of threads and must be at least 2.\n" + "Options:\n" + " --serialized\n" + " --multithread\n" + " --wal\n" + " --trace\n" + ,argv[0] + ); + exit(1); + } + if( !sqlite3_threadsafe() ){ + fprintf(stderr, "requires a threadsafe build of SQLite\n"); + exit(1); + } + sqlite3_initialize(); + sqlite3_enable_shared_cache(1); + pthread_mutex_init(&wrMutex, 0); + + /* Initialize the test database files */ + (void)unlink("tt4-test1.db"); + (void)unlink("tt4-test2.db"); + (void)unlink("tt4-test3.db"); + rc = sqlite3_open("tt4-test1.db", &db); + if( rc!=SQLITE_OK ){ + fprintf(stderr, "Unable to open test database: tt4-test2.db\n"); + exit(1); + } + memset(&infoTop, 0, sizeof(infoTop)); + infoTop.db = db; + infoTop.wkrFlags = wkrFlags; + p = &infoTop; + if( wkrFlags & TT4_WAL ){ + run_sql(p, "PRAGMA journal_mode=WAL"); + } + run_sql(p, "PRAGMA synchronous=OFF"); + run_sql(p, "CREATE TABLE IF NOT EXISTS t1(tid INTEGER, sp, a, b, c)"); + run_sql(p, "CREATE INDEX t1tid ON t1(tid)"); + run_sql(p, "CREATE INDEX t1ab ON t1(a,b)"); + run_sql(p, "ATTACH 'tt4-test2.db' AS 'test2'"); + run_sql(p, "CREATE TABLE IF NOT EXISTS test2.t2(tid INTEGER, sp, d, e, f)"); + run_sql(p, "CREATE INDEX test2.t2tid ON t2(tid)"); + run_sql(p, "CREATE INDEX test2.t2de ON t2(d,e)"); + run_sql(p, "ATTACH 'tt4-test3.db' AS 'test3'"); + run_sql(p, "CREATE TABLE IF NOT EXISTS test3.t3(tid INTEGER, sp, x, y, z)"); + run_sql(p, "CREATE INDEX test3.t3tid ON t3(tid)"); + run_sql(p, "CREATE INDEX test3.t3xy ON t3(x,y)"); + aInfo = safe_malloc( sizeof(*aInfo)*nWorker ); + memset(aInfo, 0, sizeof(*aInfo)*nWorker); + for(i=0; izDb, 0); + for(cnt=0; err.rc==SQLITE_OK && cntxProc(&err, &db, i1); + i2 += (err.rc==SQLITE_OK); + clear_error(&err, SQLITE_LOCKED); + i1++; + } + closedb(&err, &db); + } + + print_and_free_err(&err); + return sqlite3_mprintf("ok %d/%d", i2, i1); +} + +static void stress2_launch_thread_loop( + Error *pErr, /* IN/OUT: Error code */ + Threadset *pThreads, /* Thread set */ + const char *zDb, /* Database name */ + void (*x)(Error*,Sqlite*,int) /* Run this until error or timeout */ +){ + Stress2Ctx *pCtx = sqlite3_malloc(sizeof(Stress2Ctx)); + pCtx->zDb = zDb; + pCtx->xProc = x; + launch_thread(pErr, pThreads, stress2_thread_wrapper, (void*)pCtx); +} + +static void stress2(int nMs){ + struct Stress2Task { + void (*x)(Error*,Sqlite*,int); + } aTask[] = { + { stress2_workload1 }, + { stress2_workload2 }, + { stress2_workload3 }, + { stress2_workload4 }, + { stress2_workload5 }, + { stress2_workload6 }, + { stress2_workload7 }, + { stress2_workload8 }, + { stress2_workload9 }, + { stress2_workload10 }, + { stress2_workload11 }, + { stress2_workload14 }, + { stress2_workload17 }, + }; + const char *zDb = "test.db"; + + int i; + Error err = {0}; + Sqlite db = {0}; + Threadset threads = {0}; + + /* To make sure the db file is empty before commencing */ + opendb(&err, &db, zDb, 1); + sql_script(&err, &db, + "CREATE TABLE IF NOT EXISTS t0(x PRIMARY KEY, y, z);" + "CREATE INDEX IF NOT EXISTS i0 ON t0(y);" + ); + closedb(&err, &db); + + setstoptime(&err, nMs); + sqlite3_enable_shared_cache(1); + + for(i=0; i