Compare commits

...

28 Commits

Author SHA1 Message Date
drh 5942b01611 Make sure the Btree mutex is held when setting the locking mode and the
secure delete flag when attaching a shared-cache database.

FossilOrigin-Name: 6bef7ede2bbf0a51729e1943b0b0c895cb57c718
2014-12-12 00:40:58 +00:00
drh 5f5c1a0186 Merge the cell overflow page number cache thread race fix from trunk.
FossilOrigin-Name: cefad47ec2ad58d7ecd58bab9a261e4d5816cd69
2014-12-12 00:26:59 +00:00
drh 18b67f3f0d Fix a bug in the threadtest4.c program. Remove the keyinfo cache as it provides
minimal performance improvements, and then only at SQL preparation time, not
at runtime, and it has problems with data races in shared-cache mode.  We might
later add the keyinfo cache back but only enable it when shared-cache mode
is off.

FossilOrigin-Name: b7489f9451628c68f1dfc1d457fc161a0921c631
2014-12-12 00:20:37 +00:00
drh 1e57430e63 Add the threadtest4.c test program. Not yet working.
FossilOrigin-Name: ec3a74469ca2f0f3fb7d82a05fdac7500354e78f
2014-12-11 19:29:42 +00:00
dan 857536623a Fix a race condition to do with very large index keys in shared-cache mode.
FossilOrigin-Name: fc157dd7f18c94b7ae5f155e1b4a5d7714b7da8c
2014-12-11 16:38:18 +00:00
drh 0a3520c0f4 Fix a typo in the documentation for sqlite3_threadsafe().
FossilOrigin-Name: 258e747bb7e3a2bc46f932cc2b06c2689d43aeb0
2014-12-11 15:27:04 +00:00
dan 1f03b86523 Fix a typo causing a test error in e_walhook.test.
FossilOrigin-Name: d9f916ba09f1a61684b4d59548ab6cf71cdb6a37
2014-12-10 20:57:20 +00:00
dan 6e45e0c8d7 Add new test file e_walhook.test.
FossilOrigin-Name: 2eb6d3e4fbe388ef28e4b7b846e9e8a4361517a4
2014-12-10 20:29:49 +00:00
mistachkin 684ec98fe3 Revise mutex handling by the sqlite3_win32_reset_heap() function.
FossilOrigin-Name: eacb3b7baa910e84f984b8e45695a2a2f5a4c861
2014-12-10 17:34:48 +00:00
drh 6f7febffa2 Fix typos in the documentation for sqlite3_table_column_metadata().
No changes to code.

FossilOrigin-Name: 3528f8dd39acace8eeb7337994c8617313f4b04b
2014-12-10 04:58:43 +00:00
drh 45d1b20639 Fix the sqlite3_table_column_metadata() routine so that it gives the correct
answer for the "rowid" column in a WITHOUT ROWID table.  Enhance it so that
it can be used to check for the existence of a table by setting the column
name parameter to NULL.  The routine is now included in the build by
default, even without the SQLITE_ENABLE_COLUMN_METADATA compile-time option.

FossilOrigin-Name: cf9be419a16156a9814e1378bb49b780de977343
2014-12-09 22:24:42 +00:00
dan 9778bd7292 Add extra tests to e_walckpt.test.
FossilOrigin-Name: 84f9581019961efa31297f8be48427b17bcca857
2014-12-09 20:13:40 +00:00
drh 80cdfd199b Lower the default SQLITE_SORTER_PMASZ value back to 10, where it has been for
the past couple of releases.  Applications that need a larger value can set
one.

FossilOrigin-Name: 1ba8911c18f2fe34c20dc42f25a8f3c1c798fa7a
2014-12-09 19:16:41 +00:00
drh 8c3026ecf3 Make the sqlite3_table_column_metadata() interface available by default and
without requiring the SQLITE_ENABLE_COLUMN_METADATA compile-time option.
Other sqlite3_column_* interfaces that have a run-time penalty even if they
are unused still require the SQLITE_ENABLE_COLUMN_METADATA option at
compile-time.

FossilOrigin-Name: 4f7549ff924b8ed8f90fc447cd4be11421453984
2014-12-09 19:07:29 +00:00
drh f5471925c9 Add the SQLITE_REVERSE_UNORDERED_SELECTS compile-time option that causes the
"PRAGMA reverse_unordered_selects" setting to be on by default.

FossilOrigin-Name: 75a803e694d2c4e67579f45d54a0aaf120411a6e
2014-12-09 15:12:11 +00:00
drh b11c3f22b8 Increase the default minimum PMA size for multi-threaded sorting from 10x
the page size to 250x the page size.  Provide the SQLITE_SORTER_PMASZ
compile-time option to change this default.

FossilOrigin-Name: b05340fe3cd5f1676a55023228dc8d1a92de5936
2014-12-09 14:42:49 +00:00
mistachkin 0fbd7350a1 Fix harmless compiler warning.
FossilOrigin-Name: e97b7a8b4df784d148fefb9554da0f511e351d9f
2014-12-09 04:26:56 +00:00
dan 785a38f066 Add extra tests to e_walckpt.test.
FossilOrigin-Name: d6832aa24c8d93b4532a651b86605bd0a0d0aa78
2014-12-08 20:29:23 +00:00
dan 0235a0331f Add missing mutex calls around a call to sqlite3SchemaGet() within sqlite3_open().
FossilOrigin-Name: 45415899545767888d36dcc0bafaf0ef415d94c2
2014-12-08 20:20:16 +00:00
drh b88c7b550c Add a missing mutex around calls to clear the KeyInfo cache when closing
a database connection.

FossilOrigin-Name: 7047ce32a234484b8ba15311e6560aa74ff692c9
2014-12-08 18:08:45 +00:00
dan 8f1eb8a1cb Avoid accessing a single uninitialized byte when moving a rare 3-byte cell from an internal page to a leaf. This was not actually causing a problem, just a valgrind warning.
FossilOrigin-Name: 6aeece19a235344be2537e66a3fe08b1febfb5a0
2014-12-06 14:56:49 +00:00
drh d52d52be80 Fix a (harmless) uninitialized variable reference in b-tree balancing for
auto-vacuumed tables with overflow pages.

FossilOrigin-Name: dd1dd4451f468599f7a0c2f7b5ee6125db3bb152
2014-12-06 02:05:44 +00:00
drh cd934c3d2c Avoid a potential NULL pointer deference in the sqlite3_stmt_scanstatus()
logic.

FossilOrigin-Name: 42d44adc13d52b8dd571c9375eb48298123d5a60
2014-12-05 21:18:19 +00:00
dan 5cca94ea71 Fix a buffer overread that might occur in analyze.c if SQLITE_ENABLE_STAT4 was defined.
FossilOrigin-Name: c1ae1268b9023a771fda98f26bf451c6066fe70b
2014-12-05 21:04:26 +00:00
dan 2928d32791 Add new test file e_walckpt.test. Still some tests to come.
FossilOrigin-Name: e4db3db3a65ecfd4069a40d436aa7a5512d61a30
2014-12-05 20:46:19 +00:00
drh fe32daaeaa Make sure the WhereTerm objects are fully zeroed when they are allocated.
FossilOrigin-Name: fdb667335c2250239a66143aec4235325dec8274
2014-12-05 19:50:58 +00:00
drh ebdb81dd45 The KeyInfo cache must be cleared before closing the btree, not after.
Revised fix for ticket [e4a18565a36884b00edf].

FossilOrigin-Name: 7ed3346e8c10dbf52fd44ab69900699d4f7ad3fd
2014-12-05 15:31:33 +00:00
drh 318dc0b425 When closing a (shared-cache) database connection, be sure to clear out
all KeyInfo objects cached on Index objects. 
Fix for ticket [e4a18565a36884b00edf].

FossilOrigin-Name: adca7688de20ff40d8ddf2107dfaf92af3873b83
2014-12-05 14:44:57 +00:00
23 changed files with 1722 additions and 158 deletions
+26 -21
View File
@@ -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
View File
@@ -1 +1 @@
71f589e3f82a07513425e212072f32748c0732d4
6bef7ede2bbf0a51729e1943b0b0c895cb57c718
+1 -1
View File
@@ -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--;
+2
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);
+16 -6
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{
@@ -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
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
-1
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
+15 -43
View File
@@ -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
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();
+26 -24
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
-1
View File
@@ -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 */
+1
View File
@@ -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
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
@@ -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
-1
View File
@@ -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
View File
@@ -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
View File
@@ -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
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
+752
View File
@@ -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
+200
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
+7
View File
@@ -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;
+436
View File
@@ -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;
}
+74
View File
@@ -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);
}
+99
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, 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);
}