Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b0cfe5343 | |||
| bab38cbc9a | |||
| 538ed8e376 | |||
| b4c4601adf | |||
| 60dd40a62c | |||
| 2ac63b9812 | |||
| bf431367f3 | |||
| 730859568d | |||
| c9bcad12ea | |||
| e376cac1d1 | |||
| b7bcf5c16c | |||
| 31349393ba | |||
| 8df9974169 | |||
| f427296520 | |||
| 920a129de3 | |||
| 34d02325be | |||
| aa189cf00f | |||
| 2dab053e22 | |||
| 0561247d07 | |||
| 1381bbc262 |
@@ -69,6 +69,7 @@ LIBOBJ+= vdbe.o parse.o \
|
||||
notify.o opcodes.o os.o os_unix.o os_win.o \
|
||||
pager.o pcache.o pcache1.o pragma.o prepare.o printf.o \
|
||||
random.o resolve.o rowset.o rtree.o select.o sqlite3rbu.o status.o \
|
||||
server.o \
|
||||
table.o threads.o tokenize.o treeview.o trigger.o \
|
||||
update.o userauth.o util.o vacuum.o \
|
||||
vdbeapi.o vdbeaux.o vdbeblob.o vdbemem.o vdbesort.o \
|
||||
@@ -144,6 +145,8 @@ SRC = \
|
||||
$(TOP)/src/resolve.c \
|
||||
$(TOP)/src/rowset.c \
|
||||
$(TOP)/src/select.c \
|
||||
$(TOP)/src/server.c \
|
||||
$(TOP)/src/server.h \
|
||||
$(TOP)/src/status.c \
|
||||
$(TOP)/src/shell.c \
|
||||
$(TOP)/src/sqlite.h.in \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
C Fix\sa\scollision\sof\sthe\s"B0"\sidentifier\sname\sbetween\sthe\stermios.h\sheader\nfile\sand\sthe\sSHA3\simplementation\sin\sthe\sshell.
|
||||
D 2017-05-04T11:13:50.417
|
||||
C Fix\sbugs\sin\stest\sprogram\stserver.c.
|
||||
D 2017-06-28T20:12:41.779
|
||||
F Makefile.in 1cc758ce3374a32425e4d130c2fe7b026b20de5b8843243de75f087c0a2661fb
|
||||
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
|
||||
F Makefile.msc 6a8c838220f7c00820e1fc0ac1bccaaa8e5676067e1dbfa1bafa7a4ffecf8ae6
|
||||
@@ -326,7 +326,7 @@ F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
|
||||
F main.mk 9abb506e717887d57f754bae139b85c1a06d6f2ac25b589f3e792e310567f278
|
||||
F main.mk 0116e0f17f9bb71a111a41fc560c677bba20c557e4c4c11cebf66124c472b22d
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
@@ -345,7 +345,7 @@ F src/auth.c 930b376a9c56998557367e6f7f8aaeac82a2a792
|
||||
F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b
|
||||
F src/bitvec.c 17ea48eff8ba979f1f5b04cc484c7bb2be632f33
|
||||
F src/btmutex.c 0e9ce2d56159b89b9bc8e197e023ee11e39ff8ca
|
||||
F src/btree.c 8c1fd4cfa2b0bf021386e0a1f4e30b64eea7a2c1bc2e0c3e5901a626b1ab6aa9
|
||||
F src/btree.c ca26f48d5c9f2f1e930b062bb1e550ea8d26c9b64522eb2003e10c226ecfc884
|
||||
F src/btree.h 80f518c0788be6cec8d9f8e13bd8e380df299d2b5e4ac340dc887b0642647cfc
|
||||
F src/btreeInt.h a392d353104b4add58b4a59cb185f5d5693dde832c565b77d8d4c343ed98f610
|
||||
F src/build.c 4026a9c554b233e50c5e9ad46963e676cf54dd2306d952aa1eaa07a1bc9ce14f
|
||||
@@ -389,8 +389,8 @@ F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586
|
||||
F src/os_unix.c 30e2c43e4955db990e5b5a81e901f8aa74cc8820
|
||||
F src/os_win.c 2a6c73eef01c51a048cc4ddccd57f981afbec18a
|
||||
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
|
||||
F src/pager.c ff1232b3088a39806035ecfac4fffeb22717d80b
|
||||
F src/pager.h f2a99646c5533ffe11afa43e9e0bea74054e4efa
|
||||
F src/pager.c f957574dc2f8a66d8ee2b5fd978ecd98760d6669182a023a3901e98ab5e4df33
|
||||
F src/pager.h ea05992c581c3366279fb1d436944604b4be17208ebb41fa407306e5e4b34205
|
||||
F src/parse.y 0513387ce02fea97897d8caef82d45f347818593f24f1bdc48e0c530a8af122d
|
||||
F src/pcache.c 62835bed959e2914edd26afadfecce29ece0e870
|
||||
F src/pcache.h 2cedcd8407eb23017d92790b112186886e179490
|
||||
@@ -403,11 +403,13 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
|
||||
F src/resolve.c 3e518b962d932a997fae373366880fc028c75706
|
||||
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
|
||||
F src/select.c 4f0adefaa5e9417459b07757e0f6060cac97930a86f0fba9797bab233ced66c0
|
||||
F src/server.c 63c3799a184cba3d3b9f102f10e815104ba8d1d4a01c70b0e709e8173b5f9ae7
|
||||
F src/server.h e1ce2da1e4d21f335904539e3f98a7c24e015e1201b4bb16d61f0044b8bd2884
|
||||
F src/shell.c e5950029da103c5d378e71d548759459b9a7fc76177a71562c22082c705745ab
|
||||
F src/sqlite.h.in eeb1da70a61d52e1d58e5b55446b85bbac571699421d3cf857421c56214013ce
|
||||
F src/sqlite.h.in 8d126e4cfbd1f4bc6f4043aacd77f78b45613e7d630185d49a5d099394247483
|
||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||
F src/sqlite3ext.h 58fd0676d3111d02e62e5a35992a7d3da5d3f88753acc174f2d37b774fbbdd28
|
||||
F src/sqliteInt.h aea3aa1b81e0d07d5b1c39b8c5a54a1dc5e4f10136cb63da392aef9eb2a5108b
|
||||
F src/sqliteInt.h 50a6bec0b83965edb0a805e1e0f3ebeab536084109a97bdd18a524eb42579a4e
|
||||
F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
|
||||
F src/status.c a9e66593dfb28a9e746cba7153f84d49c1ddc4b1
|
||||
F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
|
||||
@@ -473,15 +475,15 @@ F src/vdbe.c 9bac2bc2313ed682e6f48ccff6644d3263341885bfcbb3cdea7b720c722be2d5
|
||||
F src/vdbe.h f7d1456e28875c2dcb964056589b5b7149ab7edf39edeca801596a39bb3d3848
|
||||
F src/vdbeInt.h c070bc5c8b913bda0ceaa995cd4d939ded5e4fc96cf7c3c1c602d41b871f8ade
|
||||
F src/vdbeapi.c 5b08d82592bcff4470601fe78aaabebd50837860
|
||||
F src/vdbeaux.c b4999c744e59deba7ab8733640219ecbc771721b362d7e26ce4c57db575ad80b
|
||||
F src/vdbeaux.c ef484bc29e56fc309285e92490e3a547a86b7f7ed3a8701d145a60f9e5ac9ad9
|
||||
F src/vdbeblob.c 359891617358deefc85bef7bcf787fa6b77facb9
|
||||
F src/vdbemem.c 2c70f8f5de6c71fb99a22c5b83be9fab5c47cdd8e279fa44a8c00cfed06d7e89
|
||||
F src/vdbesort.c e72fe02a2121386ba767ede8942e9450878b8fc873abf3d1b6824485f092570c
|
||||
F src/vdbetrace.c 41963d5376f0349842b5fc4aaaaacd7d9cdc0834
|
||||
F src/vtab.c 35b9bdc2b41de32a417141d12097bcc4e29a77ed7cdb8f836d1d2305d946b61b
|
||||
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
|
||||
F src/wal.c 40c543f0a2195d1b0dc88ef12142bea690009344
|
||||
F src/wal.h 06b2a0b599cc0f53ea97f497cf8c6b758c999f71
|
||||
F src/wal.c 8f71654244baf38b95a277e79677d5444737c326182ba81476bc101c001f2e07
|
||||
F src/wal.h 739d92494eb18b6d8f3e353e66c10eb8f94534bafd336ece9f3f60235317ea08
|
||||
F src/walker.c b71a992b413b3a022572eccf29ef4b4890223791
|
||||
F src/where.c c6352f15be5031907c68bcbde96cad1a6da20e9f4051d10168a59235de9a8566
|
||||
F src/whereInt.h 2a4b634d63ce488b46d4b0da8f2eaa8f9aeab202bc25ef76f007de5e3fba1f20
|
||||
@@ -1034,7 +1036,7 @@ F test/parser1.test 391b9bf9a229547a129c61ac345ed1a6f5eb1854
|
||||
F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
|
||||
F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
|
||||
F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
|
||||
F test/permutations.test 8aaa22a0f428a7e6b8446b97bc7691a273eaeff5dc290fb9129bf79fa9813a6e
|
||||
F test/permutations.test ca1840b39571fc87f432c104e6df00279ab67edeec0f75d8015dcfea648fee2e
|
||||
F test/pragma.test 1e94755164a3a3264cd39836de4bebcb7809e5f8
|
||||
F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f
|
||||
F test/pragma3.test 14c12bc5352b1e100e0b6b44f371053a81ccf8ed
|
||||
@@ -1110,6 +1112,10 @@ F test/selectE.test a8730ca330fcf40ace158f134f4fe0eb00c7edbf
|
||||
F test/selectF.test 21c94e6438f76537b72532fa9fd4710cdd455fc3
|
||||
F test/selectG.test e8600e379589e85e9fefd2fe4d44a4cdd63f6982
|
||||
F test/server1.test 46803bd3fe8b99b30dbc5ff38ffc756f5c13a118
|
||||
F test/server2.test 11dda300ebef43b4abe0fdc086b6f51b964beddb529fb65bc1f026db5895c36e
|
||||
F test/server3.test c33343f2f6bc23f2b4e2f047c3d083579f0cfac2795e0f1eb226ab34758967c0
|
||||
F test/servercrash.test 816c132b26af008067cab2913783f67006d4003e3988f3f3ee1075742f6e0a6c
|
||||
F test/serverwal.test 2d066ba70db35e28f0115a57385cf606513c56d6e2861119c2defb7471e4e2d9
|
||||
F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be
|
||||
F test/shared.test 1da9dbad400cee0d93f252ccf76e1ae007a63746
|
||||
F test/shared2.test 03eb4a8d372e290107d34b6ce1809919a698e879
|
||||
@@ -1191,7 +1197,7 @@ F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
|
||||
F test/temptable2.test cd396beb41117a5302fff61767c35fa4270a0d5e
|
||||
F test/temptable3.test d11a0974e52b347e45ee54ef1923c91ed91e4637
|
||||
F test/temptrigger.test 38f0ca479b1822d3117069e014daabcaacefffcc
|
||||
F test/tester.tcl 581f0185434daf7026ccede4c07e8d1479186ec5
|
||||
F test/tester.tcl d74fefbba7cdd4ad2846c73ed0e02734809d8f824bf25f44a8dbe43dd8a74283
|
||||
F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5
|
||||
F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
|
||||
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
|
||||
@@ -1519,7 +1525,7 @@ F tool/mkopts.tcl 66ac10d240cc6e86abd37dc908d50382f84ff46e
|
||||
F tool/mkpragmatab.tcl 32bb40741df11bddc8451de9ea4d130e7b4476d8064794b1cf402ac110840fba
|
||||
F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
|
||||
F tool/mksqlite3c-noext.tcl fef88397668ae83166735c41af99d79f56afaabb
|
||||
F tool/mksqlite3c.tcl 06b2e6a0f21cc0a5d70fbbd136b3e0a96470645e
|
||||
F tool/mksqlite3c.tcl 44d4b9b9d7398801164e66f866f1da0ee1e12e47d1b718f1bbf09a19fd2f3a44
|
||||
F tool/mksqlite3h.tcl b9836752c3d08f9fab2dfc0017ca9fd5d90ac863
|
||||
F tool/mksqlite3internalh.tcl eb994013e833359137eb53a55acdad0b5ae1049b
|
||||
F tool/mkvsix.tcl b9e0777a213c23156b6542842c238479e496ebf5
|
||||
@@ -1551,6 +1557,7 @@ F tool/stack_usage.tcl f8e71b92cdb099a147dad572375595eae55eca43
|
||||
F tool/symbols-mingw.sh 4dbcea7e74768305384c9fd2ed2b41bbf9f0414d
|
||||
F tool/symbols.sh c5a617b8c61a0926747a56c65f5671ef8ac0e148
|
||||
F tool/tostr.tcl 96022f35ada2194f6f8ccf6fd95809e90ed277c4
|
||||
F tool/tserver.c 8067d85410c827c9d1b33fd3f269b96615ab404a277c268e1838bfa768b58949
|
||||
F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003
|
||||
F tool/vdbe-compress.tcl 5926c71f9c12d2ab73ef35c29376e756eb68361c
|
||||
F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f
|
||||
@@ -1578,7 +1585,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P e24b73820cdca07eee87853fe6dd9f60d76e039eeb4aebef03654db3c7c94f14
|
||||
R e8b739e45c08ec8468647ac109d9456f
|
||||
U drh
|
||||
Z 82e99d9ee4b60fac8b8163f812301e67
|
||||
P d8568aacf083b63cdcf68d744ddb07a7615d14f169be6c632813a266a1e8a1cf
|
||||
R fca1d20100f6bb2d0932053fa9b009ce
|
||||
U dan
|
||||
Z abee9fae5ac9c5e9468404f3e0fbaf84
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
b9a58daca80a815e87e541cb5fff9bc8b93f131d223f322c5b83dd5a5f0c0312
|
||||
093b9108ea3532437683547c82f756df2413013a50a2fddad3d928cf8d74f9eb
|
||||
+316
-8
@@ -5616,6 +5616,255 @@ int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
|
||||
#define SERVER_DEFAULT_FREELISTS 16
|
||||
#define SERVER_DEFAULT_FREELIST_SIZE 128
|
||||
|
||||
/*
|
||||
** Allocate the free-node and the first SERVER_DEFAULT_FREELISTS
|
||||
** trunk pages.
|
||||
*/
|
||||
static int allocateServerFreenode(BtShared *pBt){
|
||||
int rc;
|
||||
MemPage *pPage1 = pBt->pPage1;
|
||||
|
||||
rc = sqlite3PagerWrite(pPage1->pDbPage);
|
||||
if( rc==SQLITE_OK ){
|
||||
Pgno pgnoNode = (++pBt->nPage);
|
||||
MemPage *pNode = 0;
|
||||
int i;
|
||||
|
||||
put4byte(&pPage1->aData[32], pgnoNode);
|
||||
rc = btreeGetUnusedPage(pBt, pgnoNode, &pNode, PAGER_GET_NOCONTENT);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3PagerWrite(pNode->pDbPage);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
put4byte(&pNode->aData[0], 0);
|
||||
put4byte(&pNode->aData[4], SERVER_DEFAULT_FREELISTS);
|
||||
}
|
||||
for(i=0; rc==SQLITE_OK && i<SERVER_DEFAULT_FREELISTS; i++){
|
||||
MemPage *pTrunk = 0;
|
||||
Pgno pgnoTrunk;
|
||||
if( ++pBt->nPage==PENDING_BYTE_PAGE(pBt) ) pBt->nPage++;
|
||||
pgnoTrunk = pBt->nPage;
|
||||
|
||||
rc = btreeGetUnusedPage(pBt, pgnoTrunk, &pTrunk, PAGER_GET_NOCONTENT);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3PagerWrite(pTrunk->pDbPage);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
memset(pTrunk->aData, 0, 8);
|
||||
put4byte(&pNode->aData[8+i*4], pgnoTrunk);
|
||||
}
|
||||
releasePage(pTrunk);
|
||||
}
|
||||
releasePage(pNode);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a reference to the first trunk page in one of the database free-lists.
|
||||
** Allocate the database free-lists if required.
|
||||
*/
|
||||
static int findServerTrunk(BtShared *pBt, int bAlloc, MemPage **ppTrunk){
|
||||
MemPage *pPage1 = pBt->pPage1;
|
||||
MemPage *pNode = 0; /* The node page */
|
||||
MemPage *pTrunk = 0; /* The returned page */
|
||||
Pgno iNode; /* Page number of node page */
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
/* If the node page and free-list trunks have not yet been allocated, allocate
|
||||
** them now. */
|
||||
pPage1 = pBt->pPage1;
|
||||
iNode = get4byte(&pPage1->aData[32]);
|
||||
if( iNode==0 ){
|
||||
rc = allocateServerFreenode(pBt);
|
||||
iNode = get4byte(&pPage1->aData[32]);
|
||||
}
|
||||
|
||||
/* Grab the node page */
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = btreeGetUnusedPage(pBt, iNode, &pNode, 0);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
int nList; /* Number of free-lists in this db */
|
||||
int i;
|
||||
|
||||
/* Try to lock a free-list trunk. If bAlloc is true, it has to be a
|
||||
** free-list trunk with at least one entry in the free-list. */
|
||||
nList = (int)get4byte(&pNode->aData[4]);
|
||||
for(i=0; i<nList; i++){
|
||||
Pgno iTrunk = get4byte(&pNode->aData[8+i*4]);
|
||||
if( SQLITE_OK==sqlite3PagerPagelock(pBt->pPager, iTrunk, 1) ){
|
||||
rc = btreeGetUnusedPage(pBt, iTrunk, &pTrunk, 0);
|
||||
if( rc==SQLITE_OK && bAlloc ){
|
||||
if( !get4byte(&pTrunk->aData[0]) && !get4byte(&pTrunk->aData[4]) ){
|
||||
releasePage(pTrunk);
|
||||
pTrunk = 0;
|
||||
}
|
||||
}
|
||||
if( rc!=SQLITE_OK || pTrunk ) break;
|
||||
}
|
||||
}
|
||||
|
||||
/* No free pages in any free-list. Or perhaps we were locked out. In
|
||||
** either case, try to allocate more from the end of the file now. */
|
||||
if( i==nList ){
|
||||
assert( rc==SQLITE_OK && pTrunk==0 );
|
||||
rc = sqlite3PagerWrite(pPage1->pDbPage);
|
||||
for(i=0; rc==SQLITE_OK && i<nList; i++){
|
||||
/* Add some free pages to each free-list. No server-locks are required
|
||||
** to do this as we have a write-lock on page 1 - guaranteeing
|
||||
** exclusive access to the db file. */
|
||||
MemPage *pT = 0;
|
||||
Pgno iTrunk = get4byte(&pNode->aData[8+i*4]);
|
||||
rc = btreeGetUnusedPage(pBt, iTrunk, &pT, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3PagerWrite(pT->pDbPage);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
int iPg = get4byte(&pT->aData[4]);
|
||||
for(/*no-op*/; iPg<SERVER_DEFAULT_FREELIST_SIZE; iPg++){
|
||||
if( ++pBt->nPage==PENDING_BYTE_PAGE(pBt) ) pBt->nPage++;
|
||||
put4byte(&pT->aData[8+iPg*4], pBt->nPage);
|
||||
}
|
||||
put4byte(&pT->aData[4], iPg);
|
||||
if( pTrunk==0 ){
|
||||
pTrunk = pT;
|
||||
pT = 0;
|
||||
}
|
||||
}
|
||||
releasePage(pT);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
MemPage *pLast = 0;
|
||||
rc = btreeGetUnusedPage(pBt, pBt->nPage, &pLast, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3PagerWrite(pLast->pDbPage);
|
||||
releasePage(pLast);
|
||||
put4byte(28 + (u8*)pPage1->aData, pBt->nPage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
releasePage(pNode);
|
||||
if( rc==SQLITE_OK ){
|
||||
assert( pTrunk );
|
||||
rc = sqlite3PagerWrite(pTrunk->pDbPage);
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
releasePage(pTrunk);
|
||||
pTrunk = 0;
|
||||
}
|
||||
*ppTrunk = pTrunk;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int allocateServerPage(
|
||||
BtShared *pBt, /* The btree */
|
||||
MemPage **ppPage, /* Store pointer to the allocated page here */
|
||||
Pgno *pPgno, /* Store the page number here */
|
||||
Pgno nearby, /* Search for a page near this one */
|
||||
u8 eMode /* BTALLOC_EXACT, BTALLOC_LT, or BTALLOC_ANY */
|
||||
){
|
||||
int rc; /* Return code */
|
||||
MemPage *pTrunk = 0; /* The node page */
|
||||
Pgno pgnoNew = 0;
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
int nRef = sqlite3PagerRefcount(pBt->pPager);
|
||||
#endif
|
||||
|
||||
assert( eMode==BTALLOC_ANY );
|
||||
assert( sqlite3_mutex_held(pBt->mutex) );
|
||||
|
||||
*ppPage = 0;
|
||||
rc = findServerTrunk(pBt, 1, &pTrunk);
|
||||
if( rc==SQLITE_OK ){
|
||||
int nFree; /* Number of free pages on this trunk page */
|
||||
nFree = (int)get4byte(&pTrunk->aData[4]);
|
||||
if( nFree==0 ){
|
||||
pgnoNew = get4byte(&pTrunk->aData[0]);
|
||||
assert( pgnoNew );
|
||||
}else{
|
||||
nFree--;
|
||||
pgnoNew = get4byte(&pTrunk->aData[8+4*nFree]);
|
||||
put4byte(&pTrunk->aData[4], (u32)nFree);
|
||||
releasePage(pTrunk);
|
||||
pTrunk = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
MemPage *pNew = 0;
|
||||
int flags = pTrunk ? 0 : PAGER_GET_NOCONTENT;
|
||||
rc = btreeGetUnusedPage(pBt, pgnoNew, &pNew, flags);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3PagerWrite(pNew->pDbPage);
|
||||
if( rc!=SQLITE_OK ){
|
||||
releasePage(pNew);
|
||||
pNew = 0;
|
||||
}
|
||||
}
|
||||
if( rc==SQLITE_OK && pTrunk ){
|
||||
memcpy(pTrunk->aData, pNew->aData, pBt->usableSize);
|
||||
}
|
||||
*ppPage = pNew;
|
||||
*pPgno = pgnoNew;
|
||||
}
|
||||
|
||||
releasePage(pTrunk);
|
||||
assert( (rc==SQLITE_OK)==(*ppPage!=0) );
|
||||
assert( sqlite3PagerRefcount(pBt->pPager)==(nRef+(*ppPage!=0)) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int freeServerPage2(BtShared *pBt, MemPage *pPage, Pgno iPage){
|
||||
int rc; /* Return code */
|
||||
MemPage *pTrunk = 0; /* The node page */
|
||||
#ifdef SQLITE_DEBUG
|
||||
int nRef = sqlite3PagerRefcount(pBt->pPager);
|
||||
#endif
|
||||
|
||||
assert( sqlite3_mutex_held(pBt->mutex) );
|
||||
rc = findServerTrunk(pBt, 0, &pTrunk);
|
||||
if( rc==SQLITE_OK ){
|
||||
int nFree; /* Number of free pages on this trunk page */
|
||||
nFree = (int)get4byte(&pTrunk->aData[4]);
|
||||
if( nFree>=((pBt->usableSize / 4) - 2) ){
|
||||
if( pPage==0 ){
|
||||
rc = btreeGetUnusedPage(pBt, iPage, &pPage, 0);
|
||||
}else{
|
||||
sqlite3PagerRef(pPage->pDbPage);
|
||||
}
|
||||
rc = sqlite3PagerWrite(pPage->pDbPage);
|
||||
if( rc==SQLITE_OK ){
|
||||
memcpy(pPage->aData, pTrunk->aData, pBt->usableSize);
|
||||
put4byte(&pTrunk->aData[0], iPage);
|
||||
put4byte(&pTrunk->aData[4], 0);
|
||||
}
|
||||
releasePage(pPage);
|
||||
}else{
|
||||
put4byte(&pTrunk->aData[8+nFree*4], iPage);
|
||||
put4byte(&pTrunk->aData[4], (u32)nFree+1);
|
||||
}
|
||||
releasePage(pTrunk);
|
||||
}
|
||||
|
||||
assert( nRef==sqlite3PagerRefcount(pBt->pPager) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
#else
|
||||
# define allocateServerPage(v, w, x, y, z) SQLITE_OK
|
||||
# define freeServerPage2(x, y, z) SQLITE_OK
|
||||
#endif /* SQLITE_SERVER_EDITION */
|
||||
|
||||
/*
|
||||
** Allocate a new page from the database file.
|
||||
**
|
||||
@@ -5653,6 +5902,10 @@ static int allocateBtreePage(
|
||||
MemPage *pPrevTrunk = 0;
|
||||
Pgno mxPage; /* Total size of the database file */
|
||||
|
||||
if( sqlite3PagerIsServer(pBt->pPager) ){
|
||||
return allocateServerPage(pBt, ppPage, pPgno, nearby, eMode);
|
||||
}
|
||||
|
||||
assert( sqlite3_mutex_held(pBt->mutex) );
|
||||
assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) );
|
||||
pPage1 = pBt->pPage1;
|
||||
@@ -5980,12 +6233,6 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
|
||||
pPage = btreePageLookup(pBt, iPage);
|
||||
}
|
||||
|
||||
/* Increment the free page count on pPage1 */
|
||||
rc = sqlite3PagerWrite(pPage1->pDbPage);
|
||||
if( rc ) goto freepage_out;
|
||||
nFree = get4byte(&pPage1->aData[36]);
|
||||
put4byte(&pPage1->aData[36], nFree+1);
|
||||
|
||||
if( pBt->btsFlags & BTS_SECURE_DELETE ){
|
||||
/* If the secure_delete option is enabled, then
|
||||
** always fully overwrite deleted information with zeros.
|
||||
@@ -5997,6 +6244,17 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
|
||||
}
|
||||
memset(pPage->aData, 0, pPage->pBt->pageSize);
|
||||
}
|
||||
|
||||
if( sqlite3PagerIsServer(pBt->pPager) ){
|
||||
rc = freeServerPage2(pBt, pPage, iPage);
|
||||
goto freepage_out;
|
||||
}
|
||||
|
||||
/* Increment the free page count on pPage1 */
|
||||
rc = sqlite3PagerWrite(pPage1->pDbPage);
|
||||
if( rc ) goto freepage_out;
|
||||
nFree = get4byte(&pPage1->aData[36]);
|
||||
put4byte(&pPage1->aData[36], nFree+1);
|
||||
|
||||
/* If the database supports auto-vacuum, write an entry in the pointer-map
|
||||
** to indicate that the page is free.
|
||||
@@ -9444,6 +9702,49 @@ end_of_check:
|
||||
#endif /* SQLITE_OMIT_INTEGRITY_CHECK */
|
||||
|
||||
#ifndef SQLITE_OMIT_INTEGRITY_CHECK
|
||||
|
||||
#if !defined(SQLITE_OMIT_INTEGRITY_CHECK) && defined(SQLITE_SERVER_EDITION)
|
||||
static void checkServerList(IntegrityCk *pCheck){
|
||||
u32 pgnoNode = get4byte(&pCheck->pBt->pPage1->aData[32]);
|
||||
if( pgnoNode ){
|
||||
DbPage *pNode = 0;
|
||||
u8 *aNodeData;
|
||||
u32 nList; /* Number of free-lists */
|
||||
int i;
|
||||
|
||||
checkRef(pCheck, pgnoNode);
|
||||
if( sqlite3PagerGet(pCheck->pPager, (Pgno)pgnoNode, &pNode, 0) ){
|
||||
checkAppendMsg(pCheck, "failed to get node page %d", pgnoNode);
|
||||
return;
|
||||
}
|
||||
aNodeData = sqlite3PagerGetData(pNode);
|
||||
nList = get4byte(&aNodeData[4]);
|
||||
for(i=0; i<nList; i++){
|
||||
u32 pgnoTrunk = get4byte(&aNodeData[8+4*i]);
|
||||
while( pgnoTrunk ){
|
||||
DbPage *pTrunk = 0;
|
||||
checkRef(pCheck, pgnoTrunk);
|
||||
if( sqlite3PagerGet(pCheck->pPager, (Pgno)pgnoTrunk, &pTrunk, 0) ){
|
||||
checkAppendMsg(pCheck, "failed to get page %d", pgnoTrunk);
|
||||
pgnoTrunk = 0;
|
||||
}else{
|
||||
u8 *aTrunkData = sqlite3PagerGetData(pTrunk);
|
||||
int nLeaf = (int)get4byte(&aTrunkData[4]);
|
||||
int iLeaf;
|
||||
for(iLeaf=0; iLeaf<nLeaf; iLeaf++){
|
||||
u32 pgnoLeaf = get4byte(&aTrunkData[8+iLeaf*4]);
|
||||
checkRef(pCheck, pgnoLeaf);
|
||||
}
|
||||
pgnoTrunk = get4byte(&aTrunkData[0]);
|
||||
sqlite3PagerUnref(pTrunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sqlite3PagerUnref(pNode);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
** This routine does a complete check of the given BTree file. aRoot[] is
|
||||
** an array of pages numbers were each page number is the root page of
|
||||
@@ -9509,8 +9810,15 @@ char *sqlite3BtreeIntegrityCheck(
|
||||
/* Check the integrity of the freelist
|
||||
*/
|
||||
sCheck.zPfx = "Main freelist: ";
|
||||
checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]),
|
||||
get4byte(&pBt->pPage1->aData[36]));
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
if( sqlite3PagerIsServer(pBt->pPager) ){
|
||||
checkServerList(&sCheck);
|
||||
}else
|
||||
#endif
|
||||
{
|
||||
checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]),
|
||||
get4byte(&pBt->pPage1->aData[36]));
|
||||
}
|
||||
sCheck.zPfx = 0;
|
||||
|
||||
/* Check all the tables.
|
||||
|
||||
+159
-5
@@ -706,6 +706,9 @@ struct Pager {
|
||||
Wal *pWal; /* Write-ahead log used by "journal_mode=wal" */
|
||||
char *zWal; /* File name for write-ahead log */
|
||||
#endif
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
Server *pServer;
|
||||
#endif
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -836,6 +839,12 @@ int sqlite3PagerUseWal(Pager *pPager, Pgno pgno){
|
||||
# define pagerBeginReadTransaction(z) SQLITE_OK
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
# define pagerIsServer(x) ((x)->pServer!=0)
|
||||
#else
|
||||
# define pagerIsServer(x) 0
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
/*
|
||||
** Usage:
|
||||
@@ -1132,6 +1141,7 @@ static int pagerUnlockDb(Pager *pPager, int eLock){
|
||||
assert( !pPager->exclusiveMode || pPager->eLock==eLock );
|
||||
assert( eLock==NO_LOCK || eLock==SHARED_LOCK );
|
||||
assert( eLock!=NO_LOCK || pagerUseWal(pPager)==0 );
|
||||
assert( eLock!=NO_LOCK || pagerIsServer(pPager)==0 );
|
||||
if( isOpen(pPager->fd) ){
|
||||
assert( pPager->eLock>=eLock );
|
||||
rc = pPager->noLock ? SQLITE_OK : sqlite3OsUnlock(pPager->fd, eLock);
|
||||
@@ -1807,6 +1817,12 @@ static void pager_unlock(Pager *pPager){
|
||||
pPager->pInJournal = 0;
|
||||
releaseAllSavepoints(pPager);
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
if( pagerIsServer(pPager) ){
|
||||
sqlite3ServerEnd(pPager->pServer);
|
||||
pPager->eState = PAGER_OPEN;
|
||||
}else
|
||||
#endif
|
||||
if( pagerUseWal(pPager) ){
|
||||
assert( !isOpen(pPager->jfd) );
|
||||
sqlite3WalEndReadTransaction(pPager->pWal);
|
||||
@@ -2105,6 +2121,11 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
|
||||
if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
if( pagerIsServer(pPager) ){
|
||||
rc2 = sqlite3ServerReleaseWriteLocks(pPager->pServer);
|
||||
}else
|
||||
#endif
|
||||
if( !pPager->exclusiveMode
|
||||
&& (!pagerUseWal(pPager) || sqlite3WalExclusiveMode(pPager->pWal, 0))
|
||||
){
|
||||
@@ -4095,10 +4116,24 @@ int sqlite3PagerClose(Pager *pPager, sqlite3 *db){
|
||||
** rollback before accessing the database file.
|
||||
*/
|
||||
if( isOpen(pPager->jfd) ){
|
||||
if( pagerIsServer(pPager) ){
|
||||
assert( pPager->journalMode==PAGER_JOURNALMODE_PERSIST );
|
||||
pPager->journalMode = PAGER_JOURNALMODE_DELETE;
|
||||
/* If necessary, change the pager state so that the journal file
|
||||
** is deleted by the call to pagerUnlockAndRollback() below. */
|
||||
if( pPager->eState==PAGER_OPEN ) pPager->eState = PAGER_READER;
|
||||
}
|
||||
pager_error(pPager, pagerSyncHotJournal(pPager));
|
||||
}
|
||||
pagerUnlockAndRollback(pPager);
|
||||
}
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
if( pagerIsServer(pPager) ){
|
||||
sqlite3ServerDisconnect(pPager->pServer, pPager->fd);
|
||||
pPager->pServer = 0;
|
||||
sqlite3_free(pPager->zJournal);
|
||||
}
|
||||
#endif
|
||||
sqlite3EndBenignMalloc();
|
||||
enable_simulated_io_errors();
|
||||
PAGERTRACE(("CLOSE %d\n", PAGERID(pPager)));
|
||||
@@ -5051,6 +5086,80 @@ static int hasHotJournal(Pager *pPager, int *pExists){
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
static int pagerServerConnect(Pager *pPager){
|
||||
int rc = SQLITE_OK;
|
||||
if( pPager->tempFile==0 ){
|
||||
int iClient = 0;
|
||||
pPager->noLock = 1;
|
||||
pPager->journalMode = PAGER_JOURNALMODE_PERSIST;
|
||||
rc = sqlite3ServerConnect(pPager, &pPager->pServer, &iClient);
|
||||
if( rc==SQLITE_OK ){
|
||||
pPager->zJournal = sqlite3_mprintf(
|
||||
"%s-journal%d", pPager->zFilename, iClient
|
||||
);
|
||||
if( pPager->zJournal==0 ){
|
||||
rc = SQLITE_NOMEM_BKPT;
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sqlite3PagerRollbackJournal(Pager *pPager, int iClient){
|
||||
int rc;
|
||||
char *zJrnl = sqlite3_mprintf("%s-journal%d", pPager->zFilename, iClient);
|
||||
|
||||
if( zJrnl ){
|
||||
int bExists = 0;
|
||||
sqlite3_file *jfd = 0;
|
||||
sqlite3_vfs * const pVfs = pPager->pVfs;
|
||||
|
||||
rc = sqlite3OsAccess(pVfs, zJrnl, SQLITE_ACCESS_EXISTS, &bExists);
|
||||
if( rc==SQLITE_OK && bExists ){
|
||||
int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_MAIN_JOURNAL;
|
||||
rc = sqlite3OsOpenMalloc(pVfs, zJrnl, &jfd, flags, &flags);
|
||||
}
|
||||
assert( rc==SQLITE_OK || jfd==0 );
|
||||
if( jfd ){
|
||||
sqlite3_file *saved_jfd = pPager->jfd;
|
||||
u8 saved_eState = pPager->eState;
|
||||
u8 saved_eLock = pPager->eLock;
|
||||
i64 saved_journalOff = pPager->journalOff;
|
||||
i64 saved_journalHdr = pPager->journalHdr;
|
||||
char *saved_zJournal = pPager->zJournal;
|
||||
|
||||
pPager->eLock = EXCLUSIVE_LOCK;
|
||||
pPager->eState = PAGER_WRITER_DBMOD;
|
||||
pPager->jfd = jfd;
|
||||
rc = pagerSyncHotJournal(pPager);
|
||||
if( rc==SQLITE_OK ) rc = pager_playback(pPager, 1);
|
||||
|
||||
pPager->jfd = saved_jfd;
|
||||
pPager->eState = saved_eState;
|
||||
pPager->eLock = saved_eLock;
|
||||
pPager->journalOff = saved_journalOff;
|
||||
pPager->journalHdr = saved_journalHdr;
|
||||
pPager->zJournal = saved_zJournal;
|
||||
|
||||
sqlite3OsCloseFree(jfd);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3OsDelete(pVfs, zJrnl, 0);
|
||||
}
|
||||
}
|
||||
sqlite3_free(zJrnl);
|
||||
}else{
|
||||
rc = SQLITE_NOMEM_BKPT;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#else
|
||||
# define pagerServerConnect(pPager) SQLITE_OK
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** This function is called to obtain a shared lock on the database file.
|
||||
** It is illegal to call sqlite3PagerGet() until after this function
|
||||
@@ -5090,7 +5199,9 @@ int sqlite3PagerSharedLock(Pager *pPager){
|
||||
assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER );
|
||||
assert( pPager->errCode==SQLITE_OK );
|
||||
|
||||
if( !pagerUseWal(pPager) && pPager->eState==PAGER_OPEN ){
|
||||
if( !pagerUseWal(pPager)
|
||||
&& !pagerIsServer(pPager)
|
||||
&& pPager->eState==PAGER_OPEN ){
|
||||
int bHotJournal = 1; /* True if there exists a hot journal-file */
|
||||
|
||||
assert( !MEMDB );
|
||||
@@ -5262,16 +5373,27 @@ int sqlite3PagerSharedLock(Pager *pPager){
|
||||
}
|
||||
}
|
||||
|
||||
rc = pagerServerConnect(pPager);
|
||||
|
||||
/* If there is a WAL file in the file-system, open this database in WAL
|
||||
** mode. Otherwise, the following function call is a no-op.
|
||||
*/
|
||||
rc = pagerOpenWalIfPresent(pPager);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = pagerOpenWalIfPresent(pPager);
|
||||
}
|
||||
#ifndef SQLITE_OMIT_WAL
|
||||
assert( pPager->pWal==0 || rc==SQLITE_OK );
|
||||
#endif
|
||||
}
|
||||
|
||||
if( pagerUseWal(pPager) ){
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
if( pagerIsServer(pPager) ){
|
||||
assert( rc==SQLITE_OK );
|
||||
pager_reset(pPager);
|
||||
rc = sqlite3ServerBegin(pPager->pServer);
|
||||
}
|
||||
#endif
|
||||
if( rc==SQLITE_OK && pagerUseWal(pPager) ){
|
||||
assert( rc==SQLITE_OK );
|
||||
rc = pagerBeginReadTransaction(pPager);
|
||||
}
|
||||
@@ -5564,6 +5686,12 @@ int sqlite3PagerGet(
|
||||
DbPage **ppPage, /* Write a pointer to the page here */
|
||||
int flags /* PAGER_GET_XXX flags */
|
||||
){
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
if( pagerIsServer(pPager) ){
|
||||
int rc = sqlite3ServerLock(pPager->pServer, pgno, 0, 0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
#endif
|
||||
return pPager->xGet(pPager, pgno, ppPage, flags);
|
||||
}
|
||||
|
||||
@@ -5866,6 +5994,13 @@ static int pager_write(PgHdr *pPg){
|
||||
assert( pPager->readOnly==0 );
|
||||
CHECK_PAGE(pPg);
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
if( pagerIsServer(pPager) ){
|
||||
rc = sqlite3ServerLock(pPager->pServer, pPg->pgno, 1, 0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* The journal file needs to be opened. Higher level routines have already
|
||||
** obtained the necessary locks to begin the write-transaction, but the
|
||||
** rollback journal might not yet be open. Open it now if this is the case.
|
||||
@@ -6144,7 +6279,10 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){
|
||||
# define DIRECT_MODE isDirectMode
|
||||
#endif
|
||||
|
||||
if( !pPager->changeCountDone && ALWAYS(pPager->dbSize>0) ){
|
||||
if( 0==pagerIsServer(pPager)
|
||||
&& !pPager->changeCountDone
|
||||
&& ALWAYS(pPager->dbSize>0)
|
||||
){
|
||||
PgHdr *pPgHdr; /* Reference to page 1 */
|
||||
|
||||
assert( !pPager->tempFile && isOpen(pPager->fd) );
|
||||
@@ -6303,6 +6441,10 @@ int sqlite3PagerCommitPhaseOne(
|
||||
** backup in progress needs to be restarted. */
|
||||
sqlite3BackupRestart(pPager->pBackup);
|
||||
}else{
|
||||
/* If this connection is in server mode, ignore any master journal. */
|
||||
if( pagerIsServer(pPager) ){
|
||||
zMaster = 0;
|
||||
}
|
||||
if( pagerUseWal(pPager) ){
|
||||
PgHdr *pList = sqlite3PcacheDirtyList(pPager->pPCache);
|
||||
PgHdr *pPageOne = 0;
|
||||
@@ -7313,7 +7455,7 @@ int sqlite3PagerWalCallback(Pager *pPager){
|
||||
*/
|
||||
int sqlite3PagerWalSupported(Pager *pPager){
|
||||
const sqlite3_io_methods *pMethods = pPager->fd->pMethods;
|
||||
if( pPager->noLock ) return 0;
|
||||
if( pPager->noLock && !pagerIsServer(pPager) ) return 0;
|
||||
return pPager->exclusiveMode || (pMethods->iVersion>=2 && pMethods->xShmMap);
|
||||
}
|
||||
|
||||
@@ -7408,6 +7550,9 @@ int sqlite3PagerOpenWal(
|
||||
if( rc==SQLITE_OK ){
|
||||
pPager->journalMode = PAGER_JOURNALMODE_WAL;
|
||||
pPager->eState = PAGER_OPEN;
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
sqlite3WalServer(pPager->pWal, pPager->pServer);
|
||||
#endif
|
||||
}
|
||||
}else{
|
||||
*pbOpen = 1;
|
||||
@@ -7521,4 +7666,13 @@ int sqlite3PagerWalFramesize(Pager *pPager){
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
int sqlite3PagerIsServer(Pager *pPager){
|
||||
return pagerIsServer(pPager);
|
||||
}
|
||||
int sqlite3PagerPagelock(Pager *pPager, Pgno pgno, int bWrite){
|
||||
return sqlite3ServerLock(pPager->pServer, pgno, bWrite, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SQLITE_OMIT_DISKIO */
|
||||
|
||||
@@ -236,4 +236,10 @@ void *sqlite3PagerCodec(DbPage *);
|
||||
# define enable_simulated_io_errors()
|
||||
#endif
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
int sqlite3PagerRollbackJournal(Pager*, int);
|
||||
int sqlite3PagerIsServer(Pager *pPager);
|
||||
int sqlite3PagerPagelock(Pager *pPager, Pgno, int);
|
||||
#endif
|
||||
|
||||
#endif /* SQLITE_PAGER_H */
|
||||
|
||||
+601
@@ -0,0 +1,601 @@
|
||||
/*
|
||||
** 2017 April 24
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
#include "sqliteInt.h"
|
||||
|
||||
/*
|
||||
** HMA file layout:
|
||||
**
|
||||
** 4 bytes - DMS slot. All connections read-lock this slot.
|
||||
**
|
||||
** 16*4 bytes - locking slots. Connections hold a read-lock on a locking slot
|
||||
** when they are connected, a write lock when they have an open
|
||||
** transaction.
|
||||
**
|
||||
** N*4 bytes - Page locking slots. N is HMA_PAGELOCK_SLOTS.
|
||||
**
|
||||
** Page-locking slot format:
|
||||
**
|
||||
** Each page-locking slot provides SHARED/RESERVED/EXCLUSIVE locks on a
|
||||
** single page. A RESERVED lock is similar to a RESERVED in SQLite's
|
||||
** rollback mode - existing SHARED locks may continue but new SHARED locks
|
||||
** may not be established. As in rollback mode, EXCLUSIVE and RESERVED
|
||||
** locks are mutually exclusive.
|
||||
**
|
||||
** Each 32-bit locking slot is divided into two sections - a bitmask for
|
||||
** read-locks and a single integer field for the write lock. The bitmask
|
||||
** occupies the least-significant 27 bits of the slot. The integer field
|
||||
** occupies the remaining 5 bits (so that it can store values from 0-31).
|
||||
**
|
||||
** Each client has a unique integer client id. Currently these range from
|
||||
** 0-15 (maximum of 16 concurrent connections). The page-locking slot format
|
||||
** allows this to be increased to 0-26 (maximum of 26 connections). To
|
||||
** take a SHARED lock, the corresponding bit is set in the locking slot
|
||||
** bitmask:
|
||||
**
|
||||
** slot = slot | (1 << iClient);
|
||||
**
|
||||
** To take an EXCLUSIVE or RESERVED lock, the integer part of the locking
|
||||
** slot is set to the client-id of the locker plus one (a value of zero
|
||||
** indicates that no connection holds a RESERVED or EXCLUSIVE lock):
|
||||
**
|
||||
** slot = slot | ((iClient+1) << 27)
|
||||
*/
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
|
||||
#define HMA_CLIENT_SLOTS 16
|
||||
#define HMA_PAGELOCK_SLOTS (256*1024)
|
||||
|
||||
#define HMA_FILE_SIZE (4 + 4*HMA_CLIENT_SLOTS + 4*HMA_PAGELOCK_SLOTS)
|
||||
|
||||
#include "unistd.h"
|
||||
#include "fcntl.h"
|
||||
#include "sys/mman.h"
|
||||
#include "sys/types.h"
|
||||
#include "sys/stat.h"
|
||||
#include "errno.h"
|
||||
|
||||
typedef struct ServerHMA ServerHMA;
|
||||
|
||||
struct ServerGlobal {
|
||||
ServerHMA *pHma; /* Linked list of all ServerHMA objects */
|
||||
};
|
||||
static struct ServerGlobal g_server;
|
||||
|
||||
/*
|
||||
** There is one instance of the following structure for each distinct
|
||||
** HMA file opened by clients within this process.
|
||||
*/
|
||||
struct ServerHMA {
|
||||
char *zName; /* hma file path */
|
||||
int fd; /* Fd open on hma file */
|
||||
int nClient; /* Current number of clients */
|
||||
Server *aClient[HMA_CLIENT_SLOTS]; /* Local (this process) clients */
|
||||
u32 *aMap; /* MMapped hma file */
|
||||
ServerHMA *pNext; /* Next HMA in this process */
|
||||
|
||||
dev_t st_dev;
|
||||
ino_t st_ino;
|
||||
};
|
||||
|
||||
struct Server {
|
||||
ServerHMA *pHma; /* Hma file object */
|
||||
int iClient; /* Client id */
|
||||
Pager *pPager; /* Associated pager object */
|
||||
i64 nUsWrite; /* Cumulative us holding WRITER lock */
|
||||
i64 iUsWrite; /* Time WRITER lock was taken */
|
||||
int nAlloc; /* Allocated size of aLock[] array */
|
||||
int nLock; /* Number of entries in aLock[] */
|
||||
u32 *aLock; /* Mapped lock file */
|
||||
};
|
||||
|
||||
#define SERVER_WRITE_LOCK 3
|
||||
#define SERVER_READ_LOCK 2
|
||||
#define SERVER_NO_LOCK 1
|
||||
|
||||
/*
|
||||
** Global mutex functions used by code in this file.
|
||||
*/
|
||||
static void serverEnterMutex(void){
|
||||
sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_APP1));
|
||||
}
|
||||
static void serverLeaveMutex(void){
|
||||
sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_APP1));
|
||||
}
|
||||
static void serverAssertMutexHeld(void){
|
||||
assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_APP1)) );
|
||||
}
|
||||
|
||||
static int posixLock(int fd, int iSlot, int eLock, int bBlock){
|
||||
int res;
|
||||
struct flock l;
|
||||
short aType[4] = {0, F_UNLCK, F_RDLCK, F_WRLCK};
|
||||
assert( eLock==SERVER_WRITE_LOCK
|
||||
|| eLock==SERVER_READ_LOCK
|
||||
|| eLock==SERVER_NO_LOCK
|
||||
);
|
||||
memset(&l, 0, sizeof(l));
|
||||
l.l_type = aType[eLock];
|
||||
l.l_whence = SEEK_SET;
|
||||
l.l_start = iSlot*sizeof(u32);
|
||||
l.l_len = 1;
|
||||
|
||||
res = fcntl(fd, (bBlock ? F_SETLKW : F_SETLK), &l);
|
||||
if( res && bBlock && errno==EDEADLK ){
|
||||
return SQLITE_BUSY_DEADLOCK;
|
||||
}
|
||||
return (res==0 ? SQLITE_OK : SQLITE_BUSY);
|
||||
}
|
||||
|
||||
static int serverMapFile(ServerHMA *p){
|
||||
assert( p->aMap==0 );
|
||||
p->aMap = mmap(0, HMA_FILE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, p->fd, 0);
|
||||
if( p->aMap==0 ){
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
static void serverDecrHmaRefcount(ServerHMA *pHma){
|
||||
if( pHma ){
|
||||
pHma->nClient--;
|
||||
if( pHma->nClient<=0 ){
|
||||
ServerHMA **pp;
|
||||
if( pHma->aMap ) munmap(pHma->aMap, HMA_FILE_SIZE);
|
||||
if( pHma->fd>=0 ) close(pHma->fd);
|
||||
for(pp=&g_server.pHma; *pp!=pHma; pp=&(*pp)->pNext);
|
||||
*pp = pHma->pNext;
|
||||
sqlite3_free(pHma);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int serverOpenHma(Pager *pPager, const char *zPath, ServerHMA **ppHma){
|
||||
struct stat sStat; /* Structure populated by stat() */
|
||||
int res; /* result of stat() */
|
||||
int rc = SQLITE_OK; /* Return code */
|
||||
ServerHMA *pHma = 0;
|
||||
|
||||
serverAssertMutexHeld();
|
||||
|
||||
res = stat(zPath, &sStat);
|
||||
if( res!=0 ){
|
||||
sqlite3_log(SQLITE_CANTOPEN, "Failed to stat(%s)", zPath);
|
||||
rc = SQLITE_ERROR;
|
||||
}else{
|
||||
for(pHma=g_server.pHma; pHma; pHma=pHma->pNext){
|
||||
if( sStat.st_dev==pHma->st_dev && sStat.st_ino==pHma->st_ino ) break;
|
||||
}
|
||||
if( pHma==0 ){
|
||||
int nPath = strlen(zPath);
|
||||
int nByte = sizeof(ServerHMA) + nPath+1 + 4;
|
||||
|
||||
pHma = (ServerHMA*)sqlite3_malloc(nByte);
|
||||
if( pHma==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
int i;
|
||||
memset(pHma, 0, nByte);
|
||||
pHma->zName = (char*)&pHma[1];
|
||||
pHma->nClient = 1;
|
||||
pHma->st_dev = sStat.st_dev;
|
||||
pHma->st_ino = sStat.st_ino;
|
||||
pHma->pNext = g_server.pHma;
|
||||
g_server.pHma = pHma;
|
||||
|
||||
memcpy(pHma->zName, zPath, nPath);
|
||||
memcpy(&pHma->zName[nPath], "-hma", 5);
|
||||
|
||||
pHma->fd = open(pHma->zName, O_RDWR|O_CREAT, 0644);
|
||||
if( pHma->fd<0 ){
|
||||
sqlite3_log(SQLITE_CANTOPEN, "Failed to open(%s)", pHma->zName);
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
/* Write-lock the DMS slot. If successful, initialize the hma file. */
|
||||
rc = posixLock(pHma->fd, 0, SERVER_WRITE_LOCK, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
res = ftruncate(pHma->fd, HMA_FILE_SIZE);
|
||||
if( res!=0 ){
|
||||
sqlite3_log(SQLITE_CANTOPEN,
|
||||
"Failed to ftruncate(%s)", pHma->zName
|
||||
);
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = serverMapFile(pHma);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
memset(pHma->aMap, 0, HMA_FILE_SIZE);
|
||||
}else{
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
for(i=0; rc==SQLITE_OK && i<HMA_CLIENT_SLOTS; i++){
|
||||
rc = sqlite3PagerRollbackJournal(pPager, i);
|
||||
}
|
||||
}else{
|
||||
rc = serverMapFile(pHma);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = posixLock(pHma->fd, 0, SERVER_READ_LOCK, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
serverDecrHmaRefcount(pHma);
|
||||
pHma = 0;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
pHma->nClient++;
|
||||
}
|
||||
}
|
||||
|
||||
*ppHma = pHma;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static u32 *serverPageLockSlot(Server *p, Pgno pgno){
|
||||
int iSlot = pgno % HMA_PAGELOCK_SLOTS;
|
||||
return &p->pHma->aMap[1 + HMA_CLIENT_SLOTS + iSlot];
|
||||
}
|
||||
static u32 *serverClientSlot(Server *p, int iClient){
|
||||
return &p->pHma->aMap[1 + iClient];
|
||||
}
|
||||
|
||||
/*
|
||||
** Close the "connection" and *-hma file. This deletes the object passed
|
||||
** as the first argument.
|
||||
*/
|
||||
void sqlite3ServerDisconnect(Server *p, sqlite3_file *dbfd){
|
||||
if( p->pHma ){
|
||||
ServerHMA *pHma = p->pHma;
|
||||
serverEnterMutex();
|
||||
if( p->iClient>=0 ){
|
||||
u32 *pSlot = serverClientSlot(p, p->iClient);
|
||||
*pSlot = 0;
|
||||
assert( pHma->aClient[p->iClient]==p );
|
||||
pHma->aClient[p->iClient] = 0;
|
||||
posixLock(pHma->fd, p->iClient+1, SERVER_NO_LOCK, 0);
|
||||
}
|
||||
if( dbfd
|
||||
&& pHma->nClient==1
|
||||
&& SQLITE_OK==sqlite3OsLock(dbfd, SQLITE_LOCK_EXCLUSIVE)
|
||||
){
|
||||
unlink(pHma->zName);
|
||||
}
|
||||
serverDecrHmaRefcount(pHma);
|
||||
serverLeaveMutex();
|
||||
}
|
||||
sqlite3_free(p->aLock);
|
||||
sqlite3_free(p);
|
||||
}
|
||||
|
||||
static int serverRollbackClient(Server *p, int iBlock){
|
||||
int rc;
|
||||
|
||||
sqlite3_log(SQLITE_NOTICE, "Rolling back failed client %d", iBlock);
|
||||
|
||||
/* Roll back any journal file for client iBlock. */
|
||||
rc = sqlite3PagerRollbackJournal(p->pPager, iBlock);
|
||||
|
||||
/* Clear any locks held by client iBlock from the HMA file. */
|
||||
if( rc==SQLITE_OK ){
|
||||
int i;
|
||||
for(i=0; i<HMA_PAGELOCK_SLOTS; i++){
|
||||
u32 *pSlot = serverPageLockSlot(p, (Pgno)i);
|
||||
u32 v = *pSlot;
|
||||
while( 1 ){
|
||||
u32 n = v & ~(1 << iBlock);
|
||||
if( (v>>HMA_CLIENT_SLOTS)==iBlock+1 ){
|
||||
n = n & ((1<<HMA_CLIENT_SLOTS)-1);
|
||||
}
|
||||
if( __sync_val_compare_and_swap(pSlot, v, n)==v ) break;
|
||||
v = *pSlot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Open the *-hma file and "connect" to the system.
|
||||
*/
|
||||
int sqlite3ServerConnect(
|
||||
Pager *pPager,
|
||||
Server **ppOut,
|
||||
int *piClient
|
||||
){
|
||||
const char *zPath = sqlite3PagerFilename(pPager, 0);
|
||||
int rc = SQLITE_OK;
|
||||
Server *p;
|
||||
|
||||
p = (Server*)sqlite3_malloc(sizeof(Server));
|
||||
if( p==0 ){
|
||||
rc = SQLITE_NOMEM;
|
||||
}else{
|
||||
memset(p, 0, sizeof(Server));
|
||||
p->iClient = -1;
|
||||
p->pPager = pPager;
|
||||
|
||||
serverEnterMutex();
|
||||
rc = serverOpenHma(pPager, zPath, &p->pHma);
|
||||
|
||||
/* File is now mapped. Find a free client slot. */
|
||||
if( rc==SQLITE_OK ){
|
||||
int i;
|
||||
Server **aClient = p->pHma->aClient;
|
||||
int fd = p->pHma->fd;
|
||||
for(i=0; i<HMA_CLIENT_SLOTS; i++){
|
||||
if( aClient[i]==0 ){
|
||||
int res = posixLock(fd, i+1, SERVER_WRITE_LOCK, 0);
|
||||
if( res==SQLITE_OK ){
|
||||
u32 *pSlot = serverClientSlot(p, i);
|
||||
if( *pSlot ){
|
||||
rc = serverRollbackClient(p, i);
|
||||
}
|
||||
posixLock(fd, i+1, (!rc ? SERVER_READ_LOCK : SERVER_NO_LOCK), 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
if( i>HMA_CLIENT_SLOTS ){
|
||||
rc = SQLITE_BUSY;
|
||||
}else{
|
||||
u32 *pSlot = serverClientSlot(p, i);
|
||||
*piClient = p->iClient = i;
|
||||
aClient[i] = p;
|
||||
*pSlot = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serverLeaveMutex();
|
||||
}
|
||||
|
||||
if( rc!=SQLITE_OK ){
|
||||
sqlite3ServerDisconnect(p, 0);
|
||||
p = 0;
|
||||
}
|
||||
*ppOut = p;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int serverOvercomeLock(
|
||||
Server *p, /* Server connection */
|
||||
int bWrite, /* True for a write-lock */
|
||||
int bBlock, /* If true, block for this lock */
|
||||
u32 v, /* Value of blocking page locking slot */
|
||||
int *pbRetry /* OUT: True if caller should retry lock */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
int iBlock = ((int)(v>>HMA_CLIENT_SLOTS))-1;
|
||||
|
||||
if( iBlock<0 || iBlock==p->iClient ){
|
||||
for(iBlock=0; iBlock<HMA_CLIENT_SLOTS; iBlock++){
|
||||
if( iBlock!=p->iClient && (v & (1<<iBlock)) ) break;
|
||||
}
|
||||
}
|
||||
assert( iBlock<HMA_CLIENT_SLOTS );
|
||||
|
||||
serverEnterMutex();
|
||||
|
||||
if( 0==p->pHma->aClient[iBlock] ){
|
||||
rc = posixLock(p->pHma->fd, iBlock+1, SERVER_WRITE_LOCK, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = serverRollbackClient(p, iBlock);
|
||||
|
||||
/* Release the lock on slot iBlock */
|
||||
posixLock(p->pHma->fd, iBlock+1, SERVER_NO_LOCK, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
*pbRetry = 1;
|
||||
}
|
||||
}else if( rc==SQLITE_BUSY ){
|
||||
if( bBlock ){
|
||||
rc = posixLock(p->pHma->fd, iBlock+1, SERVER_READ_LOCK, 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
posixLock(p->pHma->fd, iBlock+1, SERVER_NO_LOCK, 0);
|
||||
*pbRetry = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_BUSY ){
|
||||
rc = SQLITE_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serverLeaveMutex();
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Begin a transaction.
|
||||
*/
|
||||
int sqlite3ServerBegin(Server *p){
|
||||
#if 1
|
||||
int rc = posixLock(p->pHma->fd, p->iClient+1, SERVER_WRITE_LOCK, 1);
|
||||
if( rc ) return rc;
|
||||
#endif
|
||||
return sqlite3ServerLock(p, 1, 0, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
** End a transaction (and release all locks).
|
||||
*/
|
||||
int sqlite3ServerEnd(Server *p){
|
||||
int i;
|
||||
for(i=0; i<p->nLock; i++){
|
||||
u32 *pSlot = serverPageLockSlot(p, p->aLock[i]);
|
||||
while( 1 ){
|
||||
u32 v = *pSlot;
|
||||
u32 n = v;
|
||||
if( (v>>HMA_CLIENT_SLOTS)==p->iClient+1 ){
|
||||
n = n & ((1 << HMA_CLIENT_SLOTS)-1);
|
||||
}
|
||||
n = n & ~(1 << p->iClient);
|
||||
if( __sync_val_compare_and_swap(pSlot, v, n)==v ) break;
|
||||
}
|
||||
if( p->aLock[i]==0 ){
|
||||
struct timeval t2;
|
||||
i64 nUs;
|
||||
gettimeofday(&t2, 0);
|
||||
nUs = (i64)t2.tv_sec * 1000000 + t2.tv_usec - p->iUsWrite;
|
||||
p->nUsWrite += nUs;
|
||||
if( (p->nUsWrite / 1000000)!=((p->nUsWrite + nUs)/1000000) ){
|
||||
sqlite3_log(SQLITE_WARNING,
|
||||
"Cumulative WRITER time: %lldms\n", p->nUsWrite/1000
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
p->nLock = 0;
|
||||
#if 1
|
||||
return posixLock(p->pHma->fd, p->iClient+1, SERVER_READ_LOCK, 0);
|
||||
#endif
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Release all write-locks.
|
||||
*/
|
||||
int sqlite3ServerReleaseWriteLocks(Server *p){
|
||||
int rc = SQLITE_OK;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the client id of the client that currently holds the EXCLUSIVE
|
||||
** or RESERVED lock according to page-locking slot value v. Or -1 if no
|
||||
** client holds such a lock.
|
||||
*/
|
||||
int serverWriteLocker(u32 v){
|
||||
return ((int)(v >> HMA_CLIENT_SLOTS)) - 1;
|
||||
}
|
||||
|
||||
/*
|
||||
** Lock page pgno for reading (bWrite==0) or writing (bWrite==1).
|
||||
**
|
||||
** If parameter bBlock is non-zero, then make this a blocking lock if
|
||||
** possible.
|
||||
*/
|
||||
int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock){
|
||||
int rc = SQLITE_OK;
|
||||
int bReserved = 0;
|
||||
u32 *pSlot = serverPageLockSlot(p, pgno);
|
||||
|
||||
/* Grow the aLock[] array, if required */
|
||||
if( p->nLock==p->nAlloc ){
|
||||
int nNew = p->nAlloc ? p->nAlloc*2 : 128;
|
||||
u32 *aNew;
|
||||
aNew = (u32*)sqlite3_realloc(p->aLock, sizeof(u32)*nNew);
|
||||
if( aNew==0 ){
|
||||
rc = SQLITE_NOMEM_BKPT;
|
||||
}else{
|
||||
p->aLock = aNew;
|
||||
p->nAlloc = nNew;
|
||||
}
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
u32 v = *pSlot;
|
||||
|
||||
/* Check if the required lock is already held. If so, exit this function
|
||||
** early. Otherwise, add an entry to the aLock[] array to record the fact
|
||||
** that the lock may need to be released. */
|
||||
if( bWrite ){
|
||||
int iLock = ((int)(v>>HMA_CLIENT_SLOTS)) - 1;
|
||||
if( iLock==p->iClient ) goto server_lock_out;
|
||||
}else{
|
||||
if( v & (1<<p->iClient) ) goto server_lock_out;
|
||||
}
|
||||
p->aLock[p->nLock++] = pgno;
|
||||
|
||||
while( 1 ){
|
||||
u32 n;
|
||||
int w;
|
||||
u32 mask = (bWrite ? (((1<<HMA_CLIENT_SLOTS)-1) & ~(1<<p->iClient)) : 0);
|
||||
|
||||
while( ((w = serverWriteLocker(v))>=0 && w!=p->iClient) || (v & mask) ){
|
||||
int bRetry = 0;
|
||||
|
||||
if( w<0 && bWrite && bBlock ){
|
||||
/* Attempt a RESERVED lock before anything else */
|
||||
n = v | ((p->iClient+1) << HMA_CLIENT_SLOTS);
|
||||
assert( serverWriteLocker(n)==p->iClient );
|
||||
if( __sync_val_compare_and_swap(pSlot, v, n)!=v ){
|
||||
v = *pSlot;
|
||||
continue;
|
||||
}
|
||||
v = n;
|
||||
bReserved = 1;
|
||||
}
|
||||
|
||||
rc = serverOvercomeLock(p, bWrite, bBlock, v, &bRetry);
|
||||
if( rc!=SQLITE_OK ) goto server_lock_out;
|
||||
if( bRetry==0 ){
|
||||
/* There is a conflicting lock. Cannot obtain this lock. */
|
||||
sqlite3_log(SQLITE_BUSY_DEADLOCK, "Conflict at page %d", (int)pgno);
|
||||
rc = SQLITE_BUSY_DEADLOCK;
|
||||
goto server_lock_out;
|
||||
}
|
||||
|
||||
v = *pSlot;
|
||||
}
|
||||
|
||||
n = v | (1 << p->iClient);
|
||||
if( bWrite ){
|
||||
n = n | ((p->iClient+1) << HMA_CLIENT_SLOTS);
|
||||
}
|
||||
if( __sync_val_compare_and_swap(pSlot, v, n)==v ) break;
|
||||
v = *pSlot;
|
||||
}
|
||||
}
|
||||
|
||||
server_lock_out:
|
||||
if( rc!=SQLITE_OK && bReserved ){
|
||||
u32 n;
|
||||
u32 v;
|
||||
do{
|
||||
v = *pSlot;
|
||||
assert( serverWriteLocker(v)==p->iClient );
|
||||
n = v & ((1<<HMA_CLIENT_SLOTS)-1);
|
||||
}while( __sync_val_compare_and_swap(pSlot, v, n)!=v );
|
||||
}
|
||||
|
||||
if( pgno==0 ){
|
||||
struct timeval t1;
|
||||
gettimeofday(&t1, 0);
|
||||
p->iUsWrite = ((i64)t1.tv_sec * 1000000) + (i64)t1.tv_usec;
|
||||
}
|
||||
assert( rc!=SQLITE_OK || sqlite3ServerHasLock(p, pgno, bWrite) );
|
||||
return rc;
|
||||
}
|
||||
|
||||
int sqlite3ServerHasLock(Server *p, Pgno pgno, int bWrite){
|
||||
u32 v = *serverPageLockSlot(p, pgno);
|
||||
if( bWrite ){
|
||||
return (v>>HMA_CLIENT_SLOTS)==(p->iClient+1);
|
||||
}
|
||||
return (v & (1 << p->iClient))!=0;
|
||||
}
|
||||
|
||||
#endif /* ifdef SQLITE_SERVER_EDITION */
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
** 2017 April 24
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
*************************************************************************
|
||||
*/
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
|
||||
#ifndef SQLITE_SERVER_H
|
||||
#define SQLITE_SERVER_H
|
||||
|
||||
|
||||
typedef struct Server Server;
|
||||
|
||||
int sqlite3ServerConnect(Pager *pPager, Server **ppOut, int *piClient);
|
||||
|
||||
void sqlite3ServerDisconnect(Server *p, sqlite3_file *dbfd);
|
||||
|
||||
int sqlite3ServerBegin(Server *p);
|
||||
int sqlite3ServerEnd(Server *p);
|
||||
int sqlite3ServerReleaseWriteLocks(Server *p);
|
||||
|
||||
int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock);
|
||||
|
||||
int sqlite3ServerHasLock(Server *p, Pgno pgno, int bWrite);
|
||||
|
||||
#endif /* SQLITE_SERVER_H */
|
||||
|
||||
#endif /* SQLITE_SERVER_EDITION */
|
||||
@@ -497,6 +497,7 @@ int sqlite3_exec(
|
||||
#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8))
|
||||
#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8))
|
||||
#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8))
|
||||
#define SQLITE_BUSY_DEADLOCK (SQLITE_BUSY | (3<<8))
|
||||
#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8))
|
||||
#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8))
|
||||
#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8))
|
||||
|
||||
@@ -1101,6 +1101,7 @@ typedef int VList;
|
||||
#include "pcache.h"
|
||||
#include "os.h"
|
||||
#include "mutex.h"
|
||||
#include "server.h"
|
||||
|
||||
/* The SQLITE_EXTRA_DURABLE compile-time option used to set the default
|
||||
** synchronous setting to EXTRA. It is no longer supported.
|
||||
|
||||
+7
-6
@@ -2639,12 +2639,13 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
/* Check for one of the special errors */
|
||||
mrc = p->rc & 0xff;
|
||||
isSpecialError = mrc==SQLITE_NOMEM || mrc==SQLITE_IOERR
|
||||
|| mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL;
|
||||
|| mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL
|
||||
|| p->rc==SQLITE_BUSY_DEADLOCK;
|
||||
if( isSpecialError ){
|
||||
/* If the query was read-only and the error code is SQLITE_INTERRUPT,
|
||||
** no rollback is necessary. Otherwise, at least a savepoint
|
||||
** transaction must be rolled back to restore the database to a
|
||||
** consistent state.
|
||||
/* If the query was read-only and the error code is SQLITE_INTERRUPT
|
||||
** or SQLITE_BUSY_SERVER, no rollback is necessary. Otherwise, at
|
||||
** least a savepoint transaction must be rolled back to restore the
|
||||
** database to a consistent state.
|
||||
**
|
||||
** Even if the statement is read-only, it is important to perform
|
||||
** a statement or transaction rollback operation. If the error
|
||||
@@ -2653,7 +2654,7 @@ int sqlite3VdbeHalt(Vdbe *p){
|
||||
** pagerStress() in pager.c), the rollback is required to restore
|
||||
** the pager to a consistent state.
|
||||
*/
|
||||
if( !p->readOnly || mrc!=SQLITE_INTERRUPT ){
|
||||
if( !p->readOnly || (mrc!=SQLITE_INTERRUPT && mrc!=SQLITE_BUSY) ){
|
||||
if( (mrc==SQLITE_NOMEM || mrc==SQLITE_FULL) && p->usesStmtJournal ){
|
||||
eStatementOp = SAVEPOINT_ROLLBACK;
|
||||
}else{
|
||||
|
||||
@@ -454,8 +454,17 @@ struct Wal {
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
WalIndexHdr *pSnapshot; /* Start transaction here if not NULL */
|
||||
#endif
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
Server *pServer;
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
# define walIsServer(p) ((p)->pServer!=0)
|
||||
#else
|
||||
# define walIsServer(p) 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Candidate values for Wal.exclusiveMode.
|
||||
*/
|
||||
@@ -1261,6 +1270,14 @@ static void walIndexClose(Wal *pWal, int isDelete){
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
int sqlite3WalServer(Wal *pWal, Server *pServer){
|
||||
assert( pWal->pServer==0 );
|
||||
pWal->pServer = pServer;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Open a connection to the WAL file zWalName. The database file must
|
||||
** already be opened on connection pDbFd. The buffer that zWalName points
|
||||
@@ -1892,6 +1909,9 @@ static int walCheckpoint(
|
||||
** indicate that the log file contains zero valid frames. */
|
||||
walRestartHdr(pWal, salt1);
|
||||
rc = sqlite3OsTruncate(pWal->pWalFd, 0);
|
||||
}else if( walIsServer(pWal) ){
|
||||
assert( eMode==SQLITE_CHECKPOINT_RESTART );
|
||||
walRestartHdr(pWal, salt1);
|
||||
}
|
||||
walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1);
|
||||
}
|
||||
@@ -2051,6 +2071,14 @@ static int walIndexTryHdr(Wal *pWal, int *pChanged){
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int walIndexWriteLock(Wal *pWal){
|
||||
if( walIsServer(pWal) ){
|
||||
return sqlite3ServerLock(pWal->pServer, 0, 1, 0);
|
||||
}else{
|
||||
return walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Read the wal-index header from the wal-index and into pWal->hdr.
|
||||
** If the wal-header appears to be corrupt, try to reconstruct the
|
||||
@@ -2091,11 +2119,12 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
|
||||
assert( badHdr==0 || pWal->writeLock==0 );
|
||||
if( badHdr ){
|
||||
if( pWal->readOnly & WAL_SHM_RDONLY ){
|
||||
assert( walIsServer(pWal)==0 );
|
||||
if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){
|
||||
walUnlockShared(pWal, WAL_WRITE_LOCK);
|
||||
rc = SQLITE_READONLY_RECOVERY;
|
||||
}
|
||||
}else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){
|
||||
}else if( SQLITE_OK==(rc = walIndexWriteLock(pWal)) ){
|
||||
pWal->writeLock = 1;
|
||||
if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){
|
||||
badHdr = walIndexTryHdr(pWal, pChanged);
|
||||
@@ -2109,7 +2138,9 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){
|
||||
}
|
||||
}
|
||||
pWal->writeLock = 0;
|
||||
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
|
||||
if( walIsServer(pWal)==0 ){
|
||||
walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2249,6 +2280,9 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){
|
||||
}
|
||||
}
|
||||
|
||||
assert( rc==SQLITE_OK );
|
||||
if( walIsServer(pWal) ) return SQLITE_OK;
|
||||
|
||||
pInfo = walCkptInfo(pWal);
|
||||
if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame
|
||||
#ifdef SQLITE_ENABLE_SNAPSHOT
|
||||
@@ -2588,7 +2622,15 @@ int sqlite3WalFindFrame(
|
||||
int iMinHash;
|
||||
|
||||
/* This routine is only be called from within a read transaction. */
|
||||
assert( pWal->readLock>=0 || pWal->lockError );
|
||||
assert( walIsServer(pWal) || pWal->readLock>=0 || pWal->lockError );
|
||||
|
||||
assert( walIsServer(pWal)==0 || pWal->writeLock==0
|
||||
|| sqlite3ServerHasLock(pWal->pServer, 0, 1)
|
||||
);
|
||||
if( walIsServer(pWal) && pWal->writeLock==0 ){
|
||||
/* A server mode connection must read from the most recent snapshot. */
|
||||
iLast = walIndexHdr(pWal)->mxFrame;
|
||||
}
|
||||
|
||||
/* If the "last page" field of the wal-index header snapshot is 0, then
|
||||
** no data will be read from the wal under any circumstances. Return early
|
||||
@@ -2700,7 +2742,7 @@ int sqlite3WalReadFrame(
|
||||
** Return the size of the database in pages (or zero, if unknown).
|
||||
*/
|
||||
Pgno sqlite3WalDbsize(Wal *pWal){
|
||||
if( pWal && ALWAYS(pWal->readLock>=0) ){
|
||||
if( pWal && (walIsServer(pWal) || ALWAYS(pWal->readLock>=0)) ){
|
||||
return pWal->hdr.nPage;
|
||||
}
|
||||
return 0;
|
||||
@@ -2725,13 +2767,18 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){
|
||||
|
||||
/* Cannot start a write transaction without first holding a read
|
||||
** transaction. */
|
||||
assert( pWal->readLock>=0 );
|
||||
assert( walIsServer(pWal) || pWal->readLock>=0 );
|
||||
assert( pWal->writeLock==0 && pWal->iReCksum==0 );
|
||||
|
||||
if( pWal->readOnly ){
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
|
||||
/* For a server connection, do nothing at this point. */
|
||||
if( walIsServer(pWal) ){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* Only one writer allowed at a time. Get the write lock. Return
|
||||
** SQLITE_BUSY if unable.
|
||||
*/
|
||||
@@ -2782,7 +2829,7 @@ int sqlite3WalEndWriteTransaction(Wal *pWal){
|
||||
*/
|
||||
int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *pUndoCtx){
|
||||
int rc = SQLITE_OK;
|
||||
if( ALWAYS(pWal->writeLock) ){
|
||||
if( pWal->writeLock ){
|
||||
Pgno iMax = pWal->hdr.mxFrame;
|
||||
Pgno iFrame;
|
||||
|
||||
@@ -2872,11 +2919,13 @@ int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){
|
||||
** if an error occurs.
|
||||
*/
|
||||
static int walRestartLog(Wal *pWal){
|
||||
volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
|
||||
int rc = SQLITE_OK;
|
||||
int cnt;
|
||||
|
||||
if( pWal->readLock==0 ){
|
||||
volatile WalCkptInfo *pInfo = walCkptInfo(pWal);
|
||||
if( pWal->readLock==0
|
||||
|| (walIsServer(pWal) && pInfo->nBackfill==pWal->hdr.mxFrame)
|
||||
){
|
||||
assert( pInfo->nBackfill==pWal->hdr.mxFrame );
|
||||
if( pInfo->nBackfill>0 ){
|
||||
u32 salt1;
|
||||
@@ -2898,6 +2947,7 @@ static int walRestartLog(Wal *pWal){
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
if( walIsServer(pWal) ) return rc;
|
||||
walUnlockShared(pWal, WAL_READ_LOCK(0));
|
||||
pWal->readLock = -1;
|
||||
cnt = 0;
|
||||
@@ -3058,7 +3108,23 @@ int sqlite3WalFrames(
|
||||
WalIndexHdr *pLive; /* Pointer to shared header */
|
||||
|
||||
assert( pList );
|
||||
assert( pWal->writeLock );
|
||||
assert( pWal->writeLock || walIsServer(pWal) );
|
||||
if( pWal->writeLock==0 ){
|
||||
int bDummy = 0;
|
||||
#if 0
|
||||
rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1);
|
||||
#endif
|
||||
rc = sqlite3ServerLock(pWal->pServer, 0, 1, 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
pWal->writeLock = 1;
|
||||
rc = walIndexTryHdr(pWal, &bDummy);
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
return rc;
|
||||
}
|
||||
assert( sqlite3ServerHasLock(pWal->pServer, 0, 1) );
|
||||
}
|
||||
assert( walIsServer(pWal)==0 || sqlite3ServerHasLock(pWal->pServer, 0, 1) );
|
||||
|
||||
/* If this frame set completes a transaction, then nTruncate>0. If
|
||||
** nTruncate==0 then this frame set does not complete the transaction. */
|
||||
@@ -3079,7 +3145,7 @@ int sqlite3WalFrames(
|
||||
/* See if it is possible to write these frames into the start of the
|
||||
** log file, instead of appending to it at pWal->hdr.mxFrame.
|
||||
*/
|
||||
if( SQLITE_OK!=(rc = walRestartLog(pWal)) ){
|
||||
if( walIsServer(pWal)==0 && SQLITE_OK!=(rc = walRestartLog(pWal)) ){
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -3331,7 +3397,19 @@ int sqlite3WalCheckpoint(
|
||||
** lock is successfully obtained.
|
||||
*/
|
||||
if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){
|
||||
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1);
|
||||
if( walIsServer(pWal) ){
|
||||
rc = sqlite3ServerBegin(pWal->pServer);
|
||||
if( rc!=SQLITE_OK ) goto ckpt_out;
|
||||
if( eMode>=SQLITE_CHECKPOINT_RESTART ){
|
||||
/* Exclusive lock on page 1. This is exclusive access to the db. */
|
||||
rc = sqlite3ServerLock(pWal->pServer, 1, 1, 1);
|
||||
}else{
|
||||
/* Take the server write-lock ("page" 0) */
|
||||
rc = sqlite3ServerLock(pWal->pServer, 0, 1, 1);
|
||||
}
|
||||
}else{
|
||||
rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
pWal->writeLock = 1;
|
||||
}else if( rc==SQLITE_BUSY ){
|
||||
@@ -3376,10 +3454,12 @@ int sqlite3WalCheckpoint(
|
||||
}
|
||||
|
||||
/* Release the locks. */
|
||||
ckpt_out:
|
||||
sqlite3WalEndWriteTransaction(pWal);
|
||||
walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1);
|
||||
pWal->ckptLock = 0;
|
||||
WALTRACE(("WAL%p: checkpoint %s\n", pWal, rc ? "failed" : "ok"));
|
||||
if( walIsServer(pWal) ) sqlite3ServerEnd(pWal->pServer);
|
||||
return (rc==SQLITE_OK && eMode!=eMode2 ? SQLITE_BUSY : rc);
|
||||
}
|
||||
|
||||
|
||||
@@ -144,5 +144,9 @@ int sqlite3WalFramesize(Wal *pWal);
|
||||
/* Return the sqlite3_file object for the WAL file */
|
||||
sqlite3_file *sqlite3WalFile(Wal *pWal);
|
||||
|
||||
#ifdef SQLITE_SERVER_EDITION
|
||||
int sqlite3WalServer(Wal *pWal, Server *pServer);
|
||||
#endif
|
||||
|
||||
#endif /* ifndef SQLITE_OMIT_WAL */
|
||||
#endif /* SQLITE_WAL_H */
|
||||
|
||||
@@ -273,6 +273,15 @@ test_suite "fts5" -prefix "" -description {
|
||||
All FTS5 tests.
|
||||
} -files [glob -nocomplain $::testdir/../ext/fts5/test/*.test]
|
||||
|
||||
test_suite "server" -prefix "" -description {
|
||||
All server-edition tests.
|
||||
} -files [
|
||||
test_set \
|
||||
select1.test \
|
||||
[glob -nocomplain $::testdir/server*.test] \
|
||||
-exclude *server1.test
|
||||
]
|
||||
|
||||
test_suite "fts5-light" -prefix "" -description {
|
||||
All FTS5 tests.
|
||||
} -files [
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# 2017 April 25
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this script is testing the server mode of SQLite.
|
||||
#
|
||||
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix server2
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Check that the *-hma file is deleted correctly.
|
||||
#
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
} {}
|
||||
do_test 1.1 {
|
||||
file exists test.db-hma
|
||||
} {1}
|
||||
do_test 1.2 {
|
||||
db close
|
||||
file exists test.db-hma
|
||||
} {0}
|
||||
do_test 1.3 {
|
||||
sqlite3 db test.db
|
||||
db eval { CREATE TABLE t2(a, b) }
|
||||
sqlite3 db2 test.db
|
||||
db2 eval { CREATE TABLE t3(a, b) }
|
||||
file exists test.db-hma
|
||||
} {1}
|
||||
do_test 1.4 {
|
||||
db2 close
|
||||
file exists test.db-hma
|
||||
} {1}
|
||||
integrity_check 1.5
|
||||
do_test 1.6 {
|
||||
db close
|
||||
file exists test.db-hma
|
||||
} {0}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
sqlite3 db2 test.db
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE TABLE t2(c, d);
|
||||
}
|
||||
|
||||
# Two concurrent transactions committed.
|
||||
#
|
||||
do_test 2.1 {
|
||||
db eval {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
}
|
||||
db2 eval {
|
||||
BEGIN;
|
||||
INSERT INTO t2 VALUES(3, 4);
|
||||
}
|
||||
} {}
|
||||
do_test 2.2 {
|
||||
lsort [glob test.db*]
|
||||
} {test.db test.db-hma test.db-journal0 test.db-journal1}
|
||||
do_test 2.3.1 { db eval COMMIT } {}
|
||||
do_test 2.3.2 { db2 eval COMMIT } {}
|
||||
do_execsql_test 2.4 {SELECT * FROM t1, t2} {1 2 3 4}
|
||||
do_test 2.5 {
|
||||
lsort [glob test.db*]
|
||||
} {test.db test.db-hma test.db-journal0 test.db-journal1}
|
||||
|
||||
do_test 2.6 {
|
||||
execsql {BEGIN}
|
||||
execsql {INSERT INTO t1 VALUES(5, 6)}
|
||||
|
||||
execsql {BEGIN} db2
|
||||
catchsql {INSERT INTO t1 VALUES(7, 8)} db2
|
||||
} {1 {database is locked}}
|
||||
do_test 2.7 {
|
||||
# Transaction is automatically rolled back in this case.
|
||||
sqlite3_get_autocommit db2
|
||||
} {1}
|
||||
do_test 2.8 {
|
||||
execsql COMMIT
|
||||
execsql { SELECT * FROM t1 } db2
|
||||
} {1 2 5 6}
|
||||
db2 close
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
CREATE TABLE t1(a, b);
|
||||
}
|
||||
|
||||
do_test 3.1 {
|
||||
glob test.db*
|
||||
} {test.db-journal0 test.db test.db-hma}
|
||||
|
||||
do_test 3.2 {
|
||||
db close
|
||||
glob test.db*
|
||||
} {test.db}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# 2017 April 25
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this script is testing the server mode of SQLite.
|
||||
#
|
||||
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
source $testdir/lock_common.tcl
|
||||
set testprefix server3
|
||||
|
||||
db close
|
||||
|
||||
do_multiclient_test tn {
|
||||
do_test $tn.1 {
|
||||
sql1 { CREATE TABLE t1(a, b) }
|
||||
sql2 { CREATE TABLE t2(a, b) }
|
||||
} {}
|
||||
|
||||
do_test $tn.2 {
|
||||
sql1 {
|
||||
INSERT INTO t2 VALUES(1, 2);
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test $tn.3 { csql2 { SELECT * FROM t1 } } {1 {database is locked}}
|
||||
do_test $tn.4 { csql2 { SELECT * FROM t1 } } {1 {database is locked}}
|
||||
do_test $tn.5 { sql2 { SELECT * FROM t2 } } {1 2}
|
||||
|
||||
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# 2017 April 27
|
||||
#
|
||||
# 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
|
||||
set testprefix servercrash
|
||||
|
||||
ifcapable !crashtest {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
do_not_use_codec
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA page_siBlockze = 4096;
|
||||
PRAGMA auto_vacuum = OFF;
|
||||
CREATE TABLE t1(a, b);
|
||||
CREATE TABLE t2(c, d);
|
||||
|
||||
INSERT INTO t1 VALUES(1, 2), (3, 4);
|
||||
INSERT INTO t2 VALUES(1, 2), (3, 4);
|
||||
}
|
||||
|
||||
for {set i 0} {$i < 10} {incr i} {
|
||||
do_test 1.$i.1 {
|
||||
crashsql -delay 1 -file test.db { INSERT INTO t1 VALUES(5, 6) }
|
||||
} {1 {child process exited abnormally}}
|
||||
|
||||
do_execsql_test 1.$i.2 {
|
||||
SELECT * FROM t1
|
||||
} {1 2 3 4}
|
||||
}
|
||||
|
||||
for {set i 0} {$i < 10} {incr i} {
|
||||
do_test 2.$i.1 {
|
||||
crashsql -delay 1 -file test.db { INSERT INTO t1 VALUES(5, 6) }
|
||||
} {1 {child process exited abnormally}}
|
||||
|
||||
do_test 2.$i.2 {
|
||||
sqlite3 dbX test.db
|
||||
execsql { SELECT * FROM t1 } dbX
|
||||
} {1 2 3 4}
|
||||
dbX close
|
||||
}
|
||||
|
||||
db close
|
||||
for {set i 0} {$i < 10} {incr i} {
|
||||
do_test 3.$i.1 {
|
||||
crashsql -delay 1 -file test.db { INSERT INTO t1 VALUES(5, 6) }
|
||||
} {1 {child process exited abnormally}}
|
||||
|
||||
sqlite3 db test.db
|
||||
do_execsql_test 3.$i.2 { SELECT * FROM t1 } {1 2 3 4}
|
||||
db close
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
# 2017 April 25
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this script is testing the server mode of SQLite.
|
||||
#
|
||||
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix serverwal
|
||||
|
||||
# Check files are created and deleted as expected.
|
||||
#
|
||||
do_execsql_test 1.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
} {wal}
|
||||
do_execsql_test 1.1 {
|
||||
CREATE TABLE t1(a, b);
|
||||
}
|
||||
do_execsql_test 1.2 {
|
||||
SELECT * FROM t1;
|
||||
} {}
|
||||
do_test 1.3 {
|
||||
lsort [glob test.db*]
|
||||
} {test.db test.db-hma test.db-shm test.db-wal}
|
||||
do_test 1.4 {
|
||||
db close
|
||||
glob test.db*
|
||||
} {test.db}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Two concurrent transactions.
|
||||
#
|
||||
do_test 2.0 {
|
||||
sqlite3 db test.db
|
||||
sqlite3 db2 test.db
|
||||
db eval {
|
||||
CREATE TABLE t2(a, b);
|
||||
}
|
||||
} {}
|
||||
do_test 2.1 {
|
||||
execsql {
|
||||
BEGIN;
|
||||
INSERT INTO t1 VALUES(1, 2);
|
||||
} db
|
||||
execsql {
|
||||
BEGIN;
|
||||
INSERT INTO t2 VALUES(1, 2);
|
||||
} db2
|
||||
} {}
|
||||
do_test 2.2 {
|
||||
execsql COMMIT db
|
||||
execsql COMMIT db2
|
||||
} {}
|
||||
db close
|
||||
db2 close
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# That the wal file can be wrapped around.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 3.0 {
|
||||
PRAGMA journal_mode = wal;
|
||||
CREATE TABLE ttt(a, b);
|
||||
INSERT INTO ttt VALUES(1, 2);
|
||||
INSERT INTO ttt VALUES(3, 4);
|
||||
INSERT INTO ttt VALUES(5, 6);
|
||||
INSERT INTO ttt VALUES(7, 8);
|
||||
INSERT INTO ttt VALUES(9, 10);
|
||||
} {wal}
|
||||
|
||||
do_test 3.1 {
|
||||
set N [file size test.db-wal]
|
||||
execsql {
|
||||
PRAGMA wal_checkpoint = restart;
|
||||
INSERT INTO ttt VALUES(11, 12);
|
||||
INSERT INTO ttt VALUES(13, 14);
|
||||
}
|
||||
expr {$N == [file size test.db-wal]}
|
||||
} {1}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# That ROLLBACK appears to work.
|
||||
#
|
||||
reset_db
|
||||
do_execsql_test 4.0 {
|
||||
PRAGMA cache_size = 10;
|
||||
CREATE TABLE ttt(a, b);
|
||||
CREATE INDEX yyy ON ttt(b, a);
|
||||
PRAGMA journal_mode = wal;
|
||||
WITH s(i) AS (
|
||||
SELECT 1 UNION ALL SELECT i+1 FROM s WHERE i<100
|
||||
)
|
||||
INSERT INTO ttt SELECT randomblob(100), randomblob(100) FROM s;
|
||||
} {wal}
|
||||
|
||||
do_execsql_test 4.1 {
|
||||
PRAGMA integrity_check;
|
||||
BEGIN;
|
||||
UPDATE ttt SET b=a;
|
||||
ROLLBACK;
|
||||
PRAGMA integrity_check;
|
||||
} {ok ok}
|
||||
|
||||
reset_db
|
||||
do_execsql_test 5.1 {
|
||||
CREATE TABLE xyz(a);
|
||||
PRAGMA journal_mode = wal;
|
||||
INSERT INTO xyz VALUES(1);
|
||||
INSERT INTO xyz VALUES(2);
|
||||
INSERT INTO xyz VALUES(3);
|
||||
} {wal}
|
||||
|
||||
breakpoint
|
||||
|
||||
do_test 5.2 {
|
||||
sqlite3 db2 test.db
|
||||
execsql { SELECT * FROM xyz } db2
|
||||
} {1 2 3}
|
||||
|
||||
do_execsql_test 5.3 {
|
||||
PRAGMA wal_checkpoint = restart
|
||||
} {0 0 0}
|
||||
|
||||
do_test 5.4 {
|
||||
execsql { SELECT * FROM xyz } db2
|
||||
} {1 2 3}
|
||||
|
||||
finish_test
|
||||
|
||||
@@ -586,6 +586,10 @@ proc reset_db {} {
|
||||
forcedelete test.db
|
||||
forcedelete test.db-journal
|
||||
forcedelete test.db-wal
|
||||
for {set i 0} {$i < 16} {incr i} {
|
||||
forcedelete test.db-journal$i
|
||||
}
|
||||
|
||||
sqlite3 db ./test.db
|
||||
set ::DB [sqlite3_connection_pointer db]
|
||||
if {[info exists ::SETUP_SQL]} {
|
||||
|
||||
@@ -114,6 +114,7 @@ foreach hdr {
|
||||
pcache.h
|
||||
pragma.h
|
||||
rtree.h
|
||||
server.h
|
||||
sqlite3session.h
|
||||
sqlite3.h
|
||||
sqlite3ext.h
|
||||
@@ -319,6 +320,7 @@ foreach file {
|
||||
rowset.c
|
||||
pager.c
|
||||
wal.c
|
||||
server.c
|
||||
|
||||
btmutex.c
|
||||
btree.c
|
||||
|
||||
+442
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
** 2017 June 7
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
*************************************************************************
|
||||
**
|
||||
** Simple multi-threaded server used for informal testing of concurrency
|
||||
** between connections in different threads. Listens for tcp/ip connections
|
||||
** on port 9999 of the 127.0.0.1 interface only. To build:
|
||||
**
|
||||
** gcc -g $(TOP)/tool/tserver.c sqlite3.o -lpthread -o tserver
|
||||
**
|
||||
** To run using "x.db" as the db file:
|
||||
**
|
||||
** ./tserver x.db
|
||||
**
|
||||
** To connect, open a client socket on port 9999 and start sending commands.
|
||||
** Commands are either SQL - which must be terminated by a semi-colon, or
|
||||
** dot-commands, which must be terminated by a newline. If an SQL statement
|
||||
** is seen, it is prepared and added to an internal list.
|
||||
**
|
||||
** Dot-commands are:
|
||||
**
|
||||
** .list Display all SQL statements in the list.
|
||||
** .quit Disconnect.
|
||||
** .run Run all SQL statements in the list.
|
||||
** .repeats N Configure the number of repeats per ".run".
|
||||
** .seconds N Configure the number of seconds to ".run" for.
|
||||
**
|
||||
** Example input:
|
||||
**
|
||||
** BEGIN;
|
||||
** INSERT INTO t1 VALUES(randomblob(10), randomblob(100));
|
||||
** INSERT INTO t1 VALUES(randomblob(10), randomblob(100));
|
||||
** INSERT INTO t1 VALUES(randomblob(10), randomblob(100));
|
||||
** COMMIT;
|
||||
** .repeats 100000
|
||||
** .run
|
||||
**
|
||||
*/
|
||||
#define TSERVER_PORTNUMBER 9999
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sqlite3.h"
|
||||
|
||||
/* Database used by this server */
|
||||
static char *zDatabaseName = 0;
|
||||
|
||||
typedef struct ClientCtx ClientCtx;
|
||||
struct ClientCtx {
|
||||
sqlite3 *db; /* Database handle for this client */
|
||||
int fd; /* Client fd */
|
||||
int nRepeat; /* Number of times to repeat SQL */
|
||||
int nSecond; /* Number of seconds to run for */
|
||||
sqlite3_stmt **apPrepare; /* Array of prepared statements */
|
||||
int nPrepare; /* Valid size of apPrepare[] */
|
||||
int nAlloc; /* Allocated size of apPrepare[] */
|
||||
};
|
||||
|
||||
static int is_eol(int i){
|
||||
return (i=='\n' || i=='\r');
|
||||
}
|
||||
static int is_whitespace(int i){
|
||||
return (i==' ' || i=='\t' || is_eol(i));
|
||||
}
|
||||
|
||||
static void trim_string(const char **pzStr, int *pnStr){
|
||||
const char *zStr = *pzStr;
|
||||
int nStr = *pnStr;
|
||||
|
||||
while( nStr>0 && is_whitespace(zStr[0]) ){
|
||||
zStr++;
|
||||
nStr--;
|
||||
}
|
||||
while( nStr>0 && is_whitespace(zStr[nStr-1]) ){
|
||||
nStr--;
|
||||
}
|
||||
|
||||
*pzStr = zStr;
|
||||
*pnStr = nStr;
|
||||
}
|
||||
|
||||
static int send_message(ClientCtx *p, const char *zFmt, ...){
|
||||
char *zMsg;
|
||||
va_list ap; /* Vararg list */
|
||||
va_start(ap, zFmt);
|
||||
int res = -1;
|
||||
|
||||
zMsg = sqlite3_vmprintf(zFmt, ap);
|
||||
if( zMsg ){
|
||||
res = write(p->fd, zMsg, strlen(zMsg));
|
||||
}
|
||||
sqlite3_free(zMsg);
|
||||
va_end(ap);
|
||||
|
||||
return (res<0);
|
||||
}
|
||||
|
||||
static int handle_some_sql(ClientCtx *p, const char *zSql, int nSql){
|
||||
const char *zTail = zSql;
|
||||
int nTail = nSql;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
while( rc==SQLITE_OK ){
|
||||
if( p->nPrepare>=p->nAlloc ){
|
||||
int nByte = (p->nPrepare+32) * sizeof(sqlite3_stmt*);
|
||||
sqlite3_stmt **apNew = sqlite3_realloc(p->apPrepare, nByte);
|
||||
if( apNew ){
|
||||
p->apPrepare = apNew;
|
||||
p->nAlloc = p->nPrepare+32;
|
||||
}else{
|
||||
rc = SQLITE_NOMEM;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rc = sqlite3_prepare_v2(
|
||||
p->db, zTail, nTail, &p->apPrepare[p->nPrepare], &zTail
|
||||
);
|
||||
if( rc!=SQLITE_OK ){
|
||||
send_message(p, "error - %s\n", sqlite3_errmsg(p->db));
|
||||
rc = 1;
|
||||
break;
|
||||
}
|
||||
if( p->apPrepare[p->nPrepare]==0 ){
|
||||
break;
|
||||
}
|
||||
p->nPrepare++;
|
||||
nTail = nSql - (zTail-zSql);
|
||||
rc = send_message(p, "ok (%d SQL statements)\n", p->nPrepare);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static sqlite3_int64 get_timer(void){
|
||||
struct timeval t;
|
||||
gettimeofday(&t, 0);
|
||||
return ((sqlite3_int64)t.tv_usec / 1000) + ((sqlite3_int64)t.tv_sec * 1000);
|
||||
}
|
||||
|
||||
static void clear_sql(ClientCtx *p){
|
||||
int j;
|
||||
for(j=0; j<p->nPrepare; j++){
|
||||
sqlite3_finalize(p->apPrepare[j]);
|
||||
}
|
||||
p->nPrepare = 0;
|
||||
}
|
||||
|
||||
static int handle_dot_command(ClientCtx *p, const char *zCmd, int nCmd){
|
||||
assert( zCmd[0]=='.' );
|
||||
int n;
|
||||
int rc = 0;
|
||||
const char *z = &zCmd[1];
|
||||
const char *zArg;
|
||||
int nArg;
|
||||
|
||||
for(n=0; n<(nCmd-1); n++){
|
||||
if( is_whitespace(z[n]) ) break;
|
||||
}
|
||||
|
||||
zArg = &z[n];
|
||||
nArg = nCmd-n;
|
||||
trim_string(&zArg, &nArg);
|
||||
|
||||
if( n>=1 && n<=4 && 0==strncmp(z, "list", n) ){
|
||||
int i;
|
||||
for(i=0; rc==0 && i<p->nPrepare; i++){
|
||||
const char *zSql = sqlite3_sql(p->apPrepare[i]);
|
||||
int nSql = strlen(zSql);
|
||||
trim_string(&zSql, &nSql);
|
||||
rc = send_message(p, "%d: %.*s\n", i, nSql, zSql);
|
||||
}
|
||||
}
|
||||
|
||||
else if( n>=1 && n<=4 && 0==strncmp(z, "quit", n) ){
|
||||
rc = 1;
|
||||
}
|
||||
|
||||
else if( n>=2 && n<=7 && 0==strncmp(z, "repeats", n) ){
|
||||
if( nArg ){
|
||||
p->nRepeat = strtol(zArg, 0, 0);
|
||||
if( p->nRepeat>0 ) p->nSecond = 0;
|
||||
}
|
||||
rc = send_message(p, "ok (repeat=%d)\n", p->nRepeat);
|
||||
}
|
||||
|
||||
else if( n>=2 && n<=3 && 0==strncmp(z, "run", n) ){
|
||||
int i, j;
|
||||
int nBusy = 0;
|
||||
sqlite3_int64 t0 = get_timer();
|
||||
sqlite3_int64 t1 = t0;
|
||||
int nT1 = 0;
|
||||
int nTBusy1 = 0;
|
||||
|
||||
for(j=0; (p->nRepeat<=0 || j<p->nRepeat) && rc==SQLITE_OK; j++){
|
||||
sqlite3_int64 t2;
|
||||
|
||||
for(i=0; i<p->nPrepare && rc==SQLITE_OK; i++){
|
||||
sqlite3_stmt *pStmt = p->apPrepare[i];
|
||||
|
||||
/* Execute the statement */
|
||||
while( sqlite3_step(pStmt)==SQLITE_ROW );
|
||||
rc = sqlite3_reset(pStmt);
|
||||
|
||||
if( (rc & 0xFF)==SQLITE_BUSY ){
|
||||
if( sqlite3_get_autocommit(p->db)==0 ){
|
||||
sqlite3_exec(p->db, "ROLLBACK", 0, 0, 0);
|
||||
}
|
||||
nBusy++;
|
||||
rc = SQLITE_OK;
|
||||
break;
|
||||
}
|
||||
else if( rc!=SQLITE_OK ){
|
||||
send_message(p, "error - %s\n", sqlite3_errmsg(p->db));
|
||||
}
|
||||
}
|
||||
|
||||
t2 = get_timer();
|
||||
if( t2>=(t1+1000) ){
|
||||
int nMs = (t2 - t1);
|
||||
int nDone = (j+1 - nBusy - nT1);
|
||||
|
||||
rc = send_message(
|
||||
p, "(%d done @ %d per second, %d busy)\n",
|
||||
nDone, (1000*nDone + nMs/2) / nMs, nBusy - nTBusy1
|
||||
);
|
||||
t1 = t2;
|
||||
nT1 = j+1 - nBusy;
|
||||
nTBusy1 = nBusy;
|
||||
if( p->nSecond>0 && (p->nSecond*1000)<=t1-t0 ) break;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
send_message(p, "ok (%d/%d SQLITE_BUSY)\n", nBusy, j);
|
||||
}
|
||||
clear_sql(p);
|
||||
}
|
||||
|
||||
else if( n>=1 && n<=7 && 0==strncmp(z, "seconds", n) ){
|
||||
if( nArg ){
|
||||
p->nSecond = strtol(zArg, 0, 0);
|
||||
if( p->nSecond>0 ) p->nRepeat = 0;
|
||||
}
|
||||
rc = send_message(p, "ok (repeat=%d)\n", p->nRepeat);
|
||||
}
|
||||
|
||||
else{
|
||||
send_message(p,
|
||||
"unrecognized dot command: %.*s\n"
|
||||
"should be \"list\", \"run\", \"repeats\", or \"seconds\"\n", n, z
|
||||
);
|
||||
rc = 1;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void *handle_client(void *pArg){
|
||||
char zCmd[32*1024]; /* Read buffer */
|
||||
int nCmd = 0; /* Valid bytes in zCmd[] */
|
||||
int res; /* Result of read() call */
|
||||
int rc = SQLITE_OK;
|
||||
int j;
|
||||
|
||||
ClientCtx ctx;
|
||||
memset(&ctx, 0, sizeof(ClientCtx));
|
||||
|
||||
ctx.fd = (int)(intptr_t)pArg;
|
||||
ctx.nRepeat = 1;
|
||||
rc = sqlite3_open(zDatabaseName, &ctx.db);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3_open(): %s\n", sqlite3_errmsg(ctx.db));
|
||||
return 0;
|
||||
}
|
||||
|
||||
while( rc==SQLITE_OK ){
|
||||
int i;
|
||||
int iStart;
|
||||
int nConsume;
|
||||
res = read(ctx.fd, &zCmd[nCmd], sizeof(zCmd)-nCmd-1);
|
||||
if( res<=0 ) break;
|
||||
nCmd += res;
|
||||
if( nCmd>=sizeof(zCmd)-1 ){
|
||||
fprintf(stderr, "oversized (>32KiB) message\n");
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
zCmd[nCmd] = '\0';
|
||||
|
||||
do {
|
||||
nConsume = 0;
|
||||
|
||||
/* Gobble up any whitespace */
|
||||
iStart = 0;
|
||||
while( is_whitespace(zCmd[iStart]) ) iStart++;
|
||||
|
||||
if( zCmd[iStart]=='.' ){
|
||||
/* This is a dot-command. Search for end-of-line. */
|
||||
for(i=iStart; i<nCmd; i++){
|
||||
if( is_eol(zCmd[i]) ){
|
||||
rc = handle_dot_command(&ctx, &zCmd[iStart], i-iStart);
|
||||
nConsume = i+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
|
||||
int iSemi;
|
||||
char c = 0;
|
||||
for(iSemi=iStart; iSemi<nCmd; iSemi++){
|
||||
if( zCmd[iSemi]==';' ){
|
||||
c = zCmd[iSemi+1];
|
||||
zCmd[iSemi+1] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( iSemi<nCmd ){
|
||||
if( sqlite3_complete(zCmd) ){
|
||||
rc = handle_some_sql(&ctx, zCmd, iSemi+1);
|
||||
nConsume = iSemi+1;
|
||||
}
|
||||
|
||||
if( c ){
|
||||
zCmd[iSemi+1] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( nConsume>0 ){
|
||||
nCmd = nCmd-nConsume;
|
||||
if( nCmd>0 ){
|
||||
memmove(zCmd, &zCmd[nConsume], nCmd);
|
||||
}
|
||||
}
|
||||
}while( rc==SQLITE_OK && nConsume>0 );
|
||||
}
|
||||
|
||||
fprintf(stdout, "Client %d disconnects\n", ctx.fd);
|
||||
close(ctx.fd);
|
||||
clear_sql(&ctx);
|
||||
sqlite3_free(ctx.apPrepare);
|
||||
sqlite3_close(ctx.db);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
sqlite3 *db;
|
||||
int sfd;
|
||||
int rc;
|
||||
int yes = 1;
|
||||
struct sockaddr_in server;
|
||||
|
||||
/* Ignore SIGPIPE. Otherwise the server exits if a client disconnects
|
||||
** abruptly. */
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
if( argc!=2 ){
|
||||
fprintf(stderr, "Usage: %s DATABASE\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
zDatabaseName = argv[1];
|
||||
|
||||
rc = sqlite3_open(zDatabaseName, &db);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3_open(): %s\n", sqlite3_errmsg(db));
|
||||
return 1;
|
||||
}
|
||||
|
||||
rc = sqlite3_exec(db, "SELECT * FROM sqlite_master", 0, 0, 0);
|
||||
if( rc!=SQLITE_OK ){
|
||||
fprintf(stderr, "sqlite3_exec(): %s\n", sqlite3_errmsg(db));
|
||||
return 1;
|
||||
}
|
||||
|
||||
sfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if( sfd<0 ){
|
||||
fprintf(stderr, "socket() failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||
if( rc<0 ){
|
||||
perror("setsockopt");
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(&server, 0, sizeof(server));
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
server.sin_port = htons(TSERVER_PORTNUMBER);
|
||||
|
||||
rc = bind(sfd, (struct sockaddr *)&server, sizeof(struct sockaddr));
|
||||
if( rc<0 ){
|
||||
fprintf(stderr, "bind() failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
rc = listen(sfd, 8);
|
||||
if( rc<0 ){
|
||||
fprintf(stderr, "listen() failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while( 1 ){
|
||||
pthread_t tid;
|
||||
int cfd = accept(sfd, NULL, NULL);
|
||||
if( cfd<0 ){
|
||||
perror("accept()");
|
||||
return 1;
|
||||
}
|
||||
|
||||
fprintf(stdout, "Client %d connects\n", cfd);
|
||||
rc = pthread_create(&tid, NULL, handle_client, (void*)(intptr_t)cfd);
|
||||
if( rc!=0 ){
|
||||
perror("pthread_create()");
|
||||
return 1;
|
||||
}
|
||||
|
||||
pthread_detach(tid);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user