From: dan Date: Sat, 8 Jul 2017 20:46:17 +0000 (+0000) Subject: Support read-only MVCC transactions in server-mode. Started using "BEGIN X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=087794b136d706c41e90a159cbe8ec8dc1ea5e50;p=thirdparty%2Fsqlite.git Support read-only MVCC transactions in server-mode. Started using "BEGIN READONLY". FossilOrigin-Name: 5a043aa8dd0751e644c495a59deea5fe05da905f49c664d978fe477f9240bc37 --- diff --git a/manifest b/manifest index c4058dab5c..51a8e8f02d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\slatest\strunk\schanges\swith\sthis\sbranch. -D 2017-07-07T16:40:28.683 +C Support\sread-only\sMVCC\stransactions\sin\sserver-mode.\sStarted\susing\s"BEGIN\nREADONLY". +D 2017-07-08T20:46:17.624 F Makefile.in 081e48dfe7f995d57ce1a88ddf4d2917b4349158648a6cd45b42beae30de3a12 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc 4ebb1d257cac7fb1bcb4ba59278416d410ff1c4bf59447a9c37a415f3516056a @@ -391,10 +391,10 @@ F src/auth.c 79f96c6f33bf0e5da8d1c282cee5ebb1852bb8a6ccca3e485d7c459b035d9c3c F src/backup.c faf17e60b43233c214aae6a8179d24503a61e83b F src/bitvec.c 17ea48eff8ba979f1f5b04cc484c7bb2be632f33 F src/btmutex.c 0e9ce2d56159b89b9bc8e197e023ee11e39ff8ca -F src/btree.c 6ff4027f896c6d7b653e4ef20c4fc7ee52ca0510dec16e76cce0b34d4eb8ead7 +F src/btree.c 0e1bcecde07e3e022a1a1f16cdfa6817267c25eb4a48ea21fe9359b973837762 F src/btree.h 3edc5329bc59534d2d15b4f069a9f54b779a7e51289e98fa481ae3c0e526a5ca F src/btreeInt.h a392d353104b4add58b4a59cb185f5d5693dde832c565b77d8d4c343ed98f610 -F src/build.c 74108007d286232fb4290464ee5452fa860c26215f8caa0e6c7cbf69a6fafe8f +F src/build.c 32107046e8cda19a917b4a397175c7eb5f2e234d19fae2fc6164859e4a3d4704 F src/callback.c 8e14b60d1ed1c87c02cb5f121ecda99224f2aea6524a77ee6f72c9b5c7110f84 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 928954802b1397d9fb1378c7eb702c94b4735bbab1d5793e21b6a77734f56a1b @@ -435,9 +435,9 @@ F src/os_setup.h 0dbaea40a7d36bf311613d31342e0b99e2536586 F src/os_unix.c d3c3a186ceb27d363f7a5e281ece4eb32b56e173a331e153a761d280034e3212 F src/os_win.c 2a6c73eef01c51a048cc4ddccd57f981afbec18a F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a -F src/pager.c 542bf64a097f84ab37319b90b5f2db1a4aab21968252c9e9d242eb85cf75f55d -F src/pager.h 81d2c28e92e543cb1ed977f44810d04c34be2a23d7782bb02548d86a4b9ae657 -F src/parse.y 71bf06b02f567232deabe258641b03e833e93c4dee61a120765ad74c13e7faec +F src/pager.c 7d0bec5dc1411cf3a5771d8608953f2eaa856dcd506c3a444060897139f7eb21 +F src/pager.h 857d21f70acefc76e5bae4584f0133e754a2448b197d1e79b7179f26f9a11bc1 +F src/parse.y f732ca9ef4dbee9ba19d6d30d2d1f7ac0834d8642515dedbd8f6ebd16ab2a8e3 F src/pcache.c 62835bed959e2914edd26afadfecce29ece0e870 F src/pcache.h 521bb9610d38ef17a3cc9b5ddafd4546c2ea67fa3d0e464823d73c2a28d50e11 F src/pcache1.c 1195a21fe28e223e024f900b2011e80df53793f0356a24caace4188b098540dc @@ -449,19 +449,19 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 F src/resolve.c 6aa1fb1212e601f65b983ee1215d69a591986c8f97a3805c425c625a53839539 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac F src/select.c c9f7b7196e196e93979665680d055a789938b8e025556097bf484b184c0dd576 -F src/server.c 4b76241f63e07683fe30db6f6b8a334867b75c4cc5e87a8374b649cf6b8b0daa -F src/server.h 31645080b17836bcb193a3e5ea8e60d7ae3e4cee59e8f9450b17cdbf452a2b1e +F src/server.c f7b99d79a1b83d258bc0ef96ba715f8dd02749c79cd297d0dd976b4e3a4d2b46 +F src/server.h a8477ae61546a9a4c8e06dd31936b06abf18bd086ae6a183ed57f721a3a194b4 F src/shell.c a2b148e4ca8eb81b96e1050207c83d4a16ea6bf9182374faee4dd2a43628c291 F src/sqlite.h.in 7c783bb985825a13dd4c8294aade14bc4714275e142e2a5b21fc2a618cd669fb F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 58fd0676d3111d02e62e5a35992a7d3da5d3f88753acc174f2d37b774fbbdd28 -F src/sqliteInt.h e6e015985dbab8b60617b039e2eb7ce79fa06494b088acabee4fd3c1c123abc8 +F src/sqliteInt.h 908c37c3afa8ef795159541cebe909ff52612a9bb75301841db1afd1a242e746 F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b F src/status.c a9e66593dfb28a9e746cba7153f84d49c1ddc4b1 F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34 F src/tclsqlite.c 2c29b0b76e91edfd1b43bf135c32c8674710089197327682b6b7e6af88062c3d F src/test1.c 1c0726cdf7389ed053a9b9aa0dc3c63f3b9bbc607a25decae6549682008510b3 -F src/test2.c 3efb99ab7f1fc8d154933e02ae1378bac9637da5 +F src/test2.c 824e16d2ff3b57dc3680a5635d049cc889492f95910368ec1ffe2ad44ca45a7f F src/test3.c b8434949dfb8aff8dfa082c8b592109e77844c2135ed3c492113839b6956255b F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d @@ -517,7 +517,7 @@ F src/update.c c443935c652af9365e033f756550b5032d02e1b06eb2cb890ed7511ae0c051dc F src/utf.c 699001c79f28e48e9bcdf8a463da029ea660540c F src/util.c fc081ec6f63448dcd80d3dfad35baecfa104823254a815b081a4d9fe76e1db23 F src/vacuum.c 874c0f2f15ab2908748297d587d22d485ea96d55aaec91d4775dddb2e24d2ecf -F src/vdbe.c adc8a378710ec2376101483cc8a5f499539ee9bbebfb2a784f3370704d5d44ad +F src/vdbe.c e4493b09cc603a1b031c982785e94bbe637799c9e7fc974d37036c04ad2d79e0 F src/vdbe.h dde459b1e8a02b8445ecfd5959f38cd5ebb6b0ad392d491d8b159ac8193d231a F src/vdbeInt.h 8dd7548d86a98b77b6ed910d8014d0e9e0666356b56f3d16ab7063aad4f102f5 F src/vdbeapi.c 899d8f021c89ab348708b3a9b00b855f5ecc3c0f949a75359a61a3c621021281 @@ -528,7 +528,7 @@ F src/vdbesort.c f512c68d0bf7e0105316a5594c4329358c8ee9cae3b25138df041d97516c037 F src/vdbetrace.c 41963d5376f0349842b5fc4aaaaacd7d9cdc0834 F src/vtab.c 35b9bdc2b41de32a417141d12097bcc4e29a77ed7cdb8f836d1d2305d946b61b F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 -F src/wal.c 8f71654244baf38b95a277e79677d5444737c326182ba81476bc101c001f2e07 +F src/wal.c 1b208938ba3c33cc419f6240d914f68a147e9abb336c578d4c153dd1579d40f8 F src/wal.h 739d92494eb18b6d8f3e353e66c10eb8f94534bafd336ece9f3f60235317ea08 F src/walker.c a7ca64ce08a83a20d32186fbe06bca9234e348cfcf07959ee322fdc3e8a6173a F src/where.c e7cc80943459ade4ccaeb5c7c10f8a0db8b797e0b0f2509de7efc557b76bf7b6 @@ -1083,7 +1083,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 676872085bdaa9a4c789bffd2f1ebbebc22262773bf695e05d19d0085de308f4 +F test/permutations.test 9e7fb13778ca3b7aa357df382fbb0933a90980223f25b3fc9db964e33ff2aaa2 F test/pragma.test 1e94755164a3a3264cd39836de4bebcb7809e5f8 F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 14c12bc5352b1e100e0b6b44f371053a81ccf8ed @@ -1161,6 +1161,7 @@ F test/selectG.test e8600e379589e85e9fefd2fe4d44a4cdd63f6982 F test/server1.test 46803bd3fe8b99b30dbc5ff38ffc756f5c13a118 F test/server2.test 277b4b1f142232e1f992ea17b587474c32e41e2f321c01acdd49ef5ffee99ee6 F test/server3.test 3667fea050a248508d46b5eccb8a5561055f802016dc0d701e6b6f035ecc55ae +F test/server4.test 039d93f1d46011261edc16cb076673ca7acb377d81e5314df9267477d6dd93e7 F test/servercrash.test 816c132b26af008067cab2913783f67006d4003e3988f3f3ee1075742f6e0a6c F test/serverwal.test 2d066ba70db35e28f0115a57385cf606513c56d6e2861119c2defb7471e4e2d9 F test/session.test 78fa2365e93d3663a6e933f86e7afc395adf18be @@ -1567,7 +1568,7 @@ F tool/logest.c 11346aa019e2e77a00902aa7d0cabd27bd2e8cca F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439 F tool/mkautoconfamal.sh e855df211ecbcc7131dee817110ff386cfb112f7 F tool/mkctimec.tcl dd183b73ae1c28249669741c250525f0407e579a70482371668fd5f130d9feb3 -F tool/mkkeywordhash.c 2e852ac0dfdc5af18886dc1ce7e9676d11714ae3df0a282dc7d90b3a0fe2033c +F tool/mkkeywordhash.c 969c50301da61d73c4c6d0661c1d1abc6c25ba35f1212832d0b12965f90505ab F tool/mkmsvcmin.tcl cbd93f1cfa3a0a9ae56fc958510aa3fc3ac65e29cb111716199e3d0e66eefaa4 F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c F tool/mkopcodeh.tcl a01d2c1d8a6205b03fc635adf3735b4c523befd3 @@ -1635,7 +1636,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 a3a9a2e1899cc963315590ef4666972e9d92986843706a551962ed16661a19b2 281ad505d7a112c5524ea9fb195dfc46f819a58f79f42b37b12fbfe46a089ff2 -R 176ad25effe94068fc26940426749751 +P 216c757f9289af818482253f77b0b2c9b09e8a478caf67d70a4a6da2fabbc642 +R aec492ec515bdebef9944fc5b7624f8f U dan -Z fbcc844a03f0648283866c1e95fd38a8 +Z 17a24e9dfd744bc2511bd64d17405f97 diff --git a/manifest.uuid b/manifest.uuid index 000c4a780f..d3037903bd 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -216c757f9289af818482253f77b0b2c9b09e8a478caf67d70a4a6da2fabbc642 \ No newline at end of file +5a043aa8dd0751e644c495a59deea5fe05da905f49c664d978fe477f9240bc37 \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index cb6e866348..6410f228df 100644 --- a/src/btree.c +++ b/src/btree.c @@ -2910,7 +2910,7 @@ static int lockBtree(BtShared *pBt){ assert( sqlite3_mutex_held(pBt->mutex) ); assert( pBt->pPage1==0 ); - rc = sqlite3PagerSharedLock(pBt->pPager); + rc = sqlite3PagerSharedLock(pBt->pPager, pBt->db->readonlyTrans); if( rc!=SQLITE_OK ) return rc; rc = btreeGetPage(pBt, 1, &pPage1, 0); if( rc!=SQLITE_OK ) return rc; diff --git a/src/build.c b/src/build.c index cc05bdfb90..ac0efb9062 100644 --- a/src/build.c +++ b/src/build.c @@ -3956,13 +3956,13 @@ void sqlite3BeginTransaction(Parse *pParse, int type){ } v = sqlite3GetVdbe(pParse); if( !v ) return; - if( type!=TK_DEFERRED ){ + if( type!=TK_DEFERRED && type!=TK_READONLY ){ for(i=0; inDb; i++){ sqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1); sqlite3VdbeUsesBtree(v, i); } } - sqlite3VdbeAddOp0(v, OP_AutoCommit); + sqlite3VdbeAddOp3(v, OP_AutoCommit, 0, 0, type); } /* diff --git a/src/pager.c b/src/pager.c index 770e3c0105..3f36dd78fa 100644 --- a/src/pager.c +++ b/src/pager.c @@ -3028,11 +3028,28 @@ static int readDbPage(PgHdr *pPg, u32 iFrame){ }else #endif { - i64 iOffset = (pgno-1)*(i64)pPager->pageSize; - rc = sqlite3OsRead(pPager->fd, pPg->pData, pgsz, iOffset); - if( rc==SQLITE_IOERR_SHORT_READ ){ - rc = SQLITE_OK; +#ifdef SQLITE_SERVER_EDITION + u8 *pData = 0; + if( pagerIsServer(pPager) ){ + sqlite3ServerReadPage(pPager->pServer, pgno, &pData); + if( pData ){ + memcpy(pPg->pData, pData, pgsz); + } } + if( pData==0 ){ +#endif + i64 iOffset = (pgno-1)*(i64)pPager->pageSize; + rc = sqlite3OsRead(pPager->fd, pPg->pData, pgsz, iOffset); + if( rc==SQLITE_IOERR_SHORT_READ ){ + rc = SQLITE_OK; + } +#ifdef SQLITE_SERVER_EDITION + if( pagerIsServer(pPager) ){ + sqlite3ServerEndReadPage(pPager->pServer, pgno); + } + } +#endif + } if( pgno==1 ){ @@ -5219,7 +5236,7 @@ void sqlite3PagerServerJournal( ** occurs while locking the database, checking for a hot-journal file or ** rolling back a journal file, the IO error code is returned. */ -int sqlite3PagerSharedLock(Pager *pPager){ +int sqlite3PagerSharedLock(Pager *pPager, int bReadonly){ int rc = SQLITE_OK; /* Return code */ /* This routine is only called from b-tree and only when there are no @@ -5418,8 +5435,8 @@ int sqlite3PagerSharedLock(Pager *pPager){ assert( rc==SQLITE_OK ); assert( sqlite3PagerRefcount(pPager)==0 ); pager_reset(pPager); - rc = sqlite3ServerBegin(pPager->pServer); - if( rc==SQLITE_OK){ + rc = sqlite3ServerBegin(pPager->pServer, bReadonly); + if( rc==SQLITE_OK ){ rc = sqlite3ServerLock(pPager->pServer, 1, 0, 0); } } @@ -5969,6 +5986,7 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){ p->pgno = pPg->pgno; p->pNext = pPager->pServerPage; pPager->pServerPage = p; + memcpy(p->aData, pPg->pData, pPager->pageSize); } #endif @@ -7384,7 +7402,7 @@ int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ int state = pPager->eState; assert( state==PAGER_OPEN || state==PAGER_READER ); if( state==PAGER_OPEN ){ - rc = sqlite3PagerSharedLock(pPager); + rc = sqlite3PagerSharedLock(pPager, 0); } if( pPager->eState==PAGER_READER ){ assert( rc==SQLITE_OK ); diff --git a/src/pager.h b/src/pager.h index ff0038459b..bc9405837b 100644 --- a/src/pager.h +++ b/src/pager.h @@ -170,7 +170,7 @@ int sqlite3PagerCommitPhaseTwo(Pager*); int sqlite3PagerRollback(Pager*); int sqlite3PagerOpenSavepoint(Pager *pPager, int n); int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); -int sqlite3PagerSharedLock(Pager *pPager); +int sqlite3PagerSharedLock(Pager *pPager, int bReadonly); #ifndef SQLITE_OMIT_WAL int sqlite3PagerCheckpoint(Pager *pPager, sqlite3*, int, int*, int*); diff --git a/src/parse.y b/src/parse.y index dc8cff84be..c415bbc56a 100644 --- a/src/parse.y +++ b/src/parse.y @@ -137,6 +137,7 @@ trans_opt ::= TRANSACTION. trans_opt ::= TRANSACTION nm. %type transtype {int} transtype(A) ::= . {A = TK_DEFERRED;} +transtype(A) ::= READONLY(X). {A = @X; /*A-overwrites-X*/} transtype(A) ::= DEFERRED(X). {A = @X; /*A-overwrites-X*/} transtype(A) ::= IMMEDIATE(X). {A = @X; /*A-overwrites-X*/} transtype(A) ::= EXCLUSIVE(X). {A = @X; /*A-overwrites-X*/} diff --git a/src/server.c b/src/server.c index 5e086207b8..bd27ccb15b 100644 --- a/src/server.c +++ b/src/server.c @@ -118,14 +118,22 @@ struct ServerDb { struct Server { ServerDb *pDb; /* Database object */ Pager *pPager; /* Associated pager object */ + int eTrans; /* One of the SERVER_TRANS_xxx values */ int iTransId; /* Current transaction id (or -1) */ - int iCommitId; /* Current comit id (or 0) */ + int iCommitId; /* Current commit id (or 0) */ int nAlloc; /* Allocated size of aLock[] array */ int nLock; /* Number of entries in aLock[] */ u32 *aLock; /* Mapped lock file */ Server *pNext; /* Next in pCommit or pReader list */ }; +/* +** Possible values for Server.eTrans. +*/ +#define SERVER_TRANS_NONE 0 +#define SERVER_TRANS_READONLY 1 +#define SERVER_TRANS_READWRITE 2 + #define SERVER_WRITE_LOCK 3 #define SERVER_READ_LOCK 2 #define SERVER_NO_LOCK 1 @@ -327,31 +335,47 @@ int sqlite3ServerConnect( /* ** Begin a transaction. */ -int sqlite3ServerBegin(Server *p){ +int sqlite3ServerBegin(Server *p, int bReadonly){ int rc = SQLITE_OK; - if( p->iTransId<0 ){ + if( p->eTrans==SERVER_TRANS_NONE ){ int id; ServerDb *pDb = p->pDb; u32 t; + assert( p->iTransId<0 ); + assert( p->pNext==0 ); sqlite3_mutex_enter(pDb->mutex); - /* Find a transaction id to use */ - rc = SQLITE_BUSY; - t = pDb->transmask; - for(id=0; idiCommitId = pDb->iNextCommit; + for(pIter=pDb->pCommit; pIter; pIter=pIter->pNext){ + if( pIter->iCommitIdiCommitId ){ + p->iCommitId = pIter->iCommitId; + } } + p->pNext = pDb->pReader; + pDb->pReader = p; + p->eTrans = SERVER_TRANS_READONLY; + }else{ + /* Find a transaction id to use */ + rc = SQLITE_BUSY; + t = pDb->transmask; + for(id=0; idtransmask = t; + p->eTrans = SERVER_TRANS_READWRITE; } - pDb->transmask = t; sqlite3_mutex_leave(pDb->mutex); - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && bReadonly==0 ){ ServerJournal *pJrnl = &pDb->aJrnl[id]; sqlite3PagerServerJournal(p->pPager, pJrnl->jfd, pJrnl->zJournal); p->iTransId = id; @@ -382,70 +406,81 @@ static void serverReleaseLocks(Server *p){ */ int sqlite3ServerEnd(Server *p){ int rc = SQLITE_OK; - Server **pp; - ServerDb *pDb = p->pDb; - ServerPage *pFree = 0; - ServerPage *pPg = 0; - sqlite3_mutex_enter(pDb->mutex); - - serverReleaseLocks(p); + if( p->eTrans!=SERVER_TRANS_NONE ){ + Server **pp; + ServerDb *pDb = p->pDb; + ServerPage *pFree = 0; + ServerPage *pPg = 0; - /* Clear the bit in the transaction mask. */ - pDb->transmask &= ~((u32)1 << p->iTransId); + sqlite3_mutex_enter(pDb->mutex); - /* If this connection is in the committers list, remove it. */ - for(pp=&pDb->pCommit; *pp; pp = &((*pp)->pNext)){ - if( *pp==p ){ + if( p->eTrans==SERVER_TRANS_READONLY ){ + /* Remove the connection from the readers list */ + for(pp=&pDb->pReader; *pp!=p; pp = &((*pp)->pNext)); *pp = p->pNext; - break; - } - } + }else{ + serverReleaseLocks(p); - /* See if it is possible to free any ServerPage records. If so, remove - ** them from the linked list and hash table, but do not call sqlite3_free() - ** on them until the mutex has been released. */ - if( pDb->pPgFirst ){ - Server *pIter; - int iOldest = 0x7FFFFFFF; - for(pIter=pDb->pReader; pIter; pIter=pIter->pNext){ - iOldest = MIN(iOldest, pIter->iCommitId); - } - for(pIter=pDb->pCommit; pIter; pIter=pIter->pNext){ - iOldest = MIN(iOldest, pIter->iCommitId); + /* Clear the bit in the transaction mask. */ + pDb->transmask &= ~((u32)1 << p->iTransId); + + /* If this connection is in the committers list, remove it. */ + for(pp=&pDb->pCommit; *pp; pp = &((*pp)->pNext)){ + if( *pp==p ){ + *pp = p->pNext; + break; + } + } } - pFree = pDb->pPgFirst; - for(pPg=pDb->pPgFirst; pPg && pPg->iCommitIdpNext){ - if( pPg->pHashPrev ){ - pPg->pHashPrev->pHashNext = pPg->pHashNext; - }else{ - int iHash = pPg->pgno % HMA_HASH_SIZE; - assert( pDb->apPg[iHash]==pPg ); - pDb->apPg[iHash] = pPg->pHashNext; + /* See if it is possible to free any ServerPage records. If so, remove + ** them from the linked list and hash table, but do not call sqlite3_free() + ** on them until the mutex has been released. */ + if( pDb->pPgFirst ){ + Server *pIter; + int iOldest = 0x7FFFFFFF; + for(pIter=pDb->pReader; pIter; pIter=pIter->pNext){ + iOldest = MIN(iOldest, pIter->iCommitId); } - if( pPg->pHashNext ){ - pPg->pHashNext->pHashPrev = pPg->pHashPrev; + for(pIter=pDb->pCommit; pIter; pIter=pIter->pNext){ + iOldest = MIN(iOldest, pIter->iCommitId); + } + + pFree = pDb->pPgFirst; + for(pPg=pDb->pPgFirst; pPg && pPg->iCommitIdpNext){ + if( pPg->pHashPrev ){ + pPg->pHashPrev->pHashNext = pPg->pHashNext; + }else{ + int iHash = pPg->pgno % HMA_HASH_SIZE; + assert( pDb->apPg[iHash]==pPg ); + pDb->apPg[iHash] = pPg->pHashNext; + } + if( pPg->pHashNext ){ + pPg->pHashNext->pHashPrev = pPg->pHashPrev; + } + } + if( pPg==0 ){ + pDb->pPgFirst = pDb->pPgLast = 0; + }else{ + pDb->pPgFirst = pPg; } } - if( pPg==0 ){ - pDb->pPgFirst = pDb->pPgLast = 0; - }else{ - pDb->pPgFirst = pPg; - } - } - sqlite3_mutex_leave(pDb->mutex); + sqlite3_mutex_leave(pDb->mutex); - /* Call sqlite3_free() on any pages that were unlinked from the hash - ** table above. */ - while( pFree && pFree!=pPg ){ - ServerPage *pNext = pFree->pNext; - sqlite3_free(pFree); - pFree = pNext; - } + /* Call sqlite3_free() on any pages that were unlinked from the hash + ** table above. */ + while( pFree && pFree!=pPg ){ + ServerPage *pNext = pFree->pNext; + sqlite3_free(pFree); + pFree = pNext; + } - p->iTransId = -1; - p->iCommitId = 0; + p->pNext = 0; + p->eTrans = SERVER_TRANS_NONE; + p->iTransId = -1; + p->iCommitId = 0; + } return rc; } @@ -453,7 +488,6 @@ int sqlite3ServerPreCommit(Server *p, ServerPage *pPg){ ServerDb *pDb = p->pDb; int rc = SQLITE_OK; ServerPage *pIter; - ServerPage *pNext; if( pPg==0 ) return SQLITE_OK; @@ -461,6 +495,9 @@ int sqlite3ServerPreCommit(Server *p, ServerPage *pPg){ /* Assign a commit id to this transaction */ assert( p->iCommitId==0 ); + assert( p->eTrans==SERVER_TRANS_READWRITE ); + assert( p->iTransId>=0 ); + p->iCommitId = pDb->iNextCommit++; /* Iterate through all pages. For each: @@ -524,55 +561,62 @@ int sqlite3ServerReleaseWriteLocks(Server *p){ */ int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock){ int rc = SQLITE_OK; - ServerDb *pDb = p->pDb; - int iWriter; - int bSkip = 0; - u32 *pSlot; - assert( p->iTransId>=0 ); - assert( p->nLock<=p->nAlloc ); - if( p->nLock==p->nAlloc ){ - int nNew = p->nLock ? p->nLock*2 : 256; - u32 *aNew = sqlite3_realloc(p->aLock, nNew*sizeof(u32)); - if( aNew==0 ) return SQLITE_NOMEM_BKPT; - memset(&aNew[p->nLock], 0, sizeof(u32) * (nNew - p->nLock)); - p->nAlloc = nNew; - p->aLock = aNew; - } + assert( p->eTrans==SERVER_TRANS_READWRITE + || p->eTrans==SERVER_TRANS_READONLY + ); + if( p->eTrans==SERVER_TRANS_READWRITE ){ + ServerDb *pDb = p->pDb; + int iWriter; + int bSkip = 0; + u32 *pSlot; + + assert( p->iTransId>=0 ); + assert( p->nLock<=p->nAlloc ); + if( p->nLock==p->nAlloc ){ + int nNew = p->nLock ? p->nLock*2 : 256; + u32 *aNew = sqlite3_realloc(p->aLock, nNew*sizeof(u32)); + if( aNew==0 ) return SQLITE_NOMEM_BKPT; + memset(&aNew[p->nLock], 0, sizeof(u32) * (nNew - p->nLock)); + p->nAlloc = nNew; + p->aLock = aNew; + } - sqlite3_mutex_enter(pDb->mutex); + sqlite3_mutex_enter(pDb->mutex); - pSlot = &pDb->aSlot[pgno % HMA_PAGELOCK_SLOTS]; - assert( slotGetWriter(*pSlot)<0 - || slotReaderMask(*pSlot)==0 - || slotReaderMask(*pSlot)==(1 << slotGetWriter(*pSlot)) - ); + pSlot = &pDb->aSlot[pgno % HMA_PAGELOCK_SLOTS]; + assert( slotGetWriter(*pSlot)<0 + || slotReaderMask(*pSlot)==0 + || slotReaderMask(*pSlot)==(1 << slotGetWriter(*pSlot)) + ); - iWriter = slotGetWriter(*pSlot); - if( iWriter==p->iTransId || (bWrite==0 && (*pSlot & (1<iTransId))) ){ - bSkip = 1; - }else if( iWriter>=0 ){ - rc = SQLITE_BUSY_DEADLOCK; - }else if( bWrite ){ - if( (slotReaderMask(*pSlot) & ~(1 << p->iTransId))==0 ){ - *pSlot += ((p->iTransId + 1) << HMA_MAX_TRANSACTIONID); - }else{ + iWriter = slotGetWriter(*pSlot); + if( iWriter==p->iTransId || (bWrite==0 && (*pSlot & (1<iTransId))) ){ + bSkip = 1; + }else if( iWriter>=0 ){ rc = SQLITE_BUSY_DEADLOCK; + }else if( bWrite ){ + if( (slotReaderMask(*pSlot) & ~(1 << p->iTransId))==0 ){ + *pSlot += ((p->iTransId + 1) << HMA_MAX_TRANSACTIONID); + }else{ + rc = SQLITE_BUSY_DEADLOCK; + } + }else{ + *pSlot |= (1 << p->iTransId); } - }else{ - *pSlot |= (1 << p->iTransId); - } - assert( slotGetWriter(*pSlot)<0 - || slotReaderMask(*pSlot)==0 - || slotReaderMask(*pSlot)==(1 << slotGetWriter(*pSlot)) - ); + assert( slotGetWriter(*pSlot)<0 + || slotReaderMask(*pSlot)==0 + || slotReaderMask(*pSlot)==(1 << slotGetWriter(*pSlot)) + ); - sqlite3_mutex_leave(pDb->mutex); + sqlite3_mutex_leave(pDb->mutex); - if( bSkip==0 ){ - p->aLock[p->nLock++] = pgno; + if( bSkip==0 ){ + p->aLock[p->nLock++] = pgno; + } } + return rc; } @@ -581,4 +625,50 @@ int sqlite3ServerHasLock(Server *p, Pgno pgno, int bWrite){ return 0; } +static void serverIncrSlowReader(u32 *pSlot, int n){ + assert( n==1 || n==-1 ); + *pSlot += (n * (1 << HMA_SLOT_RLWL_BITS)); +} + +void sqlite3ServerReadPage(Server *p, Pgno pgno, u8 **ppData){ + if( p->eTrans==SERVER_TRANS_READONLY ){ + ServerDb *pDb = p->pDb; + ServerPage *pIter; + ServerPage *pBest = 0; + int iHash = pgno % HMA_HASH_SIZE; + + sqlite3_mutex_enter(pDb->mutex); + + /* Search the hash table for the oldest version of page pgno with + ** a commit-id greater than or equal to Server.iCommitId. */ + for(pIter=pDb->apPg[iHash]; pIter; pIter=pIter->pHashNext){ + if( pIter->iCommitId>=p->iCommitId + && (pBest==0 || pIter->iCommitIdiCommitId) + ){ + pBest = pIter; + } + } + + if( pBest ){ + *ppData = pBest->aData; + }else{ + u32 *pSlot = &pDb->aSlot[pgno % HMA_PAGELOCK_SLOTS]; + serverIncrSlowReader(pSlot, 1); + } + + sqlite3_mutex_leave(pDb->mutex); + } +} + +void sqlite3ServerEndReadPage(Server *p, Pgno pgno){ + if( p->eTrans==SERVER_TRANS_READONLY ){ + ServerDb *pDb = p->pDb; + u32 *pSlot = &pDb->aSlot[pgno % HMA_PAGELOCK_SLOTS]; + sqlite3_mutex_enter(pDb->mutex); + serverIncrSlowReader(pSlot, -1); + assert( slotGetSlowReaders(*pSlot)>=0 ); + sqlite3_mutex_leave(pDb->mutex); + } +} + #endif /* ifdef SQLITE_SERVER_EDITION */ diff --git a/src/server.h b/src/server.h index ad81001fa2..8e9ea33fe4 100644 --- a/src/server.h +++ b/src/server.h @@ -34,7 +34,7 @@ struct ServerPage { int sqlite3ServerConnect(Pager *pPager, Server **ppOut); void sqlite3ServerDisconnect(Server *p, sqlite3_file *dbfd); -int sqlite3ServerBegin(Server *p); +int sqlite3ServerBegin(Server *p, int bReadonly); int sqlite3ServerPreCommit(Server*, ServerPage*); int sqlite3ServerEnd(Server *p); @@ -44,6 +44,9 @@ int sqlite3ServerLock(Server *p, Pgno pgno, int bWrite, int bBlock); int sqlite3ServerHasLock(Server *p, Pgno pgno, int bWrite); +void sqlite3ServerReadPage(Server*, Pgno, u8**); +void sqlite3ServerEndReadPage(Server*, Pgno); + #endif /* SQLITE_SERVER_H */ #endif /* SQLITE_SERVER_EDITION */ diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 2f00de6b5b..4a8b401caf 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1327,6 +1327,7 @@ struct sqlite3 { u16 dbOptFlags; /* Flags to enable/disable optimizations */ u8 enc; /* Text encoding */ u8 autoCommit; /* The auto-commit flag. */ + u8 readonlyTrans; /* Transaction opened with BEGIN READONLY */ u8 temp_store; /* 1: file 2: memory 0: default */ u8 mallocFailed; /* True if we have seen a malloc failure */ u8 bBenignMalloc; /* Do not require OOMs if true */ diff --git a/src/test2.c b/src/test2.c index 850e1e1a04..34c980eedd 100644 --- a/src/test2.c +++ b/src/test2.c @@ -324,7 +324,7 @@ static int SQLITE_TCLAPI page_get( } pPager = sqlite3TestTextToPtr(argv[1]); if( Tcl_GetInt(interp, argv[2], &pgno) ) return TCL_ERROR; - rc = sqlite3PagerSharedLock(pPager); + rc = sqlite3PagerSharedLock(pPager, 0); if( rc==SQLITE_OK ){ rc = sqlite3PagerGet(pPager, pgno, &pPage, 0); } diff --git a/src/vdbe.c b/src/vdbe.c index e76b09f182..6242d6545d 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -3120,7 +3120,7 @@ case OP_Savepoint: { break; } -/* Opcode: AutoCommit P1 P2 * * * +/* Opcode: AutoCommit P1 P2 P3 * * ** ** Set the database auto-commit flag to P1 (1 or 0). If P2 is true, roll ** back any currently active btree transactions. If there are any active @@ -3137,6 +3137,7 @@ case OP_AutoCommit: { iRollback = pOp->p2; assert( desiredAutoCommit==1 || desiredAutoCommit==0 ); assert( desiredAutoCommit==1 || iRollback==0 ); + assert( desiredAutoCommit==0 || pOp->p3==0 ); assert( db->nVdbeActive>0 ); /* At least this one VM is active */ assert( p->bIsReader ); @@ -3168,6 +3169,7 @@ case OP_AutoCommit: { sqlite3CloseSavepoints(db); if( p->rc==SQLITE_OK ){ rc = SQLITE_DONE; + db->readonlyTrans = (pOp->p3==TK_READONLY); }else{ rc = SQLITE_ERROR; } diff --git a/src/wal.c b/src/wal.c index 954a32b557..7f000676c6 100644 --- a/src/wal.c +++ b/src/wal.c @@ -3398,7 +3398,7 @@ int sqlite3WalCheckpoint( */ if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ if( walIsServer(pWal) ){ - rc = sqlite3ServerBegin(pWal->pServer); + rc = sqlite3ServerBegin(pWal->pServer, 0); if( rc!=SQLITE_OK ) goto ckpt_out; if( eMode>=SQLITE_CHECKPOINT_RESTART ){ /* Exclusive lock on page 1. This is exclusive access to the db. */ diff --git a/test/permutations.test b/test/permutations.test index 53c08cd9c5..2713a03a20 100644 --- a/test/permutations.test +++ b/test/permutations.test @@ -277,7 +277,7 @@ test_suite "server" -prefix "" -description { All server-edition tests. } -files [ test_set \ - select1.test server2.test server3.test + select1.test server2.test server3.test server4.test ] test_suite "fts5-light" -prefix "" -description { diff --git a/test/server4.test b/test/server4.test new file mode 100644 index 0000000000..b9d7eb728f --- /dev/null +++ b/test/server4.test @@ -0,0 +1,62 @@ +# 2017 July 09 +# +# 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. +# Specifically, that "BEGIN READONLY" starts a read-only MVCC +# transaction. +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/lock_common.tcl +set testprefix server3 + +sqlite3 db2 test.db + +do_execsql_test 1.0 { + CREATE TABLE t1(x); + INSERT INTO t1 VALUES(1); + CREATE TABLE t2(x); + INSERT INTO t2 VALUES(1); + BEGIN; + INSERT INTO t1 VALUES(2); + INSERT INTO t2 VALUES(2); +} + +do_execsql_test -db db2 1.1 { + BEGIN READONLY; + SELECT * FROM t1; +} {1} + +do_execsql_test 1.2 { + COMMIT; + INSERT INTO t1 VALUES(3); + SELECT * FROM t1; +} {1 2 3} + +do_execsql_test 1.2a { + INSERT INTO t2 VALUES(3); +} {} + +do_execsql_test -db db2 1.3 { + SELECT * FROM t2; +} {1} + +do_execsql_test -db db2 1.4 { + ROLLBACK; + SELECT * FROM t1; +} {1 2 3} + + +finish_test + diff --git a/tool/mkkeywordhash.c b/tool/mkkeywordhash.c index 42112718da..7b57363a54 100644 --- a/tool/mkkeywordhash.c +++ b/tool/mkkeywordhash.c @@ -239,6 +239,7 @@ static Keyword aKeywordTable[] = { { "PRIMARY", "TK_PRIMARY", ALWAYS }, { "QUERY", "TK_QUERY", EXPLAIN }, { "RAISE", "TK_RAISE", TRIGGER }, + { "READONLY", "TK_READONLY", ALWAYS }, { "RECURSIVE", "TK_RECURSIVE", CTE }, { "REFERENCES", "TK_REFERENCES", FKEY }, { "REGEXP", "TK_LIKE_KW", ALWAYS },