Merge threading fixes from trunk into the sessions branch.

FossilOrigin-Name: 9817a2864eebe2dc90ce505fe0faa8b069ff48ff
This commit is contained in:
drh 2014-12-16 01:05:38 +00:00
commit 032f461900
30 changed files with 2057 additions and 274 deletions

13
main.mk
View File

@ -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)

View File

@ -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

View File

@ -1 +1 @@
f0940c73bd13cb49a3d229ce2517736324392cfb
9817a2864eebe2dc90ce505fe0faa8b069ff48ff

View File

@ -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);

View File

@ -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 );

View File

@ -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; i<nCol; i++){
char *zColl = pIdx->azColl[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; i<nCol; i++){
char *zColl = pIdx->azColl[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

View File

@ -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

View File

@ -1032,16 +1032,6 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
for(j=0; j<db->nDb; 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; iDb<db->nDb; 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; iCol<pTab->nCol; 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.

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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 && i<db->nDb; i++){
if( DbHasProperty(db, i, DB_SchemaLoaded) || i==1 ) continue;
rc = sqlite3InitOne(db, i, pzErrMsg);

View File

@ -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);
** </blockquote>)^
**
** ^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:
**
** <pre>
** data type: "INTEGER"
@ -5203,13 +5211,9 @@ SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N);
** auto increment: 0
** </pre>)^
**
** ^(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

View File

@ -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 */

View File

@ -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));

View File

@ -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 },

View File

@ -400,7 +400,10 @@ static int doWalCallbacks(sqlite3 *db){
for(i=0; i<db->nDb; 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);

View File

@ -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 */

View File

@ -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;
}

View File

@ -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

201
test/e_walauto.test Normal file
View File

@ -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

View File

@ -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

200
test/e_walhook.test Normal file
View File

@ -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

View File

@ -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 {

View File

@ -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; i<WALTHREAD3_NTHREAD; i++){
launch_thread(&err, &threads, walthread3_thread, i);
launch_thread(&err, &threads, walthread3_thread, INT2PTR(i));
}
join_all_threads(&err, &threads);
print_and_free_err(&err);
}
static char *walthread4_reader_thread(int iTid, int iArg){
static char *walthread4_reader_thread(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
@ -1108,7 +1128,7 @@ static char *walthread4_reader_thread(int iTid, int iArg){
return 0;
}
static char *walthread4_writer_thread(int iTid, int iArg){
static char *walthread4_writer_thread(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
i64 iRow = 1;
@ -1148,7 +1168,7 @@ static void walthread4(int nMs){
print_and_free_err(&err);
}
static char *walthread5_thread(int iTid, int iArg){
static char *walthread5_thread(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
i64 nRow;
@ -1280,7 +1300,7 @@ static void cgt_pager_1(int nMs){
** is an attempt to find a bug reported to us.
*/
static char *dynamic_triggers_1(int iTid, int iArg){
static char *dynamic_triggers_1(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
int nDrop = 0;
@ -1326,12 +1346,13 @@ static char *dynamic_triggers_1(int iTid, int iArg){
nDrop++;
}
}
closedb(&err, &db);
print_and_free_err(&err);
return sqlite3_mprintf("%d created, %d dropped", nCreate, nDrop);
}
static char *dynamic_triggers_2(int iTid, int iArg){
static char *dynamic_triggers_2(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
i64 iVal = 0;
@ -1352,6 +1373,7 @@ static char *dynamic_triggers_2(int iTid, int iArg){
nDelete++;
} while( iVal );
}
closedb(&err, &db);
print_and_free_err(&err);
return sqlite3_mprintf("%d inserts, %d deletes", nInsert, nDelete);
@ -1376,15 +1398,16 @@ static void dynamic_triggers(int nMs){
"CREATE TABLE t8(x, y);"
"CREATE TABLE t9(x, y);"
);
closedb(&err, &db);
setstoptime(&err, nMs);
sqlite3_enable_shared_cache(1);
launch_thread(&err, &threads, dynamic_triggers_2, 0);
launch_thread(&err, &threads, dynamic_triggers_2, 0);
sqlite3_enable_shared_cache(0);
sleep(2);
sqlite3_enable_shared_cache(0);
launch_thread(&err, &threads, dynamic_triggers_2, 0);
launch_thread(&err, &threads, dynamic_triggers_1, 0);
@ -1398,6 +1421,9 @@ static void dynamic_triggers(int nMs){
#include "tt3_checkpoint.c"
#include "tt3_index.c"
#include "tt3_lookaside1.c"
#include "tt3_vacuum.c"
#include "tt3_stress.c"
int main(int argc, char **argv){
struct ThreadTest {
@ -1419,34 +1445,31 @@ int main(int argc, char **argv){
{ checkpoint_starvation_2, "checkpoint_starvation_2", 10000 },
{ create_drop_index_1, "create_drop_index_1", 10000 },
{ lookaside1, "lookaside1", 10000 },
{ vacuum1, "vacuum1", 10000 },
{ stress1, "stress1", 10000 },
{ stress2, "stress2", 60000 },
};
int i;
char *zTest = 0;
int nTest = 0;
int bTestfound = 0;
int bPrefix = 0;
if( argc>2 ) 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; i<sizeof(aTest)/sizeof(aTest[0]); i++){
char const *z = aTest[i].zTest;
int n = strlen(z);
if( !zTest || ((bPrefix || n==nTest) && 0==strncmp(zTest, z, nTest)) ){
printf("Running %s for %d seconds...\n", z, aTest[i].nMs/1000);
aTest[i].xTest(aTest[i].nMs);
bTestfound++;
if( argc>1 ){
int iArg;
for(iArg=1; iArg<argc; iArg++){
if( 0==sqlite3_strglob(argv[iArg], z) ) break;
}
if( iArg==argc ) continue;
}
printf("Running %s for %d seconds...\n", z, aTest[i].nMs/1000);
aTest[i].xTest(aTest[i].nMs);
bTestfound++;
}
if( bTestfound==0 ) goto usage;
@ -1454,7 +1477,7 @@ int main(int argc, char **argv){
return (nGlobalErr>0 ? 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<sizeof(aTest)/sizeof(aTest[0]); i++){
printf(" %s\n", aTest[i].zTest);

484
test/threadtest4.c Normal file
View File

@ -0,0 +1,484 @@
/*
** 2014-12-11
**
** 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 a simple standalone program used to stress the
** SQLite library when accessing the same set of databases simultaneously
** from multiple threads in shared-cache mode.
**
** This test program runs on unix-like systems only. It uses pthreads.
** To compile:
**
** gcc -g -Wall -I. threadtest4.c sqlite3.c -ldl -lpthread
**
** To run:
**
** ./a.out 10
**
** The argument is the number of threads. There are also options, such
** as -wal and -multithread and -serialized.
**
** Consider also compiling with clang instead of gcc and adding the
** -fsanitize=thread option.
*/
#include "sqlite3.h"
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
/*
** 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; i<p->nWorker; 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<argc; i++){
const char *z = argv[i];
if( z[0]=='-' ){
if( z[1]=='-' && z[2]!=0 ) z++;
if( strcmp(z,"-multithread")==0 ){
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
wkrFlags &= ~TT4_SERIALIZED;
}else if( strcmp(z,"-serialized")==0 ){
sqlite3_config(SQLITE_CONFIG_SERIALIZED);
wkrFlags |= TT4_SERIALIZED;
}else if( strcmp(z,"-wal")==0 ){
wkrFlags |= TT4_WAL;
}else if( strcmp(z,"-trace")==0 ){
wkrFlags |= TT4_TRACE;
}else{
fprintf(stderr, "unknown command-line option: %s\n", argv[i]);
exit(1);
}
}else if( z[0]>='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; i<nWorker; i++){
aInfo[i].tid = i+1;
aInfo[i].nWorker = nWorker;
aInfo[i].wkrFlags = wkrFlags;
aInfo[i].mainDb = db;
aInfo[i].pWrMutex = &wrMutex;
rc = pthread_create(&aInfo[i].id, 0, worker_thread, &aInfo[i]);
if( rc!=0 ){
fprintf(stderr, "thread creation failed for thread %d\n", i+1);
exit(1);
}
sched_yield();
}
for(i=0; i<nWorker; i++){
pthread_join(aInfo[i].id, 0);
printf("Joined thread %d: %d errors in %d tests",
aInfo[i].tid, aInfo[i].nErr, aInfo[i].nTest);
if( aInfo[i].zMsg ){
printf(": %s\n", aInfo[i].zMsg);
}else{
printf("\n");
}
nErr += aInfo[i].nErr;
nTest += aInfo[i].nTest;
fflush(stdout);
}
sqlite3_close(db);
sqlite3_free(aInfo);
printf("Total %d errors in %d tests\n", nErr, nTest);
return nErr;
}

View File

@ -66,7 +66,7 @@ static int checkpoint_starvation_walhook(
return SQLITE_OK;
}
static char *checkpoint_starvation_reader(int iTid, int iArg){
static char *checkpoint_starvation_reader(int iTid, void *pArg){
Error err = {0};
Sqlite db = {0};

View File

@ -14,7 +14,7 @@
*/
static char *create_drop_index_thread(int iTid, int iArg){
static char *create_drop_index_thread(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
@ -22,22 +22,22 @@ static char *create_drop_index_thread(int iTid, int iArg){
opendb(&err, &db, "test.db", 0);
sql_script(&err, &db,
"DROP INDEX IF EXISTS i1;"
"DROP INDEX IF EXISTS i2;"
"DROP INDEX IF EXISTS i3;"
"DROP INDEX IF EXISTS i4;"
"CREATE INDEX IF NOT EXISTS i1 ON t1(a);"
"CREATE INDEX IF NOT EXISTS i2 ON t1(b);"
"CREATE INDEX IF NOT EXISTS i3 ON t1(c);"
"CREATE INDEX IF NOT EXISTS i4 ON t1(d);"
"CREATE INDEX IF NOT EXISTS i1 ON t11(a);"
"CREATE INDEX IF NOT EXISTS i2 ON t11(b);"
"CREATE INDEX IF NOT EXISTS i3 ON t11(c);"
"CREATE INDEX IF NOT EXISTS i4 ON t11(d);"
"SELECT * FROM t1 ORDER BY a;"
"SELECT * FROM t1 ORDER BY b;"
"SELECT * FROM t1 ORDER BY c;"
"SELECT * FROM t1 ORDER BY d;"
"SELECT * FROM t11 ORDER BY a;"
"SELECT * FROM t11 ORDER BY b;"
"SELECT * FROM t11 ORDER BY c;"
"SELECT * FROM t11 ORDER BY d;"
);
clear_error(&err, SQLITE_LOCKED);
closedb(&err, &db);
}
@ -53,9 +53,9 @@ static void create_drop_index_1(int nMs){
opendb(&err, &db, "test.db", 1);
sql_script(&err, &db,
"CREATE TABLE t1(a, b, c, d);"
"CREATE TABLE t11(a, b, c, d);"
"WITH data(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM data WHERE x<100) "
"INSERT INTO t1 SELECT x,x,x,x FROM data;"
"INSERT INTO t11 SELECT x,x,x,x FROM data;"
);
closedb(&err, &db);
@ -67,8 +67,8 @@ static void create_drop_index_1(int nMs){
launch_thread(&err, &threads, create_drop_index_thread, 0);
launch_thread(&err, &threads, create_drop_index_thread, 0);
launch_thread(&err, &threads, create_drop_index_thread, 0);
sqlite3_enable_shared_cache(0);
join_all_threads(&err, &threads);
sqlite3_enable_shared_cache(0);
print_and_free_err(&err);
}

99
test/tt3_lookaside1.c Normal file
View File

@ -0,0 +1,99 @@
/*
** 2014 December 9
**
** 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.
**
*************************************************************************
**
** lookaside1
*/
/*
** The test in this file attempts to expose a specific race condition
** that is suspected to exist at time of writing.
*/
static char *lookaside1_thread_reader(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
opendb(&err, &db, "test.db", 0);
while( !timetostop(&err) ){
sqlite3_stmt *pStmt = 0;
int rc;
sqlite3_prepare_v2(db.db, "SELECT 1 FROM t1", -1, &pStmt, 0);
while( sqlite3_step(pStmt)==SQLITE_ROW ){
execsql(&err, &db, "SELECT length(x||y||z) FROM t2");
}
rc = sqlite3_finalize(pStmt);
if( err.rc==SQLITE_OK && rc!=SQLITE_OK ){
sqlite_error(&err, &db, "finalize");
}
}
closedb(&err, &db);
print_and_free_err(&err);
return sqlite3_mprintf("ok");
}
static char *lookaside1_thread_writer(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
opendb(&err, &db, "test.db", 0);
do{
sql_script(&err, &db,
"BEGIN;"
"UPDATE t3 SET i=i+1 WHERE x=1;"
"ROLLBACK;"
);
}while( !timetostop(&err) );
closedb(&err, &db);
print_and_free_err(&err);
return sqlite3_mprintf("ok");
}
static void lookaside1(int nMs){
Error err = {0};
Sqlite db = {0};
Threadset threads = {0};
opendb(&err, &db, "test.db", 1);
sql_script(&err, &db,
"CREATE TABLE t1(x PRIMARY KEY) WITHOUT ROWID;"
"WITH data(x,y) AS ("
" SELECT 1, quote(randomblob(750)) UNION ALL "
" SELECT x*2, y||y FROM data WHERE x<5) "
"INSERT INTO t1 SELECT y FROM data;"
"CREATE TABLE t3(x PRIMARY KEY,i) WITHOUT ROWID;"
"INSERT INTO t3 VALUES(1, 1);"
"CREATE TABLE t2(x,y,z);"
"INSERT INTO t2 VALUES(randomblob(50), randomblob(50), randomblob(50));"
);
closedb(&err, &db);
setstoptime(&err, nMs);
sqlite3_enable_shared_cache(1);
launch_thread(&err, &threads, lookaside1_thread_reader, 0);
launch_thread(&err, &threads, lookaside1_thread_reader, 0);
launch_thread(&err, &threads, lookaside1_thread_reader, 0);
launch_thread(&err, &threads, lookaside1_thread_reader, 0);
launch_thread(&err, &threads, lookaside1_thread_reader, 0);
launch_thread(&err, &threads, lookaside1_thread_writer, 0);
join_all_threads(&err, &threads);
sqlite3_enable_shared_cache(0);
print_and_free_err(&err);
}

368
test/tt3_stress.c Normal file
View File

@ -0,0 +1,368 @@
/*
** 2014 December 9
**
** 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.
**
*************************************************************************
**
**
*/
/*
** Thread 1. CREATE and DROP a table.
*/
static char *stress_thread_1(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
opendb(&err, &db, "test.db", 0);
while( !timetostop(&err) ){
sql_script(&err, &db, "CREATE TABLE IF NOT EXISTS t1(a PRIMARY KEY, b)");
clear_error(&err, SQLITE_LOCKED);
sql_script(&err, &db, "DROP TABLE IF EXISTS t1");
clear_error(&err, SQLITE_LOCKED);
}
closedb(&err, &db);
print_and_free_err(&err);
return sqlite3_mprintf("ok");
}
/*
** Thread 2. Open and close database connections.
*/
static char *stress_thread_2(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
while( !timetostop(&err) ){
opendb(&err, &db, "test.db", 0);
sql_script(&err, &db, "SELECT * FROM sqlite_master;");
clear_error(&err, SQLITE_LOCKED);
closedb(&err, &db);
}
print_and_free_err(&err);
return sqlite3_mprintf("ok");
}
/*
** Thread 3. Attempt many small SELECT statements.
*/
static char *stress_thread_3(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
int i1 = 0;
int i2 = 0;
opendb(&err, &db, "test.db", 0);
while( !timetostop(&err) ){
sql_script(&err, &db, "SELECT * FROM t1 ORDER BY a;");
i1++;
if( err.rc ) i2++;
clear_error(&err, SQLITE_LOCKED);
clear_error(&err, SQLITE_ERROR);
}
closedb(&err, &db);
print_and_free_err(&err);
return sqlite3_mprintf("read t1 %d/%d attempts", i2, i1);
}
/*
** Thread 5. Attempt INSERT statements.
*/
static char *stress_thread_4(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
int i1 = 0;
int i2 = 0;
int iArg = PTR2INT(pArg);
opendb(&err, &db, "test.db", 0);
while( !timetostop(&err) ){
if( iArg ){
closedb(&err, &db);
opendb(&err, &db, "test.db", 0);
}
sql_script(&err, &db,
"WITH loop(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM loop LIMIT 200) "
"INSERT INTO t1 VALUES(randomblob(60), randomblob(60));"
);
i1++;
if( err.rc ) i2++;
clear_error(&err, SQLITE_LOCKED);
clear_error(&err, SQLITE_ERROR);
}
closedb(&err, &db);
print_and_free_err(&err);
return sqlite3_mprintf("wrote t1 %d/%d attempts", i2, i1);
}
/*
** Thread 6. Attempt DELETE operations.
*/
static char *stress_thread_5(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
int iArg = PTR2INT(pArg);
int i1 = 0;
int i2 = 0;
opendb(&err, &db, "test.db", 0);
while( !timetostop(&err) ){
i64 i = (i1 % 4);
if( iArg ){
closedb(&err, &db);
opendb(&err, &db, "test.db", 0);
}
execsql(&err, &db, "DELETE FROM t1 WHERE (rowid % 4)==:i", &i);
i1++;
if( err.rc ) i2++;
clear_error(&err, SQLITE_LOCKED);
}
closedb(&err, &db);
print_and_free_err(&err);
return sqlite3_mprintf("deleted from t1 %d/%d attempts", i2, i1);
}
static void stress1(int nMs){
Error err = {0};
Threadset threads = {0};
setstoptime(&err, nMs);
sqlite3_enable_shared_cache(1);
launch_thread(&err, &threads, stress_thread_1, 0);
launch_thread(&err, &threads, stress_thread_1, 0);
launch_thread(&err, &threads, stress_thread_2, 0);
launch_thread(&err, &threads, stress_thread_2, 0);
launch_thread(&err, &threads, stress_thread_3, 0);
launch_thread(&err, &threads, stress_thread_3, 0);
launch_thread(&err, &threads, stress_thread_4, 0);
launch_thread(&err, &threads, stress_thread_4, 0);
launch_thread(&err, &threads, stress_thread_5, 0);
launch_thread(&err, &threads, stress_thread_5, (void*)1);
join_all_threads(&err, &threads);
sqlite3_enable_shared_cache(0);
print_and_free_err(&err);
}
/**************************************************************************
***************************************************************************
** Start of test case "stress2"
*/
/*
** 1. CREATE TABLE statements.
** 2. DROP TABLE statements.
** 3. Small SELECT statements.
** 4. Big SELECT statements.
** 5. Small INSERT statements.
** 6. Big INSERT statements.
** 7. Small UPDATE statements.
** 8. Big UPDATE statements.
** 9. Small DELETE statements.
** 10. Big DELETE statements.
** 11. VACUUM.
** 14. Integrity-check.
** 17. Switch the journal mode from delete to wal and back again.
** 19. Open and close database connections rapidly.
*/
#define STRESS2_TABCNT 5 /* count1 in SDS test */
#define STRESS2_COUNT2 200 /* count2 in SDS test */
#define STRESS2_COUNT3 57 /* count2 in SDS test */
static void stress2_workload1(Error *pErr, Sqlite *pDb, int i){
int iTab = (i % (STRESS2_TABCNT-1)) + 1;
sql_script_printf(pErr, pDb,
"CREATE TABLE IF NOT EXISTS t%d(x PRIMARY KEY, y, z);", iTab
);
}
static void stress2_workload2(Error *pErr, Sqlite *pDb, int i){
int iTab = (i % (STRESS2_TABCNT-1)) + 1;
sql_script_printf(pErr, pDb, "DROP TABLE IF EXISTS t%d;", iTab);
}
static void stress2_workload3(Error *pErr, Sqlite *pDb, int i){
sql_script(pErr, pDb, "SELECT * FROM t0 WHERE z = 'small'");
}
static void stress2_workload4(Error *pErr, Sqlite *pDb, int i){
sql_script(pErr, pDb, "SELECT * FROM t0 WHERE z = 'big'");
}
static void stress2_workload5(Error *pErr, Sqlite *pDb, int i){
sql_script(pErr, pDb,
"INSERT INTO t0 VALUES(hex(random()), hex(randomblob(200)), 'small');"
);
}
static void stress2_workload6(Error *pErr, Sqlite *pDb, int i){
sql_script(pErr, pDb,
"INSERT INTO t0 VALUES(hex(random()), hex(randomblob(57)), 'big');"
);
}
static void stress2_workload7(Error *pErr, Sqlite *pDb, int i){
sql_script_printf(pErr, pDb,
"UPDATE t0 SET y = hex(randomblob(200)) "
"WHERE x LIKE hex((%d %% 5)) AND z='small';"
,i
);
}
static void stress2_workload8(Error *pErr, Sqlite *pDb, int i){
sql_script_printf(pErr, pDb,
"UPDATE t0 SET y = hex(randomblob(57)) "
"WHERE x LIKE hex(%d %% 5) AND z='big';"
,i
);
}
static void stress2_workload9(Error *pErr, Sqlite *pDb, int i){
sql_script_printf(pErr, pDb,
"DELETE FROM t0 WHERE x LIKE hex(%d %% 5) AND z='small';", i
);
}
static void stress2_workload10(Error *pErr, Sqlite *pDb, int i){
sql_script_printf(pErr, pDb,
"DELETE FROM t0 WHERE x LIKE hex(%d %% 5) AND z='big';", i
);
}
static void stress2_workload11(Error *pErr, Sqlite *pDb, int i){
sql_script(pErr, pDb, "VACUUM");
}
static void stress2_workload14(Error *pErr, Sqlite *pDb, int i){
sql_script(pErr, pDb, "PRAGMA integrity_check");
}
static void stress2_workload17(Error *pErr, Sqlite *pDb, int i){
sql_script_printf(pErr, pDb,
"PRAGMA journal_mode = %q", (i%2) ? "delete" : "wal"
);
}
static char *stress2_workload19(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
const char *zDb = (const char*)pArg;
while( !timetostop(&err) ){
opendb(&err, &db, zDb, 0);
sql_script(&err, &db, "SELECT * FROM sqlite_master;");
clear_error(&err, SQLITE_LOCKED);
closedb(&err, &db);
}
print_and_free_err(&err);
return sqlite3_mprintf("ok");
}
typedef struct Stress2Ctx Stress2Ctx;
struct Stress2Ctx {
const char *zDb;
void (*xProc)(Error*, Sqlite*, int);
};
static char *stress2_thread_wrapper(int iTid, void *pArg){
Stress2Ctx *pCtx = (Stress2Ctx*)pArg;
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
int i1 = 0;
int i2 = 0;
while( !timetostop(&err) ){
int cnt;
opendb(&err, &db, pCtx->zDb, 0);
for(cnt=0; err.rc==SQLITE_OK && cnt<STRESS2_TABCNT; cnt++){
pCtx->xProc(&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<sizeof(aTask)/sizeof(aTask[0]); i++){
stress2_launch_thread_loop(&err, &threads, zDb, aTask[i].x);
}
launch_thread(&err, &threads, stress2_workload19, (void*)zDb);
launch_thread(&err, &threads, stress2_workload19, (void*)zDb);
join_all_threads(&err, &threads);
sqlite3_enable_shared_cache(0);
print_and_free_err(&err);
}

90
test/tt3_vacuum.c Normal file
View File

@ -0,0 +1,90 @@
/*
** 2014 December 9
**
** 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 contains multi-threaded tests that use shared-cache and
** the VACUUM command.
**
** Tests:
**
** vacuum1
**
*/
static char *vacuum1_thread_writer(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
opendb(&err, &db, "test.db", 0);
i64 i = 0;
while( !timetostop(&err) ){
i++;
/* Insert lots of rows. Then delete some. */
execsql(&err, &db,
"WITH loop(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM loop WHERE i<100) "
"INSERT INTO t1 SELECT randomblob(50), randomblob(2500) FROM loop"
);
/* Delete lots of rows */
execsql(&err, &db, "DELETE FROM t1 WHERE rowid = :i", &i);
clear_error(&err, SQLITE_LOCKED);
/* Select the rows */
execsql(&err, &db, "SELECT * FROM t1 ORDER BY x");
clear_error(&err, SQLITE_LOCKED);
}
closedb(&err, &db);
print_and_free_err(&err);
return sqlite3_mprintf("ok");
}
static char *vacuum1_thread_vacuumer(int iTid, void *pArg){
Error err = {0}; /* Error code and message */
Sqlite db = {0}; /* SQLite database connection */
opendb(&err, &db, "test.db", 0);
do{
sql_script(&err, &db, "VACUUM");
clear_error(&err, SQLITE_LOCKED);
}while( !timetostop(&err) );
closedb(&err, &db);
print_and_free_err(&err);
return sqlite3_mprintf("ok");
}
static void vacuum1(int nMs){
Error err = {0};
Sqlite db = {0};
Threadset threads = {0};
opendb(&err, &db, "test.db", 1);
sql_script(&err, &db,
"CREATE TABLE t1(x PRIMARY KEY, y BLOB);"
"CREATE INDEX i1 ON t1(y);"
);
closedb(&err, &db);
setstoptime(&err, nMs);
sqlite3_enable_shared_cache(1);
launch_thread(&err, &threads, vacuum1_thread_writer, 0);
launch_thread(&err, &threads, vacuum1_thread_writer, 0);
launch_thread(&err, &threads, vacuum1_thread_writer, 0);
launch_thread(&err, &threads, vacuum1_thread_vacuumer, 0);
join_all_threads(&err, &threads);
sqlite3_enable_shared_cache(0);
print_and_free_err(&err);
}