]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
When committing an unlocked transaction, relocate newly allocated database pages...
authordan <dan@noemail.net>
Wed, 19 Aug 2015 20:27:05 +0000 (20:27 +0000)
committerdan <dan@noemail.net>
Wed, 19 Aug 2015 20:27:05 +0000 (20:27 +0000)
FossilOrigin-Name: 3bbc31d515ba9fc920c5cbc7059d3eb1ba9c7f8e

12 files changed:
manifest
manifest.uuid
src/btree.c
src/btree.h
src/btreeInt.h
src/pager.c
src/pager.h
src/vdbeaux.c
src/wal.c
src/wal.h
test/unlocked.test
test/unlocked2.test [new file with mode: 0644]

index 7ca1c00b94b0f13d3ed4c83f2d1f6853ea8fa787..92f5317a3df0b46b762c054552112db6cafc5370 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Handle\swrites\sto\sauto-vacuum\sdatabases\swithin\sUNLOCKED\stransactions\sin\sthe\ssame\sway\sas\sfor\snon-UNLOCKED\stransactions.
-D 2015-08-15T18:16:46.463
+C When\scommitting\san\sunlocked\stransaction,\srelocate\snewly\sallocated\sdatabase\spages\swithin\sthe\sfile\sto\savoid\sconflicting\swith\scommitted\stransactions.\sThere\sare\slots\sof\sthings\sstill\sto\sfix\sin\sthis\scode.
+D 2015-08-19T20:27:05.840
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 4de3ef40c8b3b75c0c55ff4242a43c8ce1ad90ee
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -270,9 +270,9 @@ F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240
 F src/backup.c 4d9134dc988a87838c06056c89c0e8c4700a0452
 F src/bitvec.c d1f21d7d91690747881f03940584f4cc548c9d3d
 F src/btmutex.c 45a968cc85afed9b5e6cf55bf1f42f8d18107f79
-F src/btree.c c1bbc83539de1e749aa36ce9c283f95b50543bcb
-F src/btree.h 40bd41ef0b71d6f7502725dc159fa0d6bebd8bb3
-F src/btreeInt.h 2ad754dd4528baa8d0946a593cc373b890bf859e
+F src/btree.c a00a7c4809d642bf6c63979e73face9fed53dc66
+F src/btree.h 00d4cdb747c4172a5566faf037116985dbbc377e
+F src/btreeInt.h 9f77bac260d81cc74ecd96cccf73a8c5af1688c4
 F src/build.c 28c15c43eefc0066ff64040526ff649c32fe5523
 F src/callback.c 7b44ce59674338ad48b0e84e7b72f935ea4f68b0
 F src/complete.c addcd8160b081131005d5bc2d34adf20c1c5c92f
@@ -315,8 +315,8 @@ F src/os_setup.h c9d4553b5aaa6f73391448b265b89bed0b890faa
 F src/os_unix.c 388c023582b17890f10c980b30ec1922b471753b
 F src/os_win.c 40b3af7a47eb1107d0d69e592bec345a3b7b798a
 F src/os_win.h eb7a47aa17b26b77eb97e4823f20a00b8bda12ca
-F src/pager.c 18aa67541bd2c7d7963f180669ea4e48cd5246f4
-F src/pager.h 41938212a5d80a7068883bb29ea6ac0d4ef3a6d8
+F src/pager.c fe82e368600278cfd35024c20b888e945236f675
+F src/pager.h 3fc23bca2e7d606ca4fbdd923713cdcdcec48297
 F src/parse.y 199145cd982587d70fa3db2f9b11f7ab118f0acd
 F src/pcache.c cde06aa50962595e412d497e22fd2e07878ba1f0
 F src/pcache.h 9968603796240cdf83da7e7bef76edf90619cea9
@@ -395,15 +395,15 @@ F src/vdbe.c fd6832408d9621ad347a65b01a05380fe766232f
 F src/vdbe.h 7a75045d879118b9d3af7e8b3c108f2f27c51473
 F src/vdbeInt.h 8b54e01ad0463590e7cffabce0bc36da9ee4f816
 F src/vdbeapi.c adabbd66eb2e3a10f3998485ee0be7e326d06ee4
-F src/vdbeaux.c 24a03ccbadc8d4cfecf25b6399db95851b10dd21
+F src/vdbeaux.c 27feae106f3f4db298e65d37a9da4962aaff724b
 F src/vdbeblob.c 4f2e8e075d238392df98c5e03a64342465b03f90
 F src/vdbemem.c ae38a0d35ae71cf604381a887c170466ba518090
 F src/vdbesort.c f5009e7a35e3065635d8918b9a31f498a499976b
 F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0
 F src/vtab.c 082b35a25a26e3d36f365ca8cd73c1922532f05e
 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb
-F src/wal.c 4497b466fee8ed56c31672bb35741dd0321ec999
-F src/wal.h cd7ea52439d56571d549b7d4eacdeed58d841385
+F src/wal.c 8bb1130dc1876e1c1c3fa082a5b0ec6437feb8e8
+F src/wal.h 5aaed8ca6cad1406088042ff8767951a6a3c7b56
 F src/walker.c c253b95b4ee44b21c406e2a1052636c31ea27804
 F src/where.c 909eba3b6db984eb2adfbca9de2c237ee7056adb
 F src/whereInt.h 5f87e3c4b0551747d119730dfebddd3c54f04047
@@ -1215,7 +1215,8 @@ F test/types3.test 99e009491a54f4dc02c06bdbc0c5eea56ae3e25a
 F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264
 F test/unique2.test 41e7f83c6827605991160a31380148a9fc5f1339
 F test/unixexcl.test cd6c765f75e50e8e2c2ba763149e5d340ea19825
-F test/unlocked.test 644a84d7f032ca029d490f0b0bc8fb9f86c7a1a5
+F test/unlocked.test d143a871bd874311d4e5c98ce458218ca6b579fd
+F test/unlocked2.test fc4c730a44c3650250467ecacca6ddb969f64405
 F test/unordered.test ca7adce0419e4ca0c50f039885e76ed2c531eda8
 F test/update.test 6c68446b8a0a33d522a7c72b320934596a2d7d32
 F test/uri.test 23662b7b61958b0f0e47082de7d06341ccf85d5b
@@ -1367,7 +1368,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 0b9718426e44df092850c5d095ce1b84a1e567cf
-R ac1d3857424ab06ed70d671704c3737d
+P de1ea450db33b140b11af5b801ea6a15875e774e
+R db05422c8fb37e882a7e6cc4bd7c8ffb
 U dan
-Z 12da9addf6141c212c3c7cdffe33d767
+Z cccfd407eb244cf1cd3b5cab72e65fda
index 6a4d41072df4632e9e143dfb5fb79a6be56ed545..7d70f3c41150e01d30943a9b2c2013c2c3235f66 100644 (file)
@@ -1 +1 @@
-de1ea450db33b140b11af5b801ea6a15875e774e
\ No newline at end of file
+3bbc31d515ba9fc920c5cbc7059d3eb1ba9c7f8e
\ No newline at end of file
index 7f9054f309ad3ea53e4311017e96ae9d45291af5..cc97fd86237a403e06afeb5b97d755b59c4baa5e 100644 (file)
@@ -439,6 +439,152 @@ static void downgradeAllSharedCacheTableLocks(Btree *p){
 
 #endif /* SQLITE_OMIT_SHARED_CACHE */
 
+/*
+** The following structure stores the in-memory pointer map used for newly
+** allocated pages in UNLOCKED transactions. Such pages are always allocated 
+** in a contiguous block (from the end of the file) starting with page
+** BtreePtrmap.iFirst.
+** 
+** The page number for the parent page iFirst is stored in aPtr[0]. For
+** (iFirst+1), aPtr[1]. A zero value indicates that the page has not
+** been allocated.
+**
+**
+*/
+
+typedef struct RollbackEntry RollbackEntry;
+typedef struct PtrmapEntry PtrmapEntry;
+struct PtrmapEntry {
+  Pgno parent;
+  u8 eType;
+};
+struct RollbackEntry {
+  Pgno pgno;
+  Pgno parent;
+  u8 eType;
+};
+
+struct BtreePtrmap {
+  Pgno iFirst;                    /* First new page number aPtr[0] */
+
+  int nPtrAlloc;                  /* Allocated size of aPtr[] array */
+  PtrmapEntry *aPtr;              /* Array of parent page numbers */
+
+  int nSvpt;                      /* Used size of aSvpt[] array */
+  int nSvptAlloc;                 /* Allocated size of aSvpt[] */
+  int *aSvpt;                     /* First aRollback[] entry for savepoint i */
+
+  int nRollback;                  /* Used size of aRollback[] array */
+  int nRollbackAlloc;             /* Allocated size of aRollback[] array */
+  RollbackEntry *aRollback;       /* Array of rollback entries */
+};
+
+static int btreePtrmapStore(
+  BtreePtrmap *pMap, 
+  Pgno pgno, 
+  u8 eType, 
+  Pgno parent
+){
+  if( pgno>=pMap->iFirst ){
+    int iEntry = pgno - pMap->iFirst;
+
+    /* Grow the aPtr[] array if required */
+    if( iEntry>=pMap->nPtrAlloc ){
+      int nNew = pMap->nPtrAlloc ? pMap->nPtrAlloc*2 : 16;
+      PtrmapEntry *aNew = (PtrmapEntry*)sqlite3_realloc(
+          pMap->aPtr, nNew*sizeof(PtrmapEntry)
+      );
+      if( aNew==0 ){
+        return SQLITE_NOMEM;
+      }else{
+        int nByte = (nNew-pMap->nPtrAlloc)*sizeof(PtrmapEntry);
+        memset(&aNew[pMap->nPtrAlloc], 0, nByte);
+        pMap->aPtr = aNew;
+        pMap->nPtrAlloc = nNew;
+      }
+    }
+
+    /* Add an entry to the rollback log if required */
+    if( pMap->nSvpt>0 && pMap->aPtr[iEntry].parent ){
+      if( pMap->nRollback>=pMap->nRollbackAlloc ){
+        int nNew = pMap->nRollback ? pMap->nRollback*2 : 16;
+        RollbackEntry *aNew = (RollbackEntry*)sqlite3_realloc(
+            pMap->aRollback, nNew*sizeof(RollbackEntry)
+        );
+        if( aNew==0 ){
+          return SQLITE_NOMEM;
+        }else{
+          pMap->aRollback = aNew;
+          pMap->nRollbackAlloc = nNew;
+        }
+      }
+
+      pMap->aRollback[pMap->nRollback].pgno = pgno;
+      pMap->aRollback[pMap->nRollback].parent = pMap->aPtr[iEntry].parent;
+      pMap->aRollback[pMap->nRollback].eType = pMap->aPtr[iEntry].eType;
+    }
+
+    /* Update the aPtr[] array */
+    pMap->aPtr[iEntry].parent = parent;
+    pMap->aPtr[iEntry].eType = eType;
+  }
+
+  return SQLITE_OK;
+}
+
+/*
+** Open savepoint iSavepoint, if it is not already open.
+*/
+static int btreePtrmapBegin(BtreePtrmap *pMap, int nSvpt){
+  if( nSvpt<pMap->nSvpt ){
+    int i;
+    if( nSvpt>=pMap->nSvptAlloc ){
+      int nNew = pMap->nSvptAlloc ? pMap->nSvptAlloc*2 : 16;
+      int *aNew = sqlite3_realloc(pMap->aSvpt, sizeof(int) * nNew);
+      if( aNew==0 ){
+        return SQLITE_NOMEM;
+      }else{
+        pMap->aSvpt = aNew;
+        pMap->nSvptAlloc = nNew;
+      }
+    }
+
+    for(i=pMap->nSvpt; i<nSvpt; i++){
+      pMap->aSvpt[i] = pMap->nRollback;
+    }
+    pMap->nSvpt = nSvpt;
+  }
+
+  return SQLITE_OK;
+}
+
+/*
+** Rollback (if op==SAVEPOINT_ROLLBACK) or release (if op==SAVEPOINT_RELEASE)
+** savepoint iSvpt.
+*/
+static void btreePtrmapEnd(BtreePtrmap *pMap, int op, int iSvpt){
+  assert( op==SAVEPOINT_ROLLBACK || op==SAVEPOINT_RELEASE );
+  assert( iSvpt>=0 || (iSvpt==-1 && op==SAVEPOINT_ROLLBACK) );
+  if( iSvpt<0 ){
+    pMap->nSvpt = 0;
+    pMap->nRollback = 0;
+    memset(pMap->aPtr, 0, sizeof(Pgno) * pMap->nPtrAlloc);
+  }else if( iSvpt<pMap->nSvpt ){
+    if( op==SAVEPOINT_ROLLBACK ){
+      int ii;
+      for(ii=pMap->nRollback-1; ii>=pMap->aSvpt[iSvpt]; ii--){
+        RollbackEntry *p = &pMap->aRollback[ii];
+        PtrmapEntry *pEntry = &pMap->aPtr[p->pgno - pMap->iFirst];
+        pEntry->parent = p->parent;
+        pEntry->eType = p->eType;
+      }
+    }
+    pMap->nSvpt = iSvpt + (op==SAVEPOINT_ROLLBACK);
+    pMap->nRollback = pMap->aSvpt[iSvpt];
+  }
+}
+
+
 static void releasePage(MemPage *pPage);  /* Forward reference */
 
 /*
@@ -878,42 +1024,44 @@ static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){
   int offset;       /* Offset in pointer map page */
   int rc;           /* Return code from subfunctions */
 
+  assert( sqlite3_mutex_held(pBt->mutex) );
   if( *pRC ) return;
 
-  assert( sqlite3_mutex_held(pBt->mutex) );
-  /* The master-journal page number must never be used as a pointer map page */
-  assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) );
+  if( pBt->pMap ){
+    *pRC = btreePtrmapStore(pBt->pMap, key, eType, parent);
+  }else{
+    /* The master-journal page number must never be used as a ptr map page */
+    assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) );
 
-  assert( pBt->autoVacuum );
-  if( key==0 ){
-    *pRC = SQLITE_CORRUPT_BKPT;
-    return;
-  }
-  iPtrmap = PTRMAP_PAGENO(pBt, key);
-  rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage);
-  if( rc!=SQLITE_OK ){
-    *pRC = rc;
-    return;
-  }
-  offset = PTRMAP_PTROFFSET(iPtrmap, key);
-  if( offset<0 ){
-    *pRC = SQLITE_CORRUPT_BKPT;
-    goto ptrmap_exit;
-  }
-  assert( offset <= (int)pBt->usableSize-5 );
-  pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage);
+    assert( pBt->autoVacuum );
+    if( key==0 ){
+      *pRC = SQLITE_CORRUPT_BKPT;
+      return;
+    }
+    iPtrmap = PTRMAP_PAGENO(pBt, key);
+    rc = sqlite3PagerGet(pBt->pPager, iPtrmap, &pDbPage);
+    if( rc!=SQLITE_OK ){
+      *pRC = rc;
+      return;
+    }
+    offset = PTRMAP_PTROFFSET(iPtrmap, key);
+    if( offset<0 ){
+      *pRC = SQLITE_CORRUPT_BKPT;
+    }else{
+      assert( offset <= (int)pBt->usableSize-5 );
+      pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage);
 
-  if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){
-    TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent));
-    *pRC= rc = sqlite3PagerWrite(pDbPage);
-    if( rc==SQLITE_OK ){
-      pPtrmap[offset] = eType;
-      put4byte(&pPtrmap[offset+1], parent);
+      if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){
+        TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent));
+        *pRC= rc = sqlite3PagerWrite(pDbPage);
+        if( rc==SQLITE_OK ){
+          pPtrmap[offset] = eType;
+          put4byte(&pPtrmap[offset+1], parent);
+        }
+      }
     }
+    sqlite3PagerUnref(pDbPage);
   }
-
-ptrmap_exit:
-  sqlite3PagerUnref(pDbPage);
 }
 
 /*
@@ -1924,6 +2072,50 @@ u32 sqlite3BtreeLastPage(Btree *p){
   return btreePagecount(p->pBt);
 }
 
+#ifdef SQLITE_ENABLE_UNLOCKED
+/*
+** This function is called before allocating or freeing a b-tree page. If
+** the current transaction is UNLOCKED, it allocates the BtreePtrmap 
+** structure and zeroes the nFree/iTrunk fields in the database header
+** on page 1.
+*/
+static int allocatePtrmap(BtShared *pBt){
+  int rc = SQLITE_OK;
+  if( pBt->pMap==0 && sqlite3PagerIsUnlocked(pBt->pPager) ){
+    /* If this is an unlocked transaction, set the header values
+    ** identifying the size of the free-list and the page number
+    ** of the first trunk page to zero. */
+    BtreePtrmap *pMap = sqlite3_malloc(sizeof(BtreePtrmap));
+    if( pMap==0 ){
+      rc = SQLITE_NOMEM;
+    }else{
+      memset(&pBt->pPage1->aData[32], 0, sizeof(u32)*2);
+      memset(pMap, 0, sizeof(BtreePtrmap));
+      pMap->iFirst = btreePagecount(pBt) + 1;
+      pBt->pMap = pMap;
+    }
+  }
+  return rc;
+}
+
+/*
+** Free a pointer-map allocated by allocatePtrmap.
+*/
+static void deletePtrmap(BtShared *pBt){
+  BtreePtrmap *pMap = pBt->pMap;
+  if( pMap ){
+    sqlite3_free(pMap->aRollback);
+    sqlite3_free(pMap->aPtr);
+    sqlite3_free(pMap->aSvpt);
+    sqlite3_free(pMap);
+    pBt->pMap = 0;
+  }
+}
+#else
+#define allocatePtrmap(x) SQLITE_OK
+#define deletePtrmap(x) 
+#endif
+
 /*
 ** Get a page from the pager and initialize it.
 **
@@ -3149,6 +3341,9 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
         if( rc==SQLITE_OK ){
           rc = newDatabase(pBt);
         }
+        if( rc==SQLITE_OK ){
+          rc = allocatePtrmap(pBt);
+        }
       }
     }
   
@@ -3205,7 +3400,11 @@ trans_begun:
     ** open savepoints. If the second parameter is greater than 0 and
     ** the sub-journal is not already open, then it will be opened here.
     */
-    rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint);
+    int nSavepoint = p->db->nSavepoint;
+    rc = sqlite3PagerOpenSavepoint(pBt->pPager, nSavepoint);
+    if( pBt->pMap && rc==SQLITE_OK && nSavepoint ){
+      rc = btreePtrmapBegin(pBt->pMap, nSavepoint);
+    }
   }
 
   btreeIntegrity(p);
@@ -3642,6 +3841,92 @@ static int autoVacuumCommit(BtShared *pBt){
 # define setChildPtrmaps(x) SQLITE_OK
 #endif
 
+/*
+** The b-tree handle passed as the only argument is about to commit an
+** UNLOCKED transaction. At this point it is guaranteed that this is 
+** possible - the wal WRITER lock is held and it is known that there are 
+** no conflicts with committed transactions.
+*/
+static int btreeFixUnlocked(Btree *p){
+  BtShared *pBt = p->pBt;
+  MemPage *pPage1 = pBt->pPage1;
+  u8 *p1 = pPage1->aData;
+  Pager *pPager = pBt->pPager;
+  int rc = SQLITE_OK;
+
+  /* If page 1 of the database is not writable, then no pages were allocated
+  ** or freed by this transaction. In this case no special handling is 
+  ** required. Otherwise, if page 1 is dirty, proceed.  */
+  BtreePtrmap *pMap = pBt->pMap;
+  Pgno iTrunk = get4byte(&p1[32]);
+  Pgno nPage = btreePagecount(pBt);
+  Pgno nOrig = pMap->iFirst-1;
+  u32 nFree = get4byte(&p1[36]);
+
+  assert( sqlite3PagerIsUnlocked(pPager) );
+  assert( pBt->pMap );
+  rc = sqlite3PagerUpgradeSnapshot(pPager, pPage1->pDbPage);
+  assert( p1==pPage1->aData );
+
+  if( rc==SQLITE_OK ){
+    Pgno nHPage = get4byte(&p1[28]);
+    Pgno nFinal = nHPage;
+
+    if( sqlite3PagerIswriteable(pPage1->pDbPage) ){
+      Pgno iHTrunk = get4byte(&p1[32]);
+      u32 nHFree = get4byte(&p1[36]);
+
+      /* Attach the head database free list to the end of the current
+      ** transactions free-list (if any).  */
+      if( iTrunk!=0 ){
+        put4byte(&p1[36], nHFree + nFree);
+        put4byte(&p1[32], iTrunk);
+        while( iTrunk ){
+          DbPage *pTrunk = sqlite3PagerLookup(pPager, iTrunk);
+          iTrunk = get4byte((u8*)pTrunk->pData);
+          if( iTrunk==0 ){
+            put4byte((u8*)pTrunk->pData, iHTrunk);
+          }
+          sqlite3PagerUnref(pTrunk);
+        };
+      }
+
+      if( nHPage<nOrig ){
+        rc = SQLITE_CORRUPT_BKPT;
+      }else{
+        /* The current transaction allocated pages pMap->iFirst through
+        ** nPage (inclusive) at the end of the database file. Meanwhile,
+        ** other transactions have allocated (iFirst..nHPage). So move
+        ** pages (iFirst..MIN(nPage,nHPage)) to (MAX(nPage,nHPage)+1).
+        */
+        Pgno iLast = MIN(nPage, nHPage);    /* Last page to move */
+        Pgno iPg;
+
+        nFinal = MAX(nPage, nHPage);   /* Final size of database */
+        for(iPg=pMap->iFirst; iPg<=iLast && rc==SQLITE_OK; iPg++){
+          MemPage *pPg = 0;
+          Pgno iNew;              /* New page number for pPg */
+          PtrmapEntry *pEntry;    /* Pointer map entry for page iPg */
+
+          btreeGetPage(pBt, iPg, &pPg, 0);
+          assert( sqlite3PagerIswriteable(pPg->pDbPage) );
+          assert( sqlite3PagerPageRefcount(pPg->pDbPage)==1 );
+          pEntry = &pMap->aPtr[iPg - pMap->iFirst];
+          iNew = ++nFinal;
+
+          rc = relocatePage(pBt, pPg, pEntry->eType, pEntry->parent, iNew, 1);
+          releasePageNotNull(pPg);
+        }
+
+        put4byte(&p1[28], nFinal);
+      }
+    }
+    sqlite3PagerSetDbsize(pPager, nFinal);
+  }
+
+  return rc;
+}
+
 /*
 ** This routine does the first phase of a two-phase commit.  This routine
 ** causes a rollback journal to be created (if it does not already exist)
@@ -3678,10 +3963,8 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
     /* Figure out if this is a commit of an UNLOCKED transaction that 
     ** requires a snapshot upgrade. If so, skip any auto-vacuum 
     ** processing.  */
-    if( pBt->autoVacuum && (
-        0==pBt->db->bUnlocked
-     || 0==sqlite3PagerCommitRequiresUpgrade(pBt->pPager) 
-    )){
+    if( pBt->autoVacuum ){
+      assert( sqlite3PagerIsUnlocked(pBt->pPager)==0 );
       rc = autoVacuumCommit(pBt);
       if( rc!=SQLITE_OK ){
         sqlite3BtreeLeave(p);
@@ -3692,6 +3975,9 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
       sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage);
     }
 #endif
+    if( rc==SQLITE_OK && sqlite3PagerIsUnlocked(pBt->pPager) ){
+      rc = btreeFixUnlocked(p);
+    }
     if( rc==SQLITE_OK ){
       rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0);
     }
@@ -3737,6 +4023,8 @@ static void btreeEndTransaction(Btree *p){
     unlockBtreeIfUnused(pBt);
   }
 
+  /* If this was an UNLOCKED transaction, delete the pBt->pMap object */
+  deletePtrmap(pBt);
   btreeIntegrity(p);
 }
 
@@ -3962,6 +4250,9 @@ int sqlite3BtreeBeginStmt(Btree *p, int iStatement){
   ** such savepoints while the statement transaction savepoint is active.
   */
   rc = sqlite3PagerOpenSavepoint(pBt->pPager, iStatement);
+  if( rc==SQLITE_OK && pBt->pMap ){
+    rc = btreePtrmapBegin(pBt->pMap, iStatement);
+  }
   sqlite3BtreeLeave(p);
   return rc;
 }
@@ -3985,6 +4276,7 @@ int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){
     assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK );
     assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) );
     sqlite3BtreeEnter(p);
+    if( pBt->pMap ) btreePtrmapEnd(pBt->pMap, op, iSavepoint);
     rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint);
     if( rc==SQLITE_OK ){
       if( iSavepoint<0 && (pBt->btsFlags & BTS_INITIALLY_EMPTY)!=0 ){
@@ -5493,6 +5785,16 @@ static int allocateBtreePage(
   if( n>=mxPage ){
     return SQLITE_CORRUPT_BKPT;
   }
+
+  /* Ensure page 1 is writable. This function will either change the number
+  ** of pages in the free-list or the size of the database file. Since both
+  ** of these operations involve modifying page 1 header fields, page 1
+  ** will definitely be written by this transaction. If this is an UNLOCKED
+  ** transaction, ensure the BtreePtrmap structure has been allocated.  */
+  assert( eMode!=BTALLOC_EXACT || sqlite3PagerIsUnlocked(pBt->pPager)==0 );
+  rc = sqlite3PagerWrite(pPage1->pDbPage);
+  if( rc ) return rc;
+
   if( n>0 ){
     /* There are pages on the freelist.  Reuse one of those pages. */
     Pgno iTrunk;
@@ -5523,8 +5825,6 @@ static int allocateBtreePage(
     /* Decrement the free-list count by 1. Set iTrunk to the index of the
     ** first free-list trunk page. iPrevTrunk is initially 1.
     */
-    rc = sqlite3PagerWrite(pPage1->pDbPage);
-    if( rc ) return rc;
     put4byte(&pPage1->aData[36], n-1);
 
     /* The code within this loop is run only once if the 'searchList' variable
@@ -5830,7 +6130,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){
   /* If the database supports auto-vacuum, write an entry in the pointer-map
   ** to indicate that the page is free.
   */
-  if( ISAUTOVACUUM ){
+  if( REQUIRE_PTRMAP ){
     ptrmapPut(pBt, iPage, PTRMAP_FREEPAGE, 0, &rc);
     if( rc ) goto freepage_out;
   }
@@ -6116,7 +6416,7 @@ static int fillInCell(
       }
 #endif
       rc = allocateBtreePage(pBt, &pOvfl, &pgnoOvfl, pgnoOvfl, 0);
-#ifndef SQLITE_OMIT_AUTOVACUUM
+
       /* If the database supports auto-vacuum, and the second or subsequent
       ** overflow page is being allocated, add an entry to the pointer-map
       ** for that page now. 
@@ -6127,14 +6427,13 @@ static int fillInCell(
       ** may misinterpret the uninitialized values and delete the
       ** wrong pages from the database.
       */
-      if( pBt->autoVacuum && rc==SQLITE_OK ){
+      if( REQUIRE_PTRMAP && rc==SQLITE_OK ){
         u8 eType = (pgnoPtrmap?PTRMAP_OVERFLOW2:PTRMAP_OVERFLOW1);
         ptrmapPut(pBt, pgnoOvfl, eType, pgnoPtrmap, &rc);
         if( rc ){
           releasePage(pOvfl);
         }
       }
-#endif
       if( rc ){
         releasePage(pToRelease);
         return rc;
@@ -6301,6 +6600,7 @@ static void insertCell(
     assert( j==0 || pPage->aiOvfl[j-1]<(u16)i ); /* Overflows in sorted order */
     assert( j==0 || i==pPage->aiOvfl[j-1]+1 );   /* Overflows are sequential */
   }else{
+    BtShared *pBt = pPage->pBt;
     int rc = sqlite3PagerWrite(pPage->pDbPage);
     if( rc!=SQLITE_OK ){
       *pRC = rc;
@@ -6315,7 +6615,7 @@ static void insertCell(
     ** if it returns successfully */
     assert( idx >= 0 );
     assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB );
-    assert( idx+sz <= (int)pPage->pBt->usableSize );
+    assert( idx+sz <= (int)pBt->usableSize );
     pPage->nFree -= (u16)(2 + sz);
     memcpy(&data[idx], pCell, sz);
     if( iChild ){
@@ -6328,14 +6628,12 @@ static void insertCell(
     /* increment the cell count */
     if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++;
     assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell );
-#ifndef SQLITE_OMIT_AUTOVACUUM
-    if( pPage->pBt->autoVacuum ){
+    if( REQUIRE_PTRMAP ){
       /* The cell may contain a pointer to an overflow page. If so, write
       ** the entry for the overflow page into the pointer map.
       */
       ptrmapPutOvflPtr(pPage, pCell, pRC);
     }
-#endif
   }
 }
 
@@ -6740,7 +7038,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){
     ** be marked as dirty. Returning an error code will cause a
     ** rollback, undoing any changes made to the parent page.
     */
-    if( ISAUTOVACUUM ){
+    if( REQUIRE_PTRMAP ){
       ptrmapPut(pBt, pgnoNew, PTRMAP_BTREE, pParent->pgno, &rc);
       if( szCell>pNew->minLocal ){
         ptrmapPutOvflPtr(pNew, pCell, &rc);
@@ -6875,7 +7173,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){
     /* If this is an auto-vacuum database, update the pointer-map entries
     ** for any b-tree or overflow pages that pTo now contains the pointers to.
     */
-    if( ISAUTOVACUUM ){
+    if( REQUIRE_PTRMAP ){
       *pRC = setChildPtrmaps(pTo);
     }
   }
@@ -7345,7 +7643,7 @@ static int balance_nonroot(
       cntOld[i] = b.nCell;
 
       /* Set the pointer-map entry for the new sibling page. */
-      if( ISAUTOVACUUM ){
+      if( REQUIRE_PTRMAP ){
         ptrmapPut(pBt, pNew->pgno, PTRMAP_BTREE, pParent->pgno, &rc);
         if( rc!=SQLITE_OK ){
           goto balance_cleanup;
@@ -7439,7 +7737,7 @@ static int balance_nonroot(
   ** updated. This happens below, after the sibling pages have been 
   ** populated, not here.
   */
-  if( ISAUTOVACUUM ){
+  if( REQUIRE_PTRMAP ){
     MemPage *pNew = apNew[0];
     u8 *aOld = pNew->aData;
     int cntOldNext = pNew->nCell + pNew->nOverflow;
@@ -7624,7 +7922,7 @@ static int balance_nonroot(
     );
     copyNodeContent(apNew[0], pParent, &rc);
     freePage(apNew[0], &rc);
-  }else if( ISAUTOVACUUM && !leafCorrection ){
+  }else if( REQUIRE_PTRMAP && !leafCorrection ){
     /* Fix the pointer map entries associated with the right-child of each
     ** sibling page. All other pointer map entries have already been taken
     ** care of.  */
@@ -7710,7 +8008,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){
   if( rc==SQLITE_OK ){
     rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0);
     copyNodeContent(pRoot, pChild, &rc);
-    if( ISAUTOVACUUM ){
+    if( REQUIRE_PTRMAP ){
       ptrmapPut(pBt, pgnoChild, PTRMAP_BTREE, pRoot->pgno, &rc);
     }
   }
@@ -9590,3 +9888,13 @@ int sqlite3BtreeIsReadonly(Btree *p){
 ** Return the size of the header added to each page by this module.
 */
 int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }
+
+int sqlite3BtreeExclusiveLock(Btree *p){
+  int rc;
+  BtShared *pBt = p->pBt;
+  assert( p->inTrans==TRANS_WRITE && pBt->pPage1 );
+  sqlite3BtreeEnter(p);
+  rc = sqlite3PagerExclusiveLock(pBt->pPager, pBt->pPage1->pDbPage);
+  sqlite3BtreeLeave(p);
+  return rc;
+}
index 50dc8ca2906c580da4acb94194c579dc23107fea..8f611b7a9b741ffc957747a7b061ca8fd760f7ed 100644 (file)
@@ -215,6 +215,8 @@ int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask);
 int sqlite3BtreeIsReadonly(Btree *pBt);
 int sqlite3HeaderSizeBtree(void);
 
+int sqlite3BtreeExclusiveLock(Btree *pBt);
+
 #ifndef NDEBUG
 int sqlite3BtreeCursorIsValid(BtCursor*);
 #endif
index cbf6c998479e427d5247504c3047b5962e93bca4..67b1da6e3f5013ee3b6599c3ccb4c0009b6035bf 100644 (file)
 typedef struct MemPage MemPage;
 typedef struct BtLock BtLock;
 typedef struct CellInfo CellInfo;
+typedef struct BtreePtrmap BtreePtrmap;
 
 /*
 ** This is a magic string that appears at the beginning of every
@@ -447,6 +448,9 @@ struct BtShared {
   Btree *pWriter;       /* Btree with currently open write transaction */
 #endif
   u8 *pTmpSpace;        /* Temp space sufficient to hold a single cell */
+#ifdef SQLITE_ENABLE_UNLOCKED
+  BtreePtrmap *pMap;
+#endif
 };
 
 /*
@@ -659,6 +663,12 @@ struct BtCursor {
 #define ISAUTOVACUUM 0
 #endif
 
+#ifdef SQLITE_ENABLE_UNLOCKED
+# define REQUIRE_PTRMAP (ISAUTOVACUUM || pBt->pMap)
+#else
+# define REQUIRE_PTRMAP ISAUTOVACUUM
+#endif
+
 
 /*
 ** This structure is passed around through all the sanity checking routines
index 5107edda361da155428c2f7330a12b252012f13e..efbea0ab8e0611698dafac1819feae5b685f1405 100644 (file)
@@ -657,7 +657,9 @@ struct Pager {
   u32 cksumInit;              /* Quasi-random value added to every checksum */
   u32 nSubRec;                /* Number of records written to sub-journal */
   Bitvec *pInJournal;         /* One bit for each page in the database file */
+#ifdef SQLITE_ENABLE_UNLOCKED
   Bitvec *pAllRead;           /* Pages read within current UNLOCKED trans. */
+#endif
   sqlite3_file *fd;           /* File descriptor for database */
   sqlite3_file *jfd;          /* File descriptor for main journal */
   sqlite3_file *sjfd;         /* File descriptor for sub-journal */
@@ -4091,7 +4093,7 @@ static int syncJournal(Pager *pPager, int newHdr){
   assert( assert_pager_state(pPager) );
   assert( !pagerUseWal(pPager) );
 
-  rc = sqlite3PagerExclusiveLock(pPager);
+  rc = sqlite3PagerExclusiveLock(pPager, 0);
   if( rc!=SQLITE_OK ) return rc;
 
   if( !pPager->noSync ){
@@ -5929,11 +5931,9 @@ int sqlite3PagerWrite(PgHdr *pPg){
 ** to sqlite3PagerWrite().  In other words, return TRUE if it is ok
 ** to change the content of the page.
 */
-#ifndef NDEBUG
 int sqlite3PagerIswriteable(DbPage *pPg){
   return pPg->flags & PGHDR_WRITEABLE;
 }
-#endif
 
 /*
 ** A call to this routine tells the pager that it is not necessary to
@@ -6091,7 +6091,7 @@ int sqlite3PagerSync(Pager *pPager, const char *zMaster){
 ** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is 
 ** returned.
 */
-int sqlite3PagerExclusiveLock(Pager *pPager){
+int sqlite3PagerExclusiveLock(Pager *pPager, PgHdr *pPage1){
   int rc = SQLITE_OK;
   assert( pPager->eState==PAGER_WRITER_CACHEMOD 
        || pPager->eState==PAGER_WRITER_DBMOD 
@@ -6108,7 +6108,7 @@ int sqlite3PagerExclusiveLock(Pager *pPager){
       ** non-zero.  */
       do {
         /* rc = sqlite3WalBeginWriteTransaction(pWal); */
-        rc = sqlite3WalLockForCommit(pPager->pWal, pPager->pAllRead);
+        rc = sqlite3WalLockForCommit(pPager->pWal, pPage1, pPager->pAllRead);
       }while( rc==SQLITE_BUSY 
            && pPager->xBusyHandler(pPager->pBusyHandlerArg) 
       );
@@ -6117,6 +6117,24 @@ int sqlite3PagerExclusiveLock(Pager *pPager){
   return rc;
 }
 
+int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage *pPage1){
+  int rc;
+  u32 iFrame = 0;
+
+  assert( pPager->pWal && pPager->pAllRead );
+  sqlite3WalUpgradeSnapshot(pPager->pWal);
+  rc = sqlite3WalFindFrame(pPager->pWal, 1, &iFrame);
+  if( rc==SQLITE_OK ){
+    rc = readDbPage(pPage1, iFrame);
+  }
+
+  return rc;
+}
+
+void sqlite3PagerSetDbsize(Pager *pPager, Pgno nFinal){
+  pPager->dbSize = nFinal;
+}
+
 /*
 ** If this is a WAL mode connection and the WRITER lock is currently held,
 ** relinquish it.
@@ -6131,14 +6149,11 @@ void sqlite3PagerDropExclusiveLock(Pager *pPager){
 ** Return true if this is a WAL database and snapshot upgrade is required
 ** before the current transaction can be committed.
 */
-int sqlite3PagerCommitRequiresUpgrade(Pager *pPager){
-  int res = 0;
-  if( pagerUseWal(pPager) ){
-    res = sqlite3WalCommitRequiresUpgrade(pPager->pWal);
-  }
-  return res;
+int sqlite3PagerIsUnlocked(Pager *pPager){
+  return pPager->pAllRead!=0;
 }
 
+
 /*
 ** Sync the database file for the pager pPager. zMaster points to the name
 ** of a master journal file that should be written into the individual
index 3c0b9a1a715ff92949cedb49a4c5feafb88863f5..92e5546282796b0f05b66c2cdca708c264e8c752 100644 (file)
@@ -145,12 +145,13 @@ int sqlite3PagerMovepage(Pager*,DbPage*,Pgno,int);
 int sqlite3PagerPageRefcount(DbPage*);
 void *sqlite3PagerGetData(DbPage *); 
 void *sqlite3PagerGetExtra(DbPage *); 
+int sqlite3PagerIsDirty(DbPage*);
 
 /* Functions used to manage pager transactions and savepoints. */
 void sqlite3PagerPagecount(Pager*, int*);
 int sqlite3PagerBegin(Pager*, int exFlag, int);
 int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int);
-int sqlite3PagerExclusiveLock(Pager*);
+int sqlite3PagerExclusiveLock(Pager*, DbPage *pPage1);
 int sqlite3PagerSync(Pager *pPager, const char *zMaster);
 int sqlite3PagerCommitPhaseTwo(Pager*);
 int sqlite3PagerRollback(Pager*);
@@ -159,7 +160,7 @@ int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint);
 int sqlite3PagerSharedLock(Pager *pPager);
 
 void sqlite3PagerDropExclusiveLock(Pager*);
-int sqlite3PagerCommitRequiresUpgrade(Pager*);
+int sqlite3PagerIsUnlocked(Pager*);
 
 #ifndef SQLITE_OMIT_WAL
   int sqlite3PagerCheckpoint(Pager *pPager, int, int*, int*);
@@ -196,6 +197,10 @@ void sqlite3PagerTruncateImage(Pager*,Pgno);
 
 void sqlite3PagerRekey(DbPage*, Pgno, u16);
 
+int sqlite3PagerIswriteable(DbPage*);
+int sqlite3PagerUpgradeSnapshot(Pager *pPager, DbPage*);
+void sqlite3PagerSetDbsize(Pager *pPager, Pgno);
+
 #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL)
 void *sqlite3PagerCodec(DbPage *);
 #endif
@@ -203,7 +208,6 @@ void *sqlite3PagerCodec(DbPage *);
 /* Functions to support testing and debugging. */
 #if !defined(NDEBUG) || defined(SQLITE_TEST)
   Pgno sqlite3PagerPagenumber(DbPage*);
-  int sqlite3PagerIswriteable(DbPage*);
 #endif
 #ifdef SQLITE_TEST
   int *sqlite3PagerStats(Pager*);
index 93a7187ab97774c6809f89906f299dfd95010ed8..f5f9d0fbdda4feda9a6ba963f0befa9396feb456 100644 (file)
@@ -2020,9 +2020,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){
     if( sqlite3BtreeIsInTrans(pBt) ){
       needXcommit = 1;
       if( i!=1 ) nTrans++;
-      sqlite3BtreeEnter(pBt);
-      rc = sqlite3PagerExclusiveLock(sqlite3BtreePager(pBt));
-      sqlite3BtreeLeave(pBt);
+      rc = sqlite3BtreeExclusiveLock(pBt);
     }
   }
 
index 5fbdae6a1adbb4abba2ed22deeab2aae5e14d683..809ce2b1097421bb83218403ad1d3e566edee95d 100644 (file)
--- a/src/wal.c
+++ b/src/wal.c
@@ -2573,7 +2573,8 @@ int sqlite3WalBeginWriteTransaction(Wal *pWal){
 ** transaction. It may be assumed that no frames have been written to
 ** the wal file.
 */
-int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pAllRead){
+int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pPage1, Bitvec *pAllRead){
+  Pager *pPager = pPage1->pPager;
   int rc = walWriteLock(pWal);
 
   /* If the database has been modified since this transaction was started,
@@ -2589,6 +2590,9 @@ int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pAllRead){
   if( rc==SQLITE_OK ){
     volatile WalIndexHdr *pHead;    /* Head of the wal file */
     pHead = walIndexHdr(pWal);
+
+    /* TODO: Check header checksum is good here. */
+
     if( memcmp(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr))!=0 ){
       /* TODO: Is this safe? Because it holds the WRITER lock this thread
       ** has exclusive access to the live header, but might it be corrupt? 
@@ -2609,12 +2613,33 @@ int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pAllRead){
           if( iMin<1 ) iMin = 1;
           if( iMax>pHead->mxFrame ) iMax = pHead->mxFrame;
           for(i=iMin; i<=iMax; i++){
-            if( sqlite3BitvecTestNotNull(pAllRead, aPgno[i]) ){
+            PgHdr *pPg;
+            if( aPgno[i]==1 ){
+              /* Check that the schema cookie has not been modified. If
+              ** it has not, the commit can proceed. */
+              u8 aNew[4];
+              u8 *aOld = &((u8*)pPage1->pData)[40];
+              int sz;
+              i64 iOffset;
+              sz = pWal->hdr.szPage;
+              sz = (sz&0xfe00) + ((sz&0x0001)<<16);
+              iOffset = walFrameOffset(i+iZero, sz) + WAL_FRAME_HDRSIZE + 40;
+              rc = sqlite3OsRead(pWal->pWalFd, aNew, sizeof(aNew), iOffset);
+              if( rc==SQLITE_OK && memcmp(aOld, aNew, sizeof(aNew)) ){
+                rc = SQLITE_BUSY_SNAPSHOT;
+              }
+            }else if( sqlite3BitvecTestNotNull(pAllRead, aPgno[i]) ){
               sqlite3_log(SQLITE_OK,
                   "cannot commit UNLOCKED transaction (conflict at page %d)",
                   (int)aPgno[i]
               );
               rc = SQLITE_BUSY_SNAPSHOT;
+            }else if( (pPg = sqlite3PagerLookup(pPager, aPgno[i])) ){
+              if( sqlite3PagerIswriteable(pPg)==0 ){
+                sqlite3PcacheDrop(pPg);
+              }else{
+                sqlite3PagerUnref(pPg);
+              }
             }
           }
         }
@@ -2626,6 +2651,11 @@ int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pAllRead){
   return rc;
 }
 
+void sqlite3WalUpgradeSnapshot(Wal *pWal){
+  assert( pWal->writeLock );
+  memcpy(&pWal->hdr, (void*)walIndexHdr(pWal), sizeof(WalIndexHdr));
+}
+
 /*
 ** This function is only ever called while committing an UNLOCKED 
 ** transaction, after the caller has already obtained the WRITER lock
@@ -2890,7 +2920,6 @@ int sqlite3WalFrames(
   int szFrame;                    /* The size of a single frame */
   i64 iOffset;                    /* Next byte to write in WAL file */
   WalWriter w;                    /* The writer */
-  int bUpgrade = 0;               /* True if commit requires snapshot upgrade */
 
   assert( pList );
   assert( pWal->writeLock );
@@ -2906,22 +2935,6 @@ int sqlite3WalFrames(
   }
 #endif
 
-  if( isCommit ){
-    volatile WalIndexHdr *pHead = walIndexHdr(pWal);
-    if( pHead->mxFrame>pWal->hdr.mxFrame ){
-      if( memcmp((void*)&pHead[0], (void*)&pHead[1], sizeof(WalIndexHdr))!=0 ){
-        /* TODO: Deal with this case. It's quite possible, but fiddly. */
-        return SQLITE_CORRUPT_BKPT;
-      }
-      memcpy(&pWal->hdr, (void*)pHead, sizeof(WalIndexHdr));
-      if( nTruncate<pWal->hdr.nPage ){
-        /* Do not truncate the database file in this case */
-        nTruncate = pWal->hdr.nPage;
-      }
-      bUpgrade = 1;
-    }
-  }
-
   /* 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.
   */
@@ -3070,14 +3083,6 @@ int sqlite3WalFrames(
     }
   }
 
-  if( rc==SQLITE_OK && bUpgrade ){
-    /* If this commit required a snapshot upgrade, the pager cache is 
-    ** not currently consistent with the head of the wal file. Zeroing
-    ** Wal.hdr here forces the next transaction to reset the cache 
-    ** before beginning to read the db. */
-    memset(&pWal->hdr, 0, sizeof(WalIndexHdr));
-  }
-
   WALTRACE(("WAL%p: frame write %s\n", pWal, rc ? "failed" : "ok"));
   return rc;
 }
index fe85a52cc03e9881324affc7e5e952939387018f..5b4ca159439d4b39a89c48e5103a22486376f76e 100644 (file)
--- a/src/wal.h
+++ b/src/wal.h
@@ -127,8 +127,9 @@ int sqlite3WalExclusiveMode(Wal *pWal, int op);
 int sqlite3WalHeapMemory(Wal *pWal);
 
 /* Return true if the WRITER lock is held. False otherwise. */
-int sqlite3WalLockForCommit(Wal *pWal, Bitvec *pRead);
+int sqlite3WalLockForCommit(Wal *pWal, PgHdr *pPg, Bitvec *pRead);
 int sqlite3WalCommitRequiresUpgrade(Wal *pWal);
+void sqlite3WalUpgradeSnapshot(Wal *pWal);
 
 #ifdef SQLITE_ENABLE_ZIPVFS
 /* If the WAL file is not empty, return the number of bytes of content
index 23eb5a506429ad35f4b86cae302b3614fc91ccbb..c743cc632845789713701a1a0016c3edc21ad0c4 100644 (file)
@@ -399,10 +399,9 @@ do_multiclient_test tn {
 
   do_test 2.$tn.7.3 {
     list [catch { sql1 { COMMIT } } msg] $msg [sqlite3_errcode db]
-  } {1 {database is locked} SQLITE_BUSY_SNAPSHOT}
-  sql1 ROLLBACK
-
+  } {0 {} SQLITE_OK}
 
+  do_test 2.$tn.7.4 { sql3 { PRAGMA integrity_check } } ok
 }
 
 
diff --git a/test/unlocked2.test b/test/unlocked2.test
new file mode 100644 (file)
index 0000000..f808a84
--- /dev/null
@@ -0,0 +1,101 @@
+# 2015 July 26
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/lock_common.tcl
+set ::testprefix unlocked2
+
+
+do_multiclient_test tn {
+
+  do_test 1.$tn.1 {
+    sql1 {
+      PRAGMA journal_mode = wal;
+      CREATE TABLE t1(x);
+      CREATE TABLE t2(y);
+    }
+  } {wal}
+
+  # Test that an UNLOCKED transaction that allocates/frees no pages does
+  # not conflict with a transaction that does allocate pages.
+  do_test 1.$tn.2  {
+    sql1 { 
+      BEGIN UNLOCKED;
+        INSERT INTO t1 VALUES(4);
+    }
+    sql2 {
+      INSERT INTO t2 VALUES(randomblob(1500));
+    }
+    sql1 {
+      COMMIT;
+    }
+  } {}
+  
+  # But that an UNLOCKED transaction does conflict with a transaction
+  # that modifies the db schema.
+  do_test 1.$tn.3  {
+    sql1 {
+      BEGIN UNLOCKED;
+        INSERT INTO t1 VALUES(5);
+    }
+    sql2 {
+      CREATE TABLE t3(z);
+    }
+    list [catch { sql1 COMMIT } msg] $msg
+  } {1 {database is locked}}
+  
+  # Test that an UNLOCKED transaction that allocates at least one page 
+  # does not conflict with a transaction that allocates no pages.
+  do_test 1.$tn.4  {
+    sql1 { 
+      ROLLBACK;
+      BEGIN UNLOCKED;
+        INSERT INTO t1 VALUES(randomblob(1500));
+    }
+    sql2 {
+      INSERT INTO t2 VALUES(8);
+    }
+    sql1 {
+      COMMIT;
+    }
+  } {}
+
+  do_test 1.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
+}
+
+do_multiclient_test tn {
+  do_test 2.$tn.1 {
+    sql1 {
+      PRAGMA journal_mode = wal;
+      CREATE TABLE t1(x UNIQUE);
+      CREATE TABLE t2(y UNIQUE);
+    }
+  } {wal}
+
+  do_test 2.$tn.2  {
+    sql1 { 
+      BEGIN UNLOCKED;
+        INSERT INTO t1 VALUES(randomblob(1500));
+    }
+    sql2 {
+      INSERT INTO t2 VALUES(randomblob(1500));
+    }
+    sql1 {
+      COMMIT;
+    }
+  } {}
+
+  do_test 2.$tn.3 { sql3 { PRAGMA integrity_check } } {ok}
+}
+
+finish_test