]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Support read-only MVCC transactions in server-mode. Started using "BEGIN
authordan <dan@noemail.net>
Sat, 8 Jul 2017 20:46:17 +0000 (20:46 +0000)
committerdan <dan@noemail.net>
Sat, 8 Jul 2017 20:46:17 +0000 (20:46 +0000)
READONLY".

FossilOrigin-Name: 5a043aa8dd0751e644c495a59deea5fe05da905f49c664d978fe477f9240bc37

16 files changed:
manifest
manifest.uuid
src/btree.c
src/build.c
src/pager.c
src/pager.h
src/parse.y
src/server.c
src/server.h
src/sqliteInt.h
src/test2.c
src/vdbe.c
src/wal.c
test/permutations.test
test/server4.test [new file with mode: 0644]
tool/mkkeywordhash.c

index c4058dab5cd3b0eb7349824931c6bf7ea4cff193..51a8e8f02d4207ffc8f08dac7d0fc443e11f57d6 100644 (file)
--- 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
index 000c4a780fb18da7f70c55f3b746b7b3d5f0192d..d3037903bd94ce0548db13fd1d0fcaafe7c3144d 100644 (file)
@@ -1 +1 @@
-216c757f9289af818482253f77b0b2c9b09e8a478caf67d70a4a6da2fabbc642
\ No newline at end of file
+5a043aa8dd0751e644c495a59deea5fe05da905f49c664d978fe477f9240bc37
\ No newline at end of file
index cb6e8663485bf6dd13b8a47530a14dab58d29940..6410f228df006480be61babd6157d10d627c6d9e 100644 (file)
@@ -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;
index cc05bdfb90fe56402486d8585d2449d9945851f3..ac0efb90623c91cfdc302ed1709caefaebc1fe02 100644 (file)
@@ -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; i<db->nDb; i++){
       sqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1);
       sqlite3VdbeUsesBtree(v, i);
     }
   }
-  sqlite3VdbeAddOp0(v, OP_AutoCommit);
+  sqlite3VdbeAddOp3(v, OP_AutoCommit, 0, 0, type);
 }
 
 /*
index 770e3c0105abe181e0f1fa7c3046aa872c30efd0..3f36dd78fac723b69a05587fcb84c6eddfa11312 100644 (file)
@@ -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 );
index ff0038459bec0d245141cb7e0930402a819f930c..bc9405837bbe0bfbf6154acdf8da4ada2a8928fd 100644 (file)
@@ -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*);
index dc8cff84be5b39036eb0537fb97cf7bf4903186a..c415bbc56a42cadd2698092dd6a615474bd8572c 100644 (file)
@@ -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*/}
index 5e086207b835976caba9c5d781cd782e361d7707..bd27ccb15b1e58b2bc38c79d4d3f9a294cb0aef7 100644 (file)
@@ -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; id<HMA_MAX_TRANSACTIONID; id++){
-      if( (t & (1 << id))==0 ){
-        t = t | (1 << id);
-        rc = SQLITE_OK;
-        break;
+    if( bReadonly ){
+      Server *pIter;
+      p->iCommitId = pDb->iNextCommit;
+      for(pIter=pDb->pCommit; pIter; pIter=pIter->pNext){
+        if( pIter->iCommitId<p->iCommitId ){
+          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; id<HMA_MAX_TRANSACTIONID; id++){
+        if( (t & (1 << id))==0 ){
+          t = t | (1 << id);
+          rc = SQLITE_OK;
+          break;
+        }
+      }
+      pDb->transmask = 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->iCommitId<iOldest; pPg=pPg->pNext){
-      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->iCommitId<iOldest; pPg=pPg->pNext){
+        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<<p->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<<p->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->iCommitId<pBest->iCommitId) 
+      ){
+        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 */
index ad81001fa2f5ae7423f4a974041a7d0fd9cfecb1..8e9ea33fe49c7d75896f3a47995dbcb1d2ee300c 100644 (file)
@@ -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 */
 
index 2f00de6b5bb9bc8c0364133f29a3e78ed09c4525..4a8b401caf88e2ce13ee44214acac4e2f76b46ee 100644 (file)
@@ -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 */
index 850e1e1a0448ef639178deb881528886bece8cc5..34c980eedd3275bc513be22bd99704e434d6f1bd 100644 (file)
@@ -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);
   }
index e76b09f1828f963359a13ba34f53cbdb0c7eb406..6242d6545df00afd7ed14bcf3a4ab82e160e4f95 100644 (file)
@@ -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;
     }
index 954a32b5579a05a633d47dcc7d845045110e235c..7f000676c634c8167ddc96ca879a9d6de6b9b425 100644 (file)
--- 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. */
index 53c08cd9c5fa06474947fc0f0cb165380f68949b..2713a03a2082e066f77508e9a72c697da74e64ff 100644 (file)
@@ -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 (file)
index 0000000..b9d7eb7
--- /dev/null
@@ -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
+
index 42112718da22cfc934bc62af8ab8395e731d25b2..7b57363a543c5665e13df773219396f8423ebe49 100644 (file)
@@ -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                 },