Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5942b01611 | |||
| 5f5c1a0186 | |||
| 18b67f3f0d | |||
| 1e57430e63 | |||
| 857536623a | |||
| 0a3520c0f4 | |||
| 1f03b86523 | |||
| 6e45e0c8d7 | |||
| 684ec98fe3 | |||
| 6f7febffa2 | |||
| 45d1b20639 | |||
| 9778bd7292 | |||
| 80cdfd199b | |||
| 8c3026ecf3 | |||
| f5471925c9 | |||
| b11c3f22b8 | |||
| 0fbd7350a1 | |||
| 785a38f066 | |||
| 0235a0331f | |||
| b88c7b550c | |||
| 8f1eb8a1cb | |||
| d52d52be80 | |||
| cd934c3d2c | |||
| 5cca94ea71 | |||
| 2928d32791 | |||
| fe32daaeaa | |||
| ebdb81dd45 | |||
| 318dc0b425 |
@@ -1,5 +1,5 @@
|
||||
C Improved\scomment\son\sthe\ssharedB.test\stest\sscript.
|
||||
D 2014-12-05T14:34:30.550
|
||||
C Make\ssure\sthe\sBtree\smutex\sis\sheld\swhen\ssetting\sthe\slocking\smode\sand\sthe\nsecure\sdelete\sflag\swhen\sattaching\sa\sshared-cache\sdatabase.
|
||||
D 2014-12-12T00:40:58.882
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in 6c4f961fa91d0b4fa121946a19f9e5eac2f2f809
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
@@ -167,16 +167,16 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
|
||||
F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786
|
||||
F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
|
||||
F src/alter.c ba266a779bc7ce10e52e59e7d3dc79fa342e8fdb
|
||||
F src/analyze.c f7d774356ba5a14e7ad4fb637681af16875ad88f
|
||||
F src/attach.c f4e94df2d1826feda65eb0939f7f6f5f923a0ad9
|
||||
F src/analyze.c 7a2986e6ea8247e5f21aca3d0b584598f58d84fe
|
||||
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 44b58cd798a32579816ce274e415de262df9843d
|
||||
F src/btree.c ab4b60fcf9920d862ff4d96efb1d605e4e7701a0
|
||||
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
|
||||
@@ -194,8 +194,8 @@ F src/insert.c 5b9243a33726008cc4132897d2be371db12a13be
|
||||
F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
|
||||
F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e
|
||||
F src/lempar.c 7274c97d24bb46631e504332ccd3bd1b37841770
|
||||
F src/loadext.c de741e66e5ddc1598d904d7289239696e40ed994
|
||||
F src/main.c fa9f0e511a64caf66b8e7bb835c55172e7d61527
|
||||
F src/loadext.c 86bd4e2fccd520b748cba52492ab60c4a770f660
|
||||
F src/main.c 1f40f66165a6609203a5ff7ecb0292b90b302130
|
||||
F src/malloc.c 740db54387204c9a2eb67c6d98e68b08e9ef4eab
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
F src/mem1.c faf615aafd8be74a71494dfa027c113ea5c6615f
|
||||
@@ -214,7 +214,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
|
||||
@@ -230,15 +230,15 @@ F src/resolve.c f6c46d3434439ab2084618d603e6d6dbeb0d6ada
|
||||
F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e
|
||||
F src/select.c f377fb8a5c73c10678ea74f3400f7913943e3d75
|
||||
F src/shell.c 45d9c9bd7cde07845af957f2d849933b990773cf
|
||||
F src/sqlite.h.in 6ec654324cb490ea3d8a7be28b8c7d37fe4ad282
|
||||
F src/sqlite.h.in 116dc731361549ee3fc79dcebace11b57d24dcfd
|
||||
F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
|
||||
F src/sqlite3ext.h 17d487c3c91b0b8c584a32fbeb393f6f795eea7d
|
||||
F src/sqliteInt.h 28049b803b74a7f73242a8226915ea00ebb1309f
|
||||
F src/sqliteInt.h 073d54f7a631b978b66d50d255c84549fb9e5429
|
||||
F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
|
||||
F src/status.c 81712116e826b0089bb221b018929536b2b5406f
|
||||
F src/table.c f142bba7903e93ca8d113a5b8877a108ad1a27dc
|
||||
F src/tclsqlite.c 0a874655dd39a9875e39c5d3c464db662171d228
|
||||
F src/test1.c c24d7f67252348d756773a6dbe23a61b4552f709
|
||||
F src/tclsqlite.c c6a21c64da1490e14d53cdc2062d1e2e57942622
|
||||
F src/test1.c 7e806af12d7915445e46407d32b275d8ca9db4e7
|
||||
F src/test2.c 98049e51a17dc62606a99a9eb95ee477f9996712
|
||||
F src/test3.c 1c0e5d6f080b8e33c1ce8b3078e7013fdbcd560c
|
||||
F src/test4.c 9b32d22f5f150abe23c1830e2057c4037c45b3df
|
||||
@@ -294,17 +294,17 @@ F src/vacuum.c 9b30ec729337dd012ed88d4c292922c8ef9cf00c
|
||||
F src/vdbe.c 1a9e671c9cfc259e4d2affc71f7df4a4c00a842c
|
||||
F src/vdbe.h 6fc69d9c5e146302c56e163cb4b31d1ee64a18c3
|
||||
F src/vdbeInt.h 9bb69ff2447c34b6ccc58b34ec35b615f86ead78
|
||||
F src/vdbeapi.c 07acb615d1e4170e71fc1b0d087f3c53a1ad8e83
|
||||
F src/vdbeapi.c f8dd2d33a30938188fc292d524e88a91f2e65887
|
||||
F src/vdbeaux.c 6f7f39c3fcf0f5923758df8561bb5d843908a553
|
||||
F src/vdbeblob.c 4af4bfb71f6df7778397b4a0ebc1879793276778
|
||||
F src/vdbemem.c 31d8eabb0cd78bfeab4e5124c7363c3e9e54db9f
|
||||
F src/vdbesort.c 42c166f7ca78cb643c7f4e4bdfa83c59d363d1a6
|
||||
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 d67fe69dd1bb32ad3d488a8c5bc803a29814f357
|
||||
F src/where.c d46de821bc604a4fd36fa3928c086950e91aafb1
|
||||
F src/whereInt.h d3633e9b592103241b74b0ec76185f3e5b8b62e0
|
||||
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
|
||||
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
|
||||
@@ -404,7 +404,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
|
||||
@@ -478,6 +478,8 @@ 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 3116a98fa0dd9b2c9e493de7c59730adfe436746
|
||||
F test/e_walhook.test da3ea8b3483d1af72190337bda50155a91a4b664
|
||||
F test/enc.test e54531cd6bf941ee6760be041dff19a104c7acea
|
||||
F test/enc2.test 83437a79ba1545a55fb549309175c683fb334473
|
||||
F test/enc3.test 90683ad0e6ea587b9d5542ca93568af9a9858c40
|
||||
@@ -910,7 +912,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 0ed13e09690f6204d7455fac3b0e8ece490f6eef
|
||||
F test/threadtest3.c 2b6e07e915c383c250a5b531cf6ef163a3047d7e
|
||||
F test/threadtest4.c 38cb574939d5e0c8bd3baa5eb45def2ac6da4db4
|
||||
F test/tkt-02a8e81d44.test 6c80d9c7514e2a42d4918bf87bf6bc54f379110c
|
||||
F test/tkt-26ff0c2d1e.test 888324e751512972c6e0d1a09df740d8f5aaf660
|
||||
F test/tkt-2a5629202f.test 0521bd25658428baa26665aa53ffed9367d33af2
|
||||
@@ -1074,6 +1077,8 @@ 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 14f4b0cc3f4c05353e25778cf531c9d6e3b0d27f
|
||||
F test/tt3_lookaside1.c 0b5b79ba37f21a1eb849cd4a54eed367f4d4aaaf
|
||||
F test/types.test bf816ce73c7dfcfe26b700c19f97ef4050d194ff
|
||||
F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84
|
||||
F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a
|
||||
@@ -1224,7 +1229,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 ffea3e905adc108d2dc37f5d6da2024f0389f176
|
||||
R 2f7d3cb7a2e1e65c7403b8d51d471007
|
||||
P cefad47ec2ad58d7ecd58bab9a261e4d5816cd69
|
||||
R 43ea10bc03cfab6b8cae2a465b94aee2
|
||||
U drh
|
||||
Z 951231dae63f2db1845bdae375b332da
|
||||
Z 01d198dabe70d93c23f2c971bf866fd9
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
71f589e3f82a07513425e212072f32748c0732d4
|
||||
6bef7ede2bbf0a51729e1943b0b0c895cb57c718
|
||||
+1
-1
@@ -1596,7 +1596,7 @@ static void initAvgEq(Index *pIdx){
|
||||
i64 nSum100 = 0; /* Number of terms contributing to sumEq */
|
||||
i64 nDist100; /* Number of distinct values in index */
|
||||
|
||||
if( pIdx->aiRowEst==0 || pIdx->aiRowEst[iCol+1]==0 ){
|
||||
if( !pIdx->aiRowEst || iCol>=pIdx->nKeyCol || pIdx->aiRowEst[iCol+1]==0 ){
|
||||
nRow = pFinal->anLt[iCol];
|
||||
nDist100 = (i64)100 * pFinal->anDLt[iCol];
|
||||
nSample--;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+16
-6
@@ -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{
|
||||
@@ -6785,7 +6787,7 @@ static int balance_nonroot(
|
||||
|
||||
/* EVIDENCE-OF: R-28375-38319 SQLite will never request a scratch buffer
|
||||
** that is more than 6 times the database page size. */
|
||||
assert( szScratch<=6*pBt->pageSize );
|
||||
assert( szScratch<=6*(int)pBt->pageSize );
|
||||
apCell = sqlite3ScratchMalloc( szScratch );
|
||||
if( apCell==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
@@ -6859,7 +6861,11 @@ static int balance_nonroot(
|
||||
}else{
|
||||
assert( leafCorrection==4 );
|
||||
if( szCell[nCell]<4 ){
|
||||
/* Do not allow any cells smaller than 4 bytes. */
|
||||
/* Do not allow any cells smaller than 4 bytes. If a smaller cell
|
||||
** does exist, pad it with 0x00 bytes. */
|
||||
assert( szCell[nCell]==3 );
|
||||
assert( apCell[nCell]==&pTemp[iSpace1-3] );
|
||||
pTemp[iSpace1++] = 0x00;
|
||||
szCell[nCell] = 4;
|
||||
}
|
||||
}
|
||||
@@ -7094,7 +7100,11 @@ static int balance_nonroot(
|
||||
** if sibling page iOld had the same page number as pNew, and if
|
||||
** pCell really was a part of sibling page iOld (not a divider or
|
||||
** overflow cell), we can skip updating the pointer map entries. */
|
||||
if( pNew->pgno!=aPgno[iOld] || pCell<aOld || pCell>=&aOld[usableSize] ){
|
||||
if( iOld>=nNew
|
||||
|| pNew->pgno!=aPgno[iOld]
|
||||
|| pCell<aOld
|
||||
|| pCell>=&aOld[usableSize]
|
||||
){
|
||||
if( !leafCorrection ){
|
||||
ptrmapPut(pBt, get4byte(pCell), PTRMAP_BTREE, pNew->pgno, &rc);
|
||||
}
|
||||
|
||||
+20
-30
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+15
-43
@@ -1034,13 +1034,7 @@ void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
|
||||
if( pDb->pBt ){
|
||||
sqlite3BtreeClose(pDb->pBt);
|
||||
pDb->pBt = 0;
|
||||
if( j!=1 && pDb->pSchema ){
|
||||
/* Must clear the KeyInfo cache. See ticket [e4a18565a36884b00edf] */
|
||||
for(i=sqliteHashFirst(&pDb->pSchema->idxHash); i; i=sqliteHashNext(i)){
|
||||
Index *pIdx = sqliteHashData(i);
|
||||
sqlite3KeyInfoUnref(pIdx->pKeyInfo);
|
||||
pIdx->pKeyInfo = 0;
|
||||
}
|
||||
if( j!=1 ){
|
||||
pDb->pSchema = 0;
|
||||
}
|
||||
}
|
||||
@@ -2165,32 +2159,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.
|
||||
@@ -2234,7 +2202,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(),
|
||||
@@ -2738,6 +2705,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);
|
||||
@@ -2786,7 +2756,9 @@ static int openDatabase(
|
||||
sqlite3Error(db, rc);
|
||||
goto opendb_out;
|
||||
}
|
||||
sqlite3BtreeEnter(db->aDb[0].pBt);
|
||||
db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt);
|
||||
sqlite3BtreeLeave(db->aDb[0].pBt);
|
||||
db->aDb[1].pSchema = sqlite3SchemaGet(db, 0);
|
||||
|
||||
/* The default safety_level for the main database is 'full'; for the temp
|
||||
@@ -3140,7 +3112,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 */
|
||||
@@ -3180,11 +3151,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];
|
||||
@@ -3193,8 +3161,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3247,7 +3220,6 @@ error_out:
|
||||
sqlite3_mutex_leave(db->mutex);
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Sleep for a little while. Return the amount of time slept.
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
+26
-24
@@ -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
|
||||
|
||||
@@ -1784,7 +1784,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 */
|
||||
|
||||
@@ -635,6 +635,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));
|
||||
|
||||
+9
-10
@@ -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
|
||||
|
||||
@@ -5704,15 +5702,18 @@ static int test_wal_checkpoint_v2(
|
||||
if( objc==4 ){
|
||||
zDb = Tcl_GetString(objv[3]);
|
||||
}
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db)
|
||||
|| Tcl_GetIndexFromObj(interp, objv[2], aMode, "mode", 0, &eMode)
|
||||
){
|
||||
if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) || (
|
||||
TCL_OK!=Tcl_GetIntFromObj(0, objv[2], &eMode)
|
||||
&& TCL_OK!=Tcl_GetIndexFromObj(interp, objv[2], aMode, "mode", 0, &eMode)
|
||||
)){
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
rc = sqlite3_wal_checkpoint_v2(db, zDb, eMode, &nLog, &nCkpt);
|
||||
if( rc!=SQLITE_OK && rc!=SQLITE_BUSY ){
|
||||
Tcl_SetResult(interp, (char *)sqlite3_errmsg(db), TCL_VOLATILE);
|
||||
const char *zErrCode = sqlite3ErrName(rc);
|
||||
Tcl_ResetResult(interp);
|
||||
Tcl_AppendResult(interp, zErrCode, " - ", (char *)sqlite3_errmsg(db), 0);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
@@ -6776,9 +6777,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
|
||||
|
||||
@@ -580,7 +580,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);
|
||||
|
||||
+5
-3
@@ -450,7 +450,9 @@ struct SorterRecord {
|
||||
|
||||
/* The minimum PMA size is set to this value multiplied by the database
|
||||
** page size in bytes. */
|
||||
#define SORTER_MIN_WORKING 10
|
||||
#ifndef SQLITE_SORTER_PMASZ
|
||||
# define SQLITE_SORTER_PMASZ 10
|
||||
#endif
|
||||
|
||||
/* Maximum number of PMAs that a single MergeEngine can merge */
|
||||
#define SORTER_MAX_MERGE_COUNT 16
|
||||
@@ -849,9 +851,9 @@ int sqlite3VdbeSorterInit(
|
||||
}
|
||||
|
||||
if( !sqlite3TempInMemory(db) ){
|
||||
pSorter->mnPmaSize = SORTER_MIN_WORKING * pgsz;
|
||||
pSorter->mnPmaSize = SQLITE_SORTER_PMASZ * pgsz;
|
||||
mxCache = db->aDb[0].pSchema->cache_size;
|
||||
if( mxCache<SORTER_MIN_WORKING ) mxCache = SORTER_MIN_WORKING;
|
||||
if( mxCache<SQLITE_SORTER_PMASZ ) mxCache = SQLITE_SORTER_PMASZ;
|
||||
pSorter->mxPmaSize = MIN((i64)mxCache*pgsz, SQLITE_MAX_MXPMASIZE);
|
||||
|
||||
/* EVIDENCE-OF: R-26747-61719 When the application provides any amount of
|
||||
|
||||
+2
-2
@@ -222,6 +222,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u8 wtFlags){
|
||||
sqlite3DbFree(db, pOld);
|
||||
}
|
||||
pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]);
|
||||
memset(&pWC->a[pWC->nTerm], 0, sizeof(pWC->a[0])*(pWC->nSlot-pWC->nTerm));
|
||||
}
|
||||
pTerm = &pWC->a[idx = pWC->nTerm++];
|
||||
if( p && ExprHasProperty(p, EP_Unlikely) ){
|
||||
@@ -2947,7 +2948,7 @@ static void addScanStatus(
|
||||
){
|
||||
const char *zObj = 0;
|
||||
WhereLoop *pLoop = pLvl->pWLoop;
|
||||
if( (pLoop->wsFlags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){
|
||||
if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){
|
||||
zObj = pLoop->u.btree.pIndex->zName;
|
||||
}else{
|
||||
zObj = pSrclist->a[pLvl->iFrom].zName;
|
||||
@@ -3940,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;
|
||||
}
|
||||
|
||||
+28
-12
@@ -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
|
||||
|
||||
@@ -0,0 +1,752 @@
|
||||
# 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/lock_common.tcl
|
||||
source $testdir/wal_common.tcl
|
||||
set testprefix e_walckpt
|
||||
|
||||
# The following two commands are used to determine if any of the files
|
||||
# "test.db", "test.db2" and "test.db3" are modified by a test case.
|
||||
#
|
||||
# The [save_db_hashes] command saves a hash of the current contents of
|
||||
# all three files in global variables. The [compare_db_hashes] compares
|
||||
# the current contents with the saved hashes and returns a list of the
|
||||
# files that have changed.
|
||||
#
|
||||
proc save_db_hashes {} {
|
||||
global H
|
||||
foreach f {test.db test.db2 test.db3} {
|
||||
set H($f) 0
|
||||
catch { set H($f) [md5file $f] }
|
||||
}
|
||||
}
|
||||
proc compare_db_hashes {} {
|
||||
global H
|
||||
set ret [list]
|
||||
foreach f {test.db test.db2 test.db3} {
|
||||
set expect 0
|
||||
catch { set expect [md5file $f] }
|
||||
if {$H($f) != $expect} { lappend ret $f }
|
||||
}
|
||||
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:
|
||||
#
|
||||
# 1) Using sqlite3_wal_checkpoint_v2()
|
||||
# 2) Using "PRAGMA wal_checkpoint"
|
||||
# 3) Using sqlite3_wal_checkpoint() in place of checkpoint_v2(PASSIVE)
|
||||
#
|
||||
# Cases (2) and (3) are to show that the following statements are
|
||||
# correct, respectively:
|
||||
#
|
||||
# EVIDENCE-OF: R-36706-10507 The PRAGMA wal_checkpoint command can be
|
||||
# used to invoke this interface from SQL.
|
||||
#
|
||||
# EVIDENCE-OF: R-41613-20553 The sqlite3_wal_checkpoint(D,X) is
|
||||
# equivalent to
|
||||
# sqlite3_wal_checkpoint_v2(D,X,SQLITE_CHECKPOINT_PASSIVE,0,0).
|
||||
#
|
||||
foreach {tn script} {
|
||||
1 {
|
||||
proc checkpoint {db mode args} {
|
||||
eval wal_checkpoint_v2 [list $db] [list $mode] $args
|
||||
}
|
||||
}
|
||||
|
||||
2 {
|
||||
proc checkpoint {db mode args} {
|
||||
set sql "PRAGMA wal_checkpoint = $mode"
|
||||
if {[llength $args] && [lindex $args 0]!=""} {
|
||||
set sql "PRAGMA [lindex $args 0].wal_checkpoint = $mode"
|
||||
}
|
||||
set rc [catch { $db eval $sql } msg]
|
||||
if {$rc} {
|
||||
regsub {database} $msg {database:} msg
|
||||
error "[sqlite3_errcode $db] - $msg"
|
||||
}
|
||||
set msg
|
||||
}
|
||||
}
|
||||
|
||||
3 {
|
||||
proc checkpoint {db mode args} {
|
||||
if {$mode == "passive"} {
|
||||
set rc [eval sqlite3_wal_checkpoint [list $db] $args]
|
||||
if {$rc != "SQLITE_OK"} {
|
||||
error "$rc - [sqlite3_errmsg $db]"
|
||||
}
|
||||
} else {
|
||||
eval wal_checkpoint_v2 [list $db] [list $mode] $args
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} {
|
||||
|
||||
eval $script
|
||||
|
||||
reset_db
|
||||
forcedelete test.db2 test.db3 test.db4
|
||||
execsql {
|
||||
ATTACH 'test.db2' AS aux;
|
||||
ATTACH 'test.db3' AS aux2;
|
||||
ATTACH 'test.db4' AS aux3;
|
||||
CREATE TABLE t1(x);
|
||||
CREATE TABLE aux.t2(x);
|
||||
CREATE TABLE aux2.t3(x);
|
||||
CREATE TABLE aux3.t4(x);
|
||||
PRAGMA main.journal_mode = WAL;
|
||||
PRAGMA aux.journal_mode = WAL;
|
||||
PRAGMA aux2.journal_mode = WAL;
|
||||
/* Leave aux4 in rollback mode */
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-49787-09095 The sqlite3_wal_checkpoint_v2(D,X,M,L,C)
|
||||
# interface runs a checkpoint operation on database X of database
|
||||
# connection D in mode M. Status information is written back into
|
||||
# integers pointed to by L and C.
|
||||
#
|
||||
# Tests 1, 2 and 3 below verify the "on database X" part of the
|
||||
# above. Other parts of this requirement are tested below.
|
||||
#
|
||||
# EVIDENCE-OF: R-00653-06026 If parameter zDb is NULL or points to a
|
||||
# zero length string, then the specified operation is attempted on all
|
||||
# WAL databases attached to database connection db.
|
||||
#
|
||||
# Tests 4 and 5 below test this.
|
||||
#
|
||||
foreach {tn2 zDb dblist} {
|
||||
1 main test.db
|
||||
2 aux test.db2
|
||||
3 aux2 test.db3
|
||||
4 "" {test.db test.db2 test.db3}
|
||||
5 - {test.db test.db2 test.db3}
|
||||
6 temp {}
|
||||
} {
|
||||
do_test $tn.1.$tn2 {
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES(1);
|
||||
INSERT INTO t2 VALUES(2);
|
||||
INSERT INTO t3 VALUES(3);
|
||||
}
|
||||
save_db_hashes
|
||||
|
||||
if {$zDb == "-"} {
|
||||
checkpoint db passive
|
||||
} else {
|
||||
checkpoint db passive $zDb
|
||||
}
|
||||
|
||||
compare_db_hashes
|
||||
} $dblist
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-38207-48996 If zDb is not NULL (or a zero length
|
||||
# string) and is not the name of any attached database, SQLITE_ERROR is
|
||||
# returned to the caller.
|
||||
do_test $tn.2.1 {
|
||||
list [catch { checkpoint db passive notadb } msg] $msg
|
||||
} {1 {SQLITE_ERROR - unknown database: notadb}}
|
||||
|
||||
# EVIDENCE-OF: R-14303-42483 If database zDb is the name of an attached
|
||||
# database that is not in WAL mode, SQLITE_OK is returned and both
|
||||
# *pnLog and *pnCkpt set to -1.
|
||||
#
|
||||
if {$tn==3} {
|
||||
# With sqlite3_wal_checkpoint() the two output variables cannot be
|
||||
# tested. So just test that no error is returned when attempting to
|
||||
# checkpoint a db in rollback mode.
|
||||
do_test $tn.2.2.a { checkpoint db passive aux3 } {}
|
||||
} else {
|
||||
do_test $tn.2.2.b { checkpoint db passive aux3 } {0 -1 -1}
|
||||
}
|
||||
|
||||
# EVIDENCE-OF: R-62028-47212 All calls obtain an exclusive "checkpoint"
|
||||
# lock on the database file.
|
||||
db close
|
||||
testvfs tvfs
|
||||
tvfs filter xShmLock
|
||||
tvfs script filelock
|
||||
proc filelock {method file handle details} {
|
||||
# Test for an exclusive checkpoint lock. A checkpoint lock locks a
|
||||
# single byte starting at offset 1.
|
||||
if {$details == "1 1 lock exclusive"} { set ::seen_checkpoint_lock 1 }
|
||||
}
|
||||
sqlite3 db test.db -vfs tvfs
|
||||
do_test $tn.3.1 {
|
||||
execsql { INSERT INTO t1 VALUES('xyz') }
|
||||
unset -nocomplain ::seen_checkpoint_lock
|
||||
checkpoint db passive
|
||||
set ::seen_checkpoint_lock
|
||||
} {1}
|
||||
db close
|
||||
tvfs delete
|
||||
reset_db
|
||||
|
||||
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# EVIDENCE-OF: R-10421-19736 If any other process is running a
|
||||
# checkpoint operation at the same time, the lock cannot be obtained and
|
||||
# SQLITE_BUSY is returned.
|
||||
#
|
||||
# EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured,
|
||||
# it will not be invoked in this case.
|
||||
#
|
||||
testvfs tvfs
|
||||
tvfs filter xWrite
|
||||
sqlite3 db test.db -vfs tvfs
|
||||
sqlite3 db2 test.db -vfs tvfs
|
||||
|
||||
do_test $tn.3.2.1 {
|
||||
db2 eval {
|
||||
PRAGMA journal_mode = WAL;
|
||||
CREATE TABLE t1(x, y);
|
||||
INSERT INTO t1 VALUES(1,2);
|
||||
INSERT INTO t1 VALUES(3,4);
|
||||
INSERT INTO t1 VALUES(5,6);
|
||||
}
|
||||
file size test.db-wal
|
||||
} [wal_file_size 5 1024]
|
||||
|
||||
|
||||
# Connection [db] runs a checkpoint. During this checkpoint, each
|
||||
# time it calls xWrite() to write a page into the database file, we
|
||||
# attempt to start a checkpoint using [db2]. According to the
|
||||
# first requirement being tested, this should return SQLITE_BUSY. According
|
||||
# to the second, the busy-handler belonging to [db2] should not be
|
||||
# invoked.
|
||||
#
|
||||
set ::write_count 0
|
||||
set ::write_errors [list]
|
||||
proc busy_callback {args} {
|
||||
lappend ::write_errors "busy handler called!"
|
||||
}
|
||||
proc write_callback {args} {
|
||||
set rc [catch {checkpoint db2 passive} msg]
|
||||
if {0==[regexp "database is locked" $msg] && $msg!="1 -1 -1"} {
|
||||
lappend ::write_errors "$rc $msg"
|
||||
}
|
||||
incr ::write_count
|
||||
}
|
||||
db2 busy busy_callback
|
||||
tvfs script write_callback
|
||||
|
||||
do_test $tn.3.2.2 {
|
||||
db eval {SELECT * FROM sqlite_master}
|
||||
checkpoint db full
|
||||
set ::write_count
|
||||
} {2}
|
||||
|
||||
do_test $tn.3.2.3 {
|
||||
set ::write_errors
|
||||
} {}
|
||||
|
||||
db close
|
||||
db2 close
|
||||
tvfs delete
|
||||
|
||||
proc busy_handler {mode busy_handler_mode n} {
|
||||
incr ::busy_handler_counter
|
||||
switch -- $busy_handler_mode {
|
||||
1 {
|
||||
# Do nothing. Do not block.
|
||||
return 1
|
||||
}
|
||||
|
||||
2 {
|
||||
# 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. 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
|
||||
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 }}]]
|
||||
catch { db5 eval ROLLBACK }
|
||||
}
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
catch { db close }
|
||||
forcedelete test.db
|
||||
testvfs tvfs
|
||||
sqlite3 db test.db -vfs tvfs
|
||||
#tvfs filter xSync
|
||||
tvfs script tvfs_callback
|
||||
|
||||
do_execsql_test $tp.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE TABLE t2(a, b);
|
||||
PRAGMA journal_mode = wal;
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
INSERT INTO t1 VALUES(3, 4);
|
||||
INSERT INTO t1 VALUES(5, 6);
|
||||
} {wal}
|
||||
|
||||
# Open a reader on the current database snapshot.
|
||||
do_test $tp.1 {
|
||||
sqlite3 db2 test.db -vfs tvfs
|
||||
execsql {
|
||||
BEGIN;
|
||||
SELECT * FROM t1 UNION ALL SELECT * FROM t2;
|
||||
} db2
|
||||
} {1 2 3 4 5 6}
|
||||
|
||||
# Open a writer. Write a transaction. Then begin, but do not commit,
|
||||
# a second transaction.
|
||||
do_test $tp.2 {
|
||||
sqlite3 db3 test.db -vfs tvfs
|
||||
execsql {
|
||||
INSERT INTO t2 VALUES(7, 8);
|
||||
BEGIN;
|
||||
INSERT INTO t2 VALUES(9, 10);
|
||||
SELECT * FROM t1 UNION ALL SELECT * FROM t2;
|
||||
} db3
|
||||
} {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].
|
||||
#
|
||||
db busy [list busy_handler $mode $busy_handler_mode]
|
||||
set ::sync_counter 0
|
||||
set ::busy_handler_counter 0
|
||||
set ::read_ok -1
|
||||
set ::write_ok -1
|
||||
set ::seen_writer_lock 0
|
||||
|
||||
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
|
||||
# without waiting for any database readers or writers to finish, then
|
||||
# sync the database file if all frames in the log were checkpointed.
|
||||
#
|
||||
# "As many frames as possible" means all but the last two transactions
|
||||
# (the two that write to table t2, of which the scond is unfinished).
|
||||
# So copying the db file only we see the t1 change, but not the t2
|
||||
# modifications.
|
||||
#
|
||||
# The busy handler is not invoked (see below) and the db reader and
|
||||
# writer are still active - so the checkpointer did not wait for either
|
||||
# readers or writers. As a result the checkpoint was not finished and
|
||||
# so the db file is not synced.
|
||||
#
|
||||
# EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked
|
||||
# in the SQLITE_CHECKPOINT_PASSIVE mode.
|
||||
#
|
||||
# It's not. Test case "$tp.6".
|
||||
#
|
||||
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 $tp.5 { set ::sync_counter } 0
|
||||
do_test $tp.6 { set ::busy_handler_counter } 0
|
||||
db4 close
|
||||
|
||||
db2 eval COMMIT
|
||||
db3 eval COMMIT
|
||||
|
||||
# EVIDENCE-OF: R-65499-53765 On the other hand, passive mode might leave
|
||||
# the checkpoint unfinished if there are concurrent readers or writers.
|
||||
#
|
||||
# The reader and writer have now dropped their locks. And so a
|
||||
# checkpoint now is able to checkpoint more frames. Showing that the
|
||||
# attempt above was left "unfinished".
|
||||
#
|
||||
# Also, because the checkpoint finishes this time, the db is synced.
|
||||
# Which is part of R-16333-64433 above.
|
||||
#
|
||||
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}
|
||||
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" || $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.
|
||||
#
|
||||
# The test below shows that both the reader and writer have
|
||||
# finished:
|
||||
#
|
||||
# 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}} \
|
||||
{1 {cannot commit - no transaction is active}} \
|
||||
]
|
||||
|
||||
# EVIDENCE-OF: R-29177-48281 It then checkpoints all frames in the log
|
||||
# file and syncs the database file.
|
||||
#
|
||||
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 $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.
|
||||
#
|
||||
# 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
|
||||
tvfs delete
|
||||
}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint
|
||||
# mode:
|
||||
#
|
||||
# Valid checkpoint modes are 0, 1, 2 and 3.
|
||||
#
|
||||
sqlite3 db test.db
|
||||
foreach {tn mode res} {
|
||||
0 -1001 {1 {SQLITE_MISUSE - not an error}}
|
||||
1 -1 {1 {SQLITE_MISUSE - not an error}}
|
||||
2 0 {0 {0 -1 -1}}
|
||||
3 1 {0 {0 -1 -1}}
|
||||
4 2 {0 {0 -1 -1}}
|
||||
5 3 {0 {0 -1 -1}}
|
||||
6 4 {1 {SQLITE_MISUSE - not an error}}
|
||||
7 114 {1 {SQLITE_MISUSE - not an error}}
|
||||
8 1000000 {1 {SQLITE_MISUSE - not an error}}
|
||||
} {
|
||||
do_test 4.$tn {
|
||||
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
|
||||
|
||||
@@ -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
|
||||
@@ -1394,7 +1394,11 @@ static void dynamic_triggers(int nMs){
|
||||
print_and_free_err(&err);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#include "tt3_checkpoint.c"
|
||||
#include "tt3_index.c"
|
||||
#include "tt3_lookaside1.c"
|
||||
|
||||
int main(int argc, char **argv){
|
||||
struct ThreadTest {
|
||||
@@ -1414,6 +1418,9 @@ int main(int argc, char **argv){
|
||||
|
||||
{ checkpoint_starvation_1, "checkpoint_starvation_1", 10000 },
|
||||
{ checkpoint_starvation_2, "checkpoint_starvation_2", 10000 },
|
||||
|
||||
{ create_drop_index_1, "create_drop_index_1", 10000 },
|
||||
{ lookaside1, "lookaside1", 10000 },
|
||||
};
|
||||
|
||||
int i;
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
** 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 -o tt4 -I. threadtest4.c sqlite3.c -ldl -lpthread
|
||||
**
|
||||
** To run:
|
||||
**
|
||||
** ./tt4 10
|
||||
**
|
||||
** The argument is the number of threads.
|
||||
*/
|
||||
#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 */
|
||||
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<=4; 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));
|
||||
}
|
||||
if( p->nErr ) break;
|
||||
sqlite3_finalize(pStmt);
|
||||
|
||||
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"
|
||||
,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].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;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
** 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.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** create_drop_index_1
|
||||
*/
|
||||
|
||||
|
||||
static char *create_drop_index_thread(int iTid, int iArg){
|
||||
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,
|
||||
|
||||
"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);"
|
||||
|
||||
"SELECT * FROM t1 ORDER BY a;"
|
||||
"SELECT * FROM t1 ORDER BY b;"
|
||||
"SELECT * FROM t1 ORDER BY c;"
|
||||
"SELECT * FROM t1 ORDER BY d;"
|
||||
);
|
||||
|
||||
closedb(&err, &db);
|
||||
}
|
||||
|
||||
print_and_free_err(&err);
|
||||
return sqlite3_mprintf("ok");
|
||||
}
|
||||
|
||||
static void create_drop_index_1(int nMs){
|
||||
Error err = {0};
|
||||
Sqlite db = {0};
|
||||
Threadset threads = {0};
|
||||
|
||||
opendb(&err, &db, "test.db", 1);
|
||||
sql_script(&err, &db,
|
||||
"CREATE TABLE t1(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;"
|
||||
);
|
||||
closedb(&err, &db);
|
||||
|
||||
setstoptime(&err, nMs);
|
||||
|
||||
sqlite3_enable_shared_cache(1);
|
||||
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);
|
||||
launch_thread(&err, &threads, create_drop_index_thread, 0);
|
||||
launch_thread(&err, &threads, create_drop_index_thread, 0);
|
||||
|
||||
join_all_threads(&err, &threads);
|
||||
sqlite3_enable_shared_cache(0);
|
||||
print_and_free_err(&err);
|
||||
}
|
||||
@@ -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, int iArg){
|
||||
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, int iArg){
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user