]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add further tests for deferred page allocation. And fixes for the same.
authordan <dan@noemail.net>
Sat, 22 Aug 2015 17:28:55 +0000 (17:28 +0000)
committerdan <dan@noemail.net>
Sat, 22 Aug 2015 17:28:55 +0000 (17:28 +0000)
FossilOrigin-Name: ed0a31be726e60115a5dd73d4ed580201b400ab7

manifest
manifest.uuid
src/btree.c
src/btreeInt.h
test/unlocked2.test
test/unlocked3.test [new file with mode: 0644]

index 6840db861cb6abaac9d094345d03e678b51220a5..bc04296ba05ca98c818798dc68eb8aa6bd4f322f 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Merge\sfurther\strunk\schanges.
-D 2015-08-22T07:56:49.673
+C Add\sfurther\stests\sfor\sdeferred\spage\sallocation.\sAnd\sfixes\sfor\sthe\ssame.
+D 2015-08-22T17:28:55.533
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in e2218eb228374422969de7b1680eda6864affcef
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -279,9 +279,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 dc80ac8374105e3caf298a6533e3312101691330
+F src/btree.c 245bc696824d5107f3570905325c6ac5b05c18f8
 F src/btree.h 00d4cdb747c4172a5566faf037116985dbbc377e
-F src/btreeInt.h 1f7258e1f35ba3cc2197f9019f523dd20d59a0d8
+F src/btreeInt.h 0c19847f87ab82e4f5e67750a069f10829475da6
 F src/build.c e47b6fffe14a28d9050e6747beebb01597d37542
 F src/callback.c 7b44ce59674338ad48b0e84e7b72f935ea4f68b0
 F src/complete.c addcd8160b081131005d5bc2d34adf20c1c5c92f
@@ -1228,7 +1228,8 @@ F test/unique.test 93f8b2ef5ea51b9495f8d6493429b1fd0f465264
 F test/unique2.test 41e7f83c6827605991160a31380148a9fc5f1339
 F test/unixexcl.test cd6c765f75e50e8e2c2ba763149e5d340ea19825
 F test/unlocked.test d143a871bd874311d4e5c98ce458218ca6b579fd
-F test/unlocked2.test 1e645e6f6808e4303f1766d8c615c22714f31541
+F test/unlocked2.test 7f913b404cda03f22bc7b299e8eb82a34d2880c5
+F test/unlocked3.test 4facd06286885faa9bd53713fa3d701ca403aeb2
 F test/unordered.test ca7adce0419e4ca0c50f039885e76ed2c531eda8
 F test/update.test 6c68446b8a0a33d522a7c72b320934596a2d7d32
 F test/uri.test 23662b7b61958b0f0e47082de7d06341ccf85d5b
@@ -1380,7 +1381,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 9021f7369f4b32db6126b3dc0ac75d11039625be 213a6c5ccbcfe4495d45e3608e99a6398751aeed
-R d0dd8783a44e8da0a2326d10e188c817
+P c2327a3b8e5d604ab948b1e9f6cfc401429e51db
+R c62a3f455b34280d099a1332c1c4f1e4
 U dan
-Z dd4c495d35a4ec7386b2f6d290b0dddd
+Z 1442e612d3eba52580ab50755ceb943f
index 4a13cf0ec435cfe8650346b67919e984cd7e7e2e..545070a4322267c2c4dd3024e43e0f676ddb5d95 100644 (file)
@@ -1 +1 @@
-c2327a3b8e5d604ab948b1e9f6cfc401429e51db
\ No newline at end of file
+ed0a31be726e60115a5dd73d4ed580201b400ab7
\ No newline at end of file
index fd7afc073cf35d78b1eed985025678e5bfa38c8e..53b769720d9f17ac963f34f9f0dc9f164e8dbc01 100644 (file)
@@ -439,19 +439,14 @@ static void downgradeAllSharedCacheTableLocks(Btree *p){
 
 #endif /* SQLITE_OMIT_SHARED_CACHE */
 
+
+#ifdef SQLITE_ENABLE_UNLOCKED
 /*
-** 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.
-**
-**
+** The following structure - BtreePtrmap - 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.
 */
-
 typedef struct RollbackEntry RollbackEntry;
 typedef struct PtrmapEntry PtrmapEntry;
 struct PtrmapEntry {
@@ -463,7 +458,6 @@ struct RollbackEntry {
   Pgno parent;
   u8 eType;
 };
-
 struct BtreePtrmap {
   Pgno iFirst;                    /* First new page number aPtr[0] */
 
@@ -479,17 +473,22 @@ struct BtreePtrmap {
   RollbackEntry *aRollback;       /* Array of rollback entries */
 };
 
+/*
+** If page number pgno is greater than or equal to BtreePtrmap.iFirst, 
+** store an entry for it in the pointer-map structure.
+*/
 static int btreePtrmapStore(
-  BtreePtrmap *pMap, 
-  Pgno pgno, 
+  BtShared *pBt,
+  Pgno pgno,
   u8 eType, 
   Pgno parent
 ){
+  BtreePtrmap *pMap = pBt->pMap;
   if( pgno>=pMap->iFirst ){
     int iEntry = pgno - pMap->iFirst;
 
-    /* Grow the aPtr[] array if required */
-    if( iEntry>=pMap->nPtrAlloc ){
+    /* Grow the aPtr[] array as required */
+    while( iEntry>=pMap->nPtrAlloc ){
       int nNew = pMap->nPtrAlloc ? pMap->nPtrAlloc*2 : 16;
       PtrmapEntry *aNew = (PtrmapEntry*)sqlite3_realloc(
           pMap->aPtr, nNew*sizeof(PtrmapEntry)
@@ -535,8 +534,9 @@ static int btreePtrmapStore(
 /*
 ** Open savepoint iSavepoint, if it is not already open.
 */
-static int btreePtrmapBegin(BtreePtrmap *pMap, int nSvpt){
-  if( nSvpt<pMap->nSvpt ){
+static int btreePtrmapBegin(BtShared *pBt, int nSvpt){
+  BtreePtrmap *pMap = pBt->pMap;
+  if( pMap && nSvpt<pMap->nSvpt ){
     int i;
     if( nSvpt>=pMap->nSvptAlloc ){
       int nNew = pMap->nSvptAlloc ? pMap->nSvptAlloc*2 : 16;
@@ -562,28 +562,72 @@ static int btreePtrmapBegin(BtreePtrmap *pMap, int nSvpt){
 ** 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;
+static void btreePtrmapEnd(BtShared *pBt, int op, int iSvpt){
+  BtreePtrmap *pMap = pBt->pMap;
+  if( pMap ){
+    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];
     }
-    pMap->nSvpt = iSvpt + (op==SAVEPOINT_ROLLBACK);
-    pMap->nRollback = pMap->aSvpt[iSvpt];
   }
 }
 
+/*
+** This function is called after an UNLOCKED transaction is opened on the
+** database. It allocates the BtreePtrmap structure used to track pointers
+** to allocated pages and zeroes the nFree/iTrunk fields in the database 
+** header on page 1.
+*/
+static int btreePtrmapAllocate(BtShared *pBt){
+  int rc = SQLITE_OK;
+  BtreePtrmap *pMap = sqlite3_malloc(sizeof(BtreePtrmap));
+  assert( pBt->pMap==0 && sqlite3PagerIsUnlocked(pBt->pPager) );
+  if( pMap==0 ){
+    rc = SQLITE_NOMEM;
+  }else{
+    memset(&pBt->pPage1->aData[32], 0, sizeof(u32)*2);
+    memset(pMap, 0, sizeof(BtreePtrmap));
+    pMap->iFirst = pBt->nPage + 1;
+    pBt->pMap = pMap;
+  }
+  return rc;
+}
+
+/*
+** Free any BtreePtrmap structure allocated by an earlier call to
+** btreePtrmapAllocate().
+*/
+static void btreePtrmapDelete(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 btreePtrmapAllocate(x) SQLITE_OK
+# define btreePtrmapDelete(x) 
+# define btreePtrmapBegin(x,y)  SQLITE_OK
+# define btreePtrmapEnd(x,y,z) 
+#endif
 
 static void releasePage(MemPage *pPage);  /* Forward reference */
 
@@ -1024,44 +1068,49 @@ 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 is never added to a pointer-map page */
+  assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) );
+
+#ifdef SQLITE_ENABLE_UNLOCKED
   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)) );
+    *pRC = btreePtrmapStore(pBt, key, eType, parent);
+    return;
+  }
+#endif
 
-    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);
+  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);
 
-      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);
 }
 
 /*
@@ -2072,50 +2121,6 @@ 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.
 **
@@ -3341,8 +3346,8 @@ int sqlite3BtreeBeginTrans(Btree *p, int wrflag){
         if( rc==SQLITE_OK ){
           rc = newDatabase(pBt);
         }
-        if( rc==SQLITE_OK ){
-          rc = allocatePtrmap(pBt);
+        if( rc==SQLITE_OK && sqlite3PagerIsUnlocked(pBt->pPager) ){
+          rc = btreePtrmapAllocate(pBt);
         }
       }
     }
@@ -3402,8 +3407,8 @@ trans_begun:
     */
     int nSavepoint = p->db->nSavepoint;
     rc = sqlite3PagerOpenSavepoint(pBt->pPager, nSavepoint);
-    if( pBt->pMap && rc==SQLITE_OK && nSavepoint ){
-      rc = btreePtrmapBegin(pBt->pMap, nSavepoint);
+    if( rc==SQLITE_OK && nSavepoint ){
+      rc = btreePtrmapBegin(pBt, nSavepoint);
     }
   }
 
@@ -3841,6 +3846,7 @@ static int autoVacuumCommit(BtShared *pBt){
 # define setChildPtrmaps(x) SQLITE_OK
 #endif
 
+#ifdef SQLITE_ENABLE_UNLOCKED
 /*
 ** 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 
@@ -3913,6 +3919,7 @@ static int btreeFixUnlocked(Btree *p){
           Pgno iNew;              /* New page number for pPg */
           PtrmapEntry *pEntry;    /* Pointer map entry for page iPg */
 
+          if( iPg==PENDING_BYTE_PAGE(pBt) ) continue;
           pEntry = &pMap->aPtr[iPg - pMap->iFirst];
           if( pEntry->eType==PTRMAP_FREEPAGE ){
             MemPage *pFree = 0;
@@ -3925,31 +3932,39 @@ static int btreeFixUnlocked(Btree *p){
             assert( sqlite3PagerIswriteable(pPg->pDbPage) );
             assert( sqlite3PagerPageRefcount(pPg->pDbPage)==1 );
             iNew = ++nCurrent;
+            if( iNew==PENDING_BYTE_PAGE(pBt) ) iNew = ++nCurrent;
             rc = relocatePage(pBt, pPg, pEntry->eType, pEntry->parent, iNew, 1);
             releasePageNotNull(pPg);
           }
         }
         sqlite3PagerSetDbsize(pPager, nCurrent);
+        assert( nCurrent!=PENDING_BYTE_PAGE(pBt) );
 
         nFree = get4byte(&p1[36]);
         nFinal = MAX(nCurrent-nFree, nHPage);
+        if( nCurrent>PENDING_BYTE_PAGE(pBt) && nFinal<=PENDING_BYTE_PAGE(pBt) ){
+          nFinal--;
+        }
 
-        for(iPg=nFinal+1; rc==SQLITE_OK && iPg<nCurrent; iPg++){
+        for(iPg=nFinal+1; rc==SQLITE_OK && iPg<=nCurrent; iPg++){
           Pgno iNew;              /* New page number for pPg */
           MemPage *pFree;
           PtrmapEntry *pEntry;    /* Pointer map entry for page iPg */
 
+          if( iPg==PENDING_BYTE_PAGE(pBt) ) continue;
           pEntry = &pMap->aPtr[iPg - pMap->iFirst];
           if( pEntry->eType==PTRMAP_FREEPAGE ){
             rc = allocateBtreePage(pBt, &pFree, &iNew, iPg, BTALLOC_EXACT);
             releasePage(pFree);
           }else{
             rc = allocateBtreePage(pBt, &pFree, &iNew, nFinal, BTALLOC_LE);
+            assert( rc!=SQLITE_OK || iNew<=nFinal );
             releasePage(pFree);
             if( rc==SQLITE_OK ){
               MemPage *pPg = 0;
               btreeGetPage(pBt, iPg, &pPg, 0);
               rc = relocatePage(pBt, pPg, pEntry->eType, pEntry->parent,iNew,1);
+              releasePage(pPg);
             }
           }
         }
@@ -3961,6 +3976,7 @@ static int btreeFixUnlocked(Btree *p){
 
   return rc;
 }
+#endif
 
 /*
 ** This routine does the first phase of a two-phase commit.  This routine
@@ -3995,11 +4011,8 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
     sqlite3BtreeEnter(p);
 
 #ifndef SQLITE_OMIT_AUTOVACUUM
-    /* 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 ){
-      assert( sqlite3PagerIsUnlocked(pBt->pPager)==0 );
+      assert( ISUNLOCKED==0 );
       rc = autoVacuumCommit(pBt);
       if( rc!=SQLITE_OK ){
         sqlite3BtreeLeave(p);
@@ -4010,7 +4023,7 @@ int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){
       sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage);
     }
 #endif
-    if( rc==SQLITE_OK && sqlite3PagerIsUnlocked(pBt->pPager) ){
+    if( rc==SQLITE_OK && ISUNLOCKED ){
       rc = btreeFixUnlocked(p);
     }
     if( rc==SQLITE_OK ){
@@ -4059,7 +4072,7 @@ static void btreeEndTransaction(Btree *p){
   }
 
   /* If this was an UNLOCKED transaction, delete the pBt->pMap object */
-  deletePtrmap(pBt);
+  btreePtrmapDelete(pBt);
   btreeIntegrity(p);
 }
 
@@ -4285,8 +4298,8 @@ 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);
+  if( rc==SQLITE_OK ){
+    rc = btreePtrmapBegin(pBt, iStatement);
   }
   sqlite3BtreeLeave(p);
   return rc;
@@ -4311,7 +4324,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);
+    btreePtrmapEnd(pBt, op, iSavepoint);
     rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint);
     if( rc==SQLITE_OK ){
       if( iSavepoint<0 && (pBt->btsFlags & BTS_INITIALLY_EMPTY)!=0 ){
@@ -5817,7 +5830,7 @@ static int allocateBtreePage(
   ** stores stores the total number of pages on the freelist. */
   n = get4byte(&pPage1->aData[36]);
   testcase( n==mxPage-1 );
-  if( sqlite3PagerIsUnlocked(pBt->pPager)==0 && n>=mxPage ){
+  if( ISUNLOCKED==0 && n>=mxPage ){
     return SQLITE_CORRUPT_BKPT;
   }
 
@@ -5840,7 +5853,7 @@ static int allocateBtreePage(
     ** the entire-list will be searched for that page.
     */
     if( eMode==BTALLOC_EXACT ){
-      assert( ISAUTOVACUUM==!sqlite3PagerIsUnlocked(pBt->pPager) );
+      assert( ISAUTOVACUUM!=ISUNLOCKED );
       if( ISAUTOVACUUM ){
         if( nearby<=mxPage ){
           u8 eType;
@@ -9926,6 +9939,23 @@ int sqlite3BtreeIsReadonly(Btree *p){
 */
 int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); }
 
+/*
+** This function is called to ensure that all locks required to commit the
+** current write-transaction to the database file are held. If the db is
+** in rollback mode, this means the EXCLUSIVE lock on the database file.
+**
+** Or, if this is an UNLOCKED transaction on a wal-mode database, the WRITER
+** lock on the wal file. In this case this function also checks that the
+** UNLOCKED transaction can be safely committed (does not commit with any
+** other transaction committed since it was opened).
+**
+** SQLITE_OK is returned if successful. SQLITE_BUSY if the required locks
+** cannot be obtained due to a conflicting lock. If the locks cannot be
+** obtained for an UNLOCKED transaction due to a conflict with an already
+** committed transaction, SQLITE_BUSY_SNAPSHOT is returned. Otherwise, if
+** some other error (OOM, IO, etc.) occurs, the relevant SQLite error code
+** is returned.
+*/
 int sqlite3BtreeExclusiveLock(Btree *p){
   int rc;
   BtShared *pBt = p->pBt;
index a60f2d5c17798775e3151d78f39024362aa630f5..4c23629cc8445a56342f667f35215c11b2956d46 100644 (file)
@@ -664,11 +664,12 @@ struct BtCursor {
 #endif
 
 #ifdef SQLITE_ENABLE_UNLOCKED
-# define REQUIRE_PTRMAP (ISAUTOVACUUM || pBt->pMap)
+# define ISUNLOCKED (pBt->pMap!=0)
 #else
-# define REQUIRE_PTRMAP ISAUTOVACUUM
+# define ISUNLOCKED 0
 #endif
 
+#define REQUIRE_PTRMAP (ISAUTOVACUUM || ISUNLOCKED)
 
 /*
 ** This structure is passed around through all the sanity checking routines
index e5b102aa02b51f420c4ffae49555c945e5225f11..927fe3e6752f69dddeda1fb526a16524b6da442e 100644 (file)
@@ -68,7 +68,6 @@ do_multiclient_test tn {
     sql2 {
       INSERT INTO t2 VALUES(8);
     }
-    breakpoint
     sql1 {
       COMMIT;
     }
@@ -225,5 +224,38 @@ do_multiclient_test tn {
   } {}
 }
 
+#-------------------------------------------------------------------------
+#
+do_multiclient_test tn {
+  do_test 5.$tn.1 {
+    sql1 {
+      PRAGMA journal_mode = wal;
+      CREATE TABLE t1(x);
+      CREATE TABLE t2(x);
+      INSERT INTO t1 VALUES(randomblob(1500));
+      PRAGMA page_count;
+    }
+  } {wal 4}
+
+  do_test 5.$tn.2 {
+    sql1 {
+      BEGIN UNLOCKED;
+        INSERT INTO t2 VALUES(randomblob(1500));
+        PRAGMA page_count;
+    }
+  } {5}
+
+  do_test 5.$tn.3 {
+    sql2 { 
+      DELETE FROM t1;
+      PRAGMA freelist_count;
+      PRAGMA page_count;
+    }
+  } {1 4}
+
+  do_test 5.$tn.4 { sql1 COMMIT } {}
+  do_test 5.$tn.5 { sql3 { PRAGMA integrity_check } } {ok}
+}
+
 finish_test
 
diff --git a/test/unlocked3.test b/test/unlocked3.test
new file mode 100644 (file)
index 0000000..04e741d
--- /dev/null
@@ -0,0 +1,110 @@
+# 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.
+#
+#***********************************************************************
+#
+# Tests for transactions started with BEGIN UNLOCKED. The tests in this
+# file focus on testing that deferred page allocation works properly.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+source $testdir/lock_common.tcl
+set ::testprefix unlocked3
+
+if {$AUTOVACUUM} { finish_test ; return }
+
+proc create_schema {} {
+  db eval {
+    PRAGMA journal_mode = wal;
+
+    CREATE TABLE t1(x, y);
+    CREATE TABLE t2(x, y);
+    CREATE TABLE t3(x, y);
+    CREATE TABLE t4(x, y);
+
+    CREATE INDEX i1 ON t1(y, x);
+    CREATE INDEX i2 ON t2(y, x);
+    CREATE INDEX i3 ON t3(y, x);
+    CREATE INDEX i4 ON t4(y, x);
+  }
+}
+
+proc do_sql_op {iTbl iOp} {
+  set db "db$iTbl"
+
+  switch $iOp {
+    "i" {
+      set sql "
+        WITH cnt(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM cnt WHERE i<10)
+        INSERT INTO t$iTbl SELECT randomblob(800), randomblob(800) FROM cnt;
+      "
+    }
+
+    "d" {
+      set sql "
+        DELETE FROM t$iTbl WHERE rowid IN (
+          SELECT rowid FROM t$iTbl ORDER BY 1 ASC LIMIT 10
+        )
+      "
+    }
+
+    default {
+      error "bad iOp parameter: $iOp"
+    }
+  }
+
+  $db eval $sql
+}
+
+
+set DBLIST {db1 db2 db3 db4} 
+
+create_schema
+foreach {tn oplist} {
+  1 {1i   2i   3i   4i} 
+  2 {1iii 2iii 3iii 4iii}
+  3 {1d   2d   3d   4d} 
+  . -----------------------
+  4 {1i}
+  5 {1d 2i}
+  . -----------------------
+  6 {1iii 2iii 3iii 4iii}
+  7 {1di  2id  3iii 4ddd}
+  8 {1iii 2iii 3iii 4iii}
+} {
+  if {[string range $oplist 0 0]=="-"} {
+    reset_db
+    create_schema
+    continue
+  }
+  foreach db $DBLIST { sqlite3 $db test.db }
+
+  do_test 1.$tn {
+    foreach db $DBLIST { $db eval "BEGIN UNLOCKED" } 
+
+    foreach op $oplist {
+      set iTbl [string range $op 0 0]
+      foreach char [split [string range $op 1 end] {}] {
+        do_sql_op $iTbl $char
+      }
+    }
+
+    foreach db $DBLIST { $db eval "COMMIT" }
+    db eval {PRAGMA integrity_check}
+  } {ok}
+
+  foreach db $DBLIST { 
+    if {$db=="db2"} breakpoint
+    $db close 
+  }
+}
+
+finish_test
+