]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Allow btree cursors to persist through BtreeDelete() calls. (CVS 2103)
authordanielk1977 <danielk1977@noemail.net>
Tue, 16 Nov 2004 04:57:24 +0000 (04:57 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Tue, 16 Nov 2004 04:57:24 +0000 (04:57 +0000)
FossilOrigin-Name: 6ad5fc8e1a119b750a82fc1426704164a2042d57

manifest
manifest.uuid
src/btree.c
test/btree8.test

index 548005fe8c7da27c2b832671ab15cdfa375ada24..26acdd9abfaca363abffe7b9f9fec3852bc14c13 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sa\stypo\sin\sthe\sheader\scomment\sto\sthe\sMakeRecord\sopcode\sso\sthat\sthe\ndocumentation\sgenerator\swill\sactually\ssee\sthe\sopcode\sdescription.\nTicket\s#1001.\s(CVS\s2102)
-D 2004-11-15T23:42:28
+C Allow\sbtree\scursors\sto\spersist\sthrough\sBtreeDelete()\scalls.\s(CVS\s2103)
+D 2004-11-16T04:57:24
 F Makefile.in e747bb5ba34ccbdd81f79dcf1b2b33c02817c21d
 F Makefile.linux-gcc a9e5a0d309fa7c38e7c14d3ecf7690879d3a5457
 F README a01693e454a00cc117967e3f9fdab2d4d52e9bc1
@@ -29,7 +29,7 @@ F sqlite3.def dbaeb20c153e1d366e8f421b55a573f5dfc00863
 F sqlite3.pc.in 985b9bf34192a549d7d370e0f0b6b34a4f61369a
 F src/attach.c e49d09dad9f5f9fb10b4b0c1be5a70ae4c45e689
 F src/auth.c 3b81f2a42f48a62c2c9c9b0eda31a157c681edea
-F src/btree.c b0b6238c54302e779aa9cb958c8c6da7cab4a090
+F src/btree.c 967b42616d0dc06d1bc9eb12d0af95252163df01
 F src/btree.h 861e40b759a195ba63819740e484390012cf81ab
 F src/build.c a95eb1181247368b0ffe2eed121a43735976a964
 F src/date.c 65536e7ea04fdde6e0551264fca15966966e171f
@@ -101,7 +101,7 @@ F test/btree4.test 3797b4305694c7af6828675b0f4b1424b8ca30e4
 F test/btree5.test 8e5ff32c02e685d36516c6499add9375fe1377f2
 F test/btree6.test a5ede6bfbbb2ec8b27e62813612c0f28e8f3e027
 F test/btree7.test a6d3b842db22af97dd14b989e90a2fd96066b72f
-F test/btree8.test eee59cfd3a0151adbc7d85e5373f31ae6d4c3061
+F test/btree8.test f0841e71ea311faf3fa04d5c80a75ccb9dc6d2c9
 F test/capi2.test 1ec97bf8896185aec2366c7d07b01edef6ae4b7e
 F test/capi3.test c9b162838cda7f61c6cfefed304b69287f2fc688
 F test/capi3b.test 5b6a66f9f295f79f443b5d3f33187fa5ef6cf336
@@ -258,7 +258,7 @@ F www/tclsqlite.tcl 560ecd6a916b320e59f2917317398f3d59b7cc25
 F www/vdbe.tcl 095f106d93875c94b47367384ebc870517431618
 F www/version3.tcl 092a01f5ef430d2c4acc0ae558d74c4bb89638a0
 F www/whentouse.tcl fdacb0ba2d39831e8a6240d05a490026ad4c4e4c
-P bb2d9ff881e4c6fe0cf60a13609d39426346fa9d
-R 2be6540265abc47343fc0c7a5238dac8
-U drh
-Z 7898d7d96ac0e92187c4488997a79889
+P 33c9b647aa70d1a9dab0e999daf853aa71d7df37
+R 50b298d755ddcbf483d54572fec74d25
+U danielk1977
+Z 8f5fe953592a6b674b59302a9768154c
index 7e7dfd33d949ea83b83955198e477175ed908b1a..d6372e52c2a1ad78b93e500497ff46cd08750506 100644 (file)
@@ -1 +1 @@
-33c9b647aa70d1a9dab0e999daf853aa71d7df37
\ No newline at end of file
+6ad5fc8e1a119b750a82fc1426704164a2042d57
\ No newline at end of file
index b132302523002539afb770c48b25024e80001ea7..daaf397069127a2053cb6d0d444daa66eca9d29d 100644 (file)
@@ -9,7 +9,7 @@
 **    May you share freely, never taking more than you give.
 **
 *************************************************************************
-** $Id: btree.c,v 1.217 2004/11/13 13:19:56 danielk1977 Exp $
+** $Id: btree.c,v 1.218 2004/11/16 04:57:24 danielk1977 Exp $
 **
 ** This file implements a external (disk-based) database using BTrees.
 ** For a detailed discussion of BTrees, refer to
@@ -344,6 +344,16 @@ struct CellInfo {
 ** A cursor is a pointer to a particular entry in the BTree.
 ** The entry is identified by its MemPage and the index in
 ** MemPage.aCell[] of the entry.
+**
+** Normally, the BtCursor.delShift variable is 0. If non-zero, this
+** indicates that the entry to which the cursor logically points 
+** was deleted (by a BtreeDelete() call). If this is the case, the
+** BtreeKeySize() and BtreeDataSize() calls both return 0. 
+
+** If BtCursor.delShift is +1, then do not move the cursor for a 
+** BtreeNext() operation (it was already advanced when the entry the
+** cursor logically points to was deleted). If BtCursor.delShift is
+** -1, then ignore the next BtreePrevious() call.
 */
 struct BtCursor {
   Btree *pBt;               /* The Btree to which this cursor belongs */
@@ -357,6 +367,7 @@ struct BtCursor {
   u8 wrFlag;                /* True if writable */
   u8 isValid;               /* TRUE if points to a valid entry */
   u8 status;                /* Set to SQLITE_ABORT if cursors is invalidated */
+  int delShift;             /* See above. */
 };
 
 /*
@@ -2104,6 +2115,7 @@ int sqlite3BtreeCursor(
   pBt->pCursor = pCur;
   pCur->isValid = 0;
   pCur->status = SQLITE_OK;
+  pCur->delShift = 0;
   *ppCur = pCur;
   return SQLITE_OK;
 
@@ -2202,7 +2214,7 @@ static void getCellInfo(BtCursor *pCur){
 ** itself, not the number of bytes in the key.
 */
 int sqlite3BtreeKeySize(BtCursor *pCur, i64 *pSize){
-  if( !pCur->isValid ){
+  if( !pCur->isValid || pCur->delShift ){
     *pSize = 0;
   }else{
     getCellInfo(pCur);
@@ -2219,7 +2231,7 @@ int sqlite3BtreeKeySize(BtCursor *pCur, i64 *pSize){
 ** the database is empty) then *pSize is set to 0.
 */
 int sqlite3BtreeDataSize(BtCursor *pCur, u32 *pSize){
-  if( !pCur->isValid ){
+  if( !pCur->isValid || pCur->delShift ){
     /* Not pointing at a valid entry - set *pSize to 0. */
     *pSize = 0;
   }else{
@@ -2330,7 +2342,7 @@ static int getPayload(
 ** the available payload.
 */
 int sqlite3BtreeKey(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
-  if( pCur->isValid==0 ){
+  if( !pCur->isValid || pCur->delShift ){
     return pCur->status;
   }
   assert( pCur->pPage!=0 );
@@ -2349,7 +2361,7 @@ int sqlite3BtreeKey(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
 ** the available payload.
 */
 int sqlite3BtreeData(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){
-  if( !pCur->isValid ){
+  if( !pCur->isValid || pCur->delShift ){
     return pCur->status ? pCur->status : SQLITE_INTERNAL;
   }
   assert( pCur->pPage!=0 );
@@ -2537,6 +2549,7 @@ static int moveToRoot(BtCursor *pCur){
     rc = moveToChild(pCur, subpage);
   }
   pCur->isValid = pCur->pPage->nCell>0;
+  pCur->delShift = 0;
   return rc;
 }
 
@@ -2773,6 +2786,16 @@ int sqlite3BtreeNext(BtCursor *pCur, int *pRes){
   }
   assert( pPage->isInit );
   assert( pCur->idx<pPage->nCell );
+
+  /* If BtCursor.delShift is 1, the cursor has already been advanced. */
+  if( pCur->delShift==1 ){
+    *pRes = 0;
+    pCur->delShift = 0;
+    return SQLITE_OK;
+  }else{
+    pCur->delShift = 0;
+  }
+
   pCur->idx++;
   pCur->info.nSize = 0;
   if( pCur->idx>=pPage->nCell ){
@@ -2822,6 +2845,16 @@ int sqlite3BtreePrevious(BtCursor *pCur, int *pRes){
     *pRes = 1;
     return SQLITE_OK;
   }
+
+  /* If BtCursor.delShift is -1, the cursor has already been advanced. */
+  if( pCur->delShift==-1 ){
+    *pRes = 0;
+    pCur->delShift = 0;
+    return SQLITE_OK;
+  }else{
+    pCur->delShift = 0;
+  }
+
   pPage = pCur->pPage;
   assert( pPage->isInit );
   assert( pCur->idx>=0 );
@@ -3906,60 +3939,6 @@ static int balance_nonroot(MemPage *pPage){
     nNew>=4 ? pgnoNew[3] : 0, nNew>=4 ? szNew[3] : 0,
     nNew>=5 ? pgnoNew[4] : 0, nNew>=5 ? szNew[4] : 0));
 
-#if 0
-  /* The following block shows how cells migrated during the balance op. */
-  if( sqlite3_btree_trace ){
-    char zBuf[200];
-    char *zCsr = zBuf;
-    int a, b, c=0, d=0;
-    *zCsr = '\0';
-    for(a=0; a<nOld; a++){
-      int nOldCells = apCopy[a]->nCell+apCopy[a]->nOverflow;
-      for(b=0; b<(nOldCells+((a!=nOld-1&&!leafData)?1:0)); b++){
-        int x = 0;
-        Pgno iNewPage;
-        Pgno iOldPage;
-        int iNewIndex;
-        int iOldIndex;
-
-        if( b<nOldCells ){
-          iOldPage = pgnoOld[a];
-          iOldIndex = b;
-        }else{
-          iOldPage = pParent->pgno;
-          iOldIndex = idxDiv[a];
-        }
-
-        while( cntNew[x]<=c ) x++;
-        if( x>0 && c==cntNew[x-1] && !leafData ){
-          iNewPage = pParent->pgno;
-          iNewIndex = nxDiv + a;
-        }else{
-          assert( x<nNew );
-          iNewPage = pgnoNew[x];
-          iNewIndex = c-(x>0?cntNew[x-1]:0)-(leafData?0:1);
-        }
-
-        if( (&zBuf[sizeof(zBuf)])-zCsr > 100 && 
-            (1 || iOldPage!=iNewPage || iOldIndex!=iNewIndex) ){
-          zCsr += sprintf(zCsr, " %d.%d->%d.%d", iOldPage, iOldIndex,
-              iNewPage, iNewIndex);
-        }
-        c++;
-        if( (d==0 && strlen(zBuf)>35) || strlen(zBuf)>60 ){
-          TRACE(("%s%s\n", d==0?"BALANCE: Cell migration:":"", zBuf));
-          zCsr = zBuf;
-          d = 1;
-        }
-      }
-    }
-    assert( c==nCell );
-    if( zCsr!=zBuf ){
-      TRACE(("%s%s\n", d==0?"BALANCE: Cell migration":"", zBuf));
-    }
-  }
-#endif
-
   /* If there are other cursors that refer to one of the pages involved
   ** in the balancing, then adjust these cursors so that they still
   ** point to the same cells.
@@ -3988,7 +3967,7 @@ static int balance_nonroot(MemPage *pPage){
           iCell += (apCopy[i]->nCell + apCopy[i]->nOverflow + 1);
         }
       }
-      if( pCur->idx>=(nxDiv+nOld) ){
+      if( pCur->idx>=(nxDiv+nOld-1) ){
         TRACE(("BALANCE: Cursor %p migrates from %d,%d to %d,%d\n", 
             pCur, pgno, pCur->idx, pgno, pCur->idx+(nNew-nOld)));
         pCur->idx += (nNew-nOld);
@@ -4001,6 +3980,7 @@ static int balance_nonroot(MemPage *pPage){
       Pgno pgnoNew;
       int x = 0;
 
+      assert( iCell<nCell );
       while( cntNew[x]<=iCell ) x++;
       if( x>0 && !leafData && cntNew[x-1]==iCell ){
         /* The cell that pCur points to is a divider cell in pParent. */
@@ -4019,6 +3999,7 @@ static int balance_nonroot(MemPage *pPage){
       releasePage(pCur->pPage);
       rc = getPage(pBt, pgnoNew, &pCur->pPage);
       assert( rc==SQLITE_OK );
+      assert( pCur->pPage->isInit );
       pCur->info.nSize = 0;
     }
   }
@@ -4175,6 +4156,7 @@ static int balance_shallower(MemPage *pPage){
         /* The child information will fit on the root page, so do the
         ** copy */
         int i;
+        BtCursor *pCur;
         zeroPage(pPage, pChild->aData[0]);
         for(i=0; i<pChild->nCell; i++){
           apCell[i] = findCell(pChild,i);
@@ -4183,12 +4165,26 @@ static int balance_shallower(MemPage *pPage){
         assemblePage(pPage, pChild->nCell, apCell, szCell);
         freePage(pChild);
         TRACE(("BALANCE: child %d transfer to page 1\n", pChild->pgno));
+        /* If there were cursors pointing at this page, point them at the 
+        ** new page instead. Decrement the reference count for the old 
+        ** page and increment it for the new one.
+        */
+        for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
+          if( pCur->pPage==pChild ){
+            TRACE(("BALANCE: Cursor %p migrates from %d,%d to %d,%d\n", 
+                pCur, pPage->pgno, pCur->idx, pPage->pgno, pCur->idx));
+            releasePage(pCur->pPage);
+            rc = getPage(pBt, 1,  &pCur->pPage);
+            assert( rc==SQLITE_OK );
+          }
+        }
       }else{
         /* The child has more information that will fit on the root.
         ** The tree is already balanced.  Do nothing. */
         TRACE(("BALANCE: child %d will not fit on page 1\n", pChild->pgno));
       }
     }else{
+      BtCursor *pCur;
       memcpy(pPage->aData, pChild->aData, pPage->pBt->usableSize);
       pPage->isInit = 0;
       pPage->pParent = 0;
@@ -4197,6 +4193,15 @@ static int balance_shallower(MemPage *pPage){
       freePage(pChild);
       TRACE(("BALANCE: transfer child %d into root %d\n",
               pChild->pgno, pPage->pgno));
+      for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
+        if( pCur->pPage==pChild ){
+          TRACE(("BALANCE: Cursor %p migrates from %d,%d to %d,%d\n", 
+              pCur, pChild->pgno, pCur->idx, pPage->pgno, pCur->idx));
+          releasePage(pCur->pPage);
+          rc = getPage(pBt, pPage->pgno,  &pCur->pPage);
+          assert( rc==SQLITE_OK );
+        }
+      }
     }
     rc = reparentChildPages(pPage);
     if( rc!=SQLITE_OK ) goto end_shallow_balance;
@@ -4300,23 +4305,12 @@ static int balance(MemPage *pPage){
 ** wrFlag==0 then this routine returns SQLITE_LOCKED.  If all
 ** cursors that point to pgnoRoot were opened with wrFlag==1
 ** then this routine returns SQLITE_OK.
-**
-** In addition to checking for read-locks (where a read-lock 
-** means a cursor opened with wrFlag==0) this routine also moves
-** all cursors other than pExclude so that they are pointing to the 
-** first Cell on root page.  This is necessary because an insert 
-** or delete might change the number of cells on a page or delete
-** a page entirely and we do not want to leave any cursors 
-** pointing to non-existant pages or cells.
 */
 static int checkReadLocks(Btree *pBt, Pgno pgnoRoot, BtCursor *pExclude){
   BtCursor *p;
   for(p=pBt->pCursor; p; p=p->pNext){
     if( p->pgnoRoot!=pgnoRoot || p==pExclude ) continue;
     if( p->wrFlag==0 ) return SQLITE_LOCKED;
-    if( p->pPage->pgno!=p->pgnoRoot ){
-/*      moveToRoot(p); */
-    }
   }
   return SQLITE_OK;
 }
@@ -4418,9 +4412,6 @@ int sqlite3BtreeInsert(
   rc = balance(pPage);
   /* sqlite3BtreePageDump(pCur->pBt, pCur->pgnoRoot, 1); */
   /* fflush(stdout); */
-  if( rc==SQLITE_OK ){
-  /*  moveToRoot(pCur); */
-  }
 end_insert:
   sqliteFree(newCell);
   return rc;
@@ -4436,6 +4427,8 @@ int sqlite3BtreeDelete(BtCursor *pCur){
   int rc;
   Pgno pgnoChild = 0;
   Btree *pBt = pCur->pBt;
+  int idx;                /* Index of the cell to delete */
+  BtCursor *pCur2;        /* Iterator variable for the pBt.pCursor link-list */
 
   assert( pPage->isInit );
   if( pCur->status ){
@@ -4458,11 +4451,50 @@ int sqlite3BtreeDelete(BtCursor *pCur){
   rc = sqlite3pager_write(pPage->aData);
   if( rc ) return rc;
 
+  /* Set index to the index in pPage that contains the cell to delete. Also
+  ** increment the reference count for pPage. This allows us to move the
+  ** cursor pCur before the delete takes place.
+  */
+  idx = pCur->idx;
+  rc = getPage(pBt, pPage->pgno, &pPage);
+  if( rc ) return rc;
+  assert( pPage==pCur->pPage );
+
+  /* If there are any cursors that point to the cell being deleted, 
+  ** move them to the next or previous entry in the table. It is preferable
+  ** to move the cursor to the 'next' location, rather than the 'previous'
+  ** one, as most table scans are done in the forward direction (also, code
+  ** below depends on this). If neither entry exists, declare the cursor
+  ** invalid.
+  */ 
+  for(pCur2=pBt->pCursor; pCur2; pCur2 = pCur2->pNext){
+    if( pCur2->pPage==pPage && pCur2->idx==idx && pCur2->isValid ){
+      int res;
+      pCur2->delShift = 0;
+      rc = sqlite3BtreeNext(pCur2, &res);
+      if( rc ) goto delete_out;
+      if( res ){
+        /* If the next tree entry cannot be found, then the cursor must
+        ** already point to the last table entry. So point it to the
+        ** second last by calling BtreeLast(), BtreePrevious().
+        */
+        rc = sqlite3BtreeLast(pCur2, &res);
+        if( rc ) goto delete_out;
+        assert( res==0 );
+        rc = sqlite3BtreePrevious(pCur2, &res);
+        if( rc ) goto delete_out;
+        pCur2->delShift = -1;
+      }else{
+        pCur2->delShift = 1;
+      }
+    }
+  }
+
   /* Locate the cell within it's page and leave pCell pointing to the
   ** data. The clearCell() call frees any overflow pages associated with the
   ** cell. The cell itself is still intact.
   */
-  pCell = findCell(pPage, pCur->idx);
+  pCell = findCell(pPage, idx);
   if( !pPage->leaf ){
     pgnoChild = get4byte(pCell);
   }
@@ -4474,7 +4506,8 @@ int sqlite3BtreeDelete(BtCursor *pCur){
     ** do something we will leave a hole on an internal page.
     ** We have to fill the hole by moving in a cell from a leaf.  The
     ** next Cell after the one to be deleted is guaranteed to exist and
-    ** to be a leaf so we can use it.
+    ** to be a leaf so we can use it. Conveniantly, pCur now points
+    ** at this cell (because it was advanced above).
     */
     BtCursor leafCur;
     unsigned char *pNext;
@@ -4482,42 +4515,104 @@ int sqlite3BtreeDelete(BtCursor *pCur){
     int notUsed;
     unsigned char *tempCell;
     assert( !pPage->leafData );
+
+    /* Make a copy of *pCur in leafCur. leafCur now points to the cell 
+    ** that will be moved into the space left by the cell being deleted.
+    */
+    assert( pCur->delShift==1 );
+    assert( pCur->isValid );
     getTempCursor(pCur, &leafCur);
-    rc = sqlite3BtreeNext(&leafCur, &notUsed);
     if( rc!=SQLITE_OK ){
       if( rc!=SQLITE_NOMEM ){
         rc = SQLITE_CORRUPT;  /* bkpt-CORRUPT */
       }
-      return rc;
+      goto delete_out;
     }
     rc = sqlite3pager_write(leafCur.pPage->aData);
-    if( rc ) return rc;
+    if( rc ) goto delete_out;
     TRACE(("DELETE: table=%d delete internal from %d replace from leaf %d\n",
        pCur->pgnoRoot, pPage->pgno, leafCur.pPage->pgno));
-    dropCell(pPage, pCur->idx, cellSizePtr(pPage, pCell));
+
+    /* Drop the cell from the internal page. Make a copy of the cell from
+    ** the leaf page into memory obtained from malloc(). Insert it into
+    ** the internal page, at the position vacated by the delete. There
+    ** are now two copies of the leaf-cell in the tree.
+    */
+    dropCell(pPage, idx, cellSizePtr(pPage, pCell));
     pNext = findCell(leafCur.pPage, leafCur.idx);
     szNext = cellSizePtr(leafCur.pPage, pNext);
     assert( MX_CELL_SIZE(pBt)>=szNext+4 );
     tempCell = sqliteMallocRaw( MX_CELL_SIZE(pBt) );
-    if( tempCell==0 ) return SQLITE_NOMEM;
-    rc = insertCell(pPage, pCur->idx, pNext-4, szNext+4, tempCell);
-    if( rc!=SQLITE_OK ) return rc;
-    put4byte(findOverflowCell(pPage, pCur->idx), pgnoChild);
-    pCur->isValid = 0;
+    if( tempCell==0 ){
+       rc = SQLITE_NOMEM;
+       goto delete_out;
+    }
+    rc = insertCell(pPage, idx, pNext-4, szNext+4, tempCell);
+    if( rc!=SQLITE_OK ) goto delete_out;
+    put4byte(findOverflowCell(pPage, idx), pgnoChild);
+    pPage->idxShift = 0;
+
+    /* If there are any cursors that point to the leaf-cell, move them
+    ** so that they point at internal cell. This is easiest done by
+    ** calling BtreePrevious().
+    */
+    for(pCur2=pBt->pCursor; pCur2; pCur2 = pCur2->pNext){
+      if( pCur2->pPage==leafCur.pPage && pCur2->idx==leafCur.idx ){
+        int res;
+        int delShiftSave = pCur2->delShift; 
+        assert( leafCur.idx==0 );
+        pCur2->delShift = 0;
+        rc = sqlite3BtreePrevious(pCur2, &res);
+        if( rc ) goto delete_out;
+        assert( res==0 );
+        assert( pCur2->pPage==pPage );
+        assert( pCur2->idx==idx );
+        pCur2->delShift = delShiftSave;
+      }
+    }
+
+    /* Balance the internal page. Free the memory allocated for the 
+    ** copy of the leaf cell. Then delete the cell from the leaf page.
+    */
     rc = balance(pPage);
     sqliteFree(tempCell);
-    if( rc ) return rc;
+    if( rc ) goto delete_out;
     dropCell(leafCur.pPage, leafCur.idx, szNext);
+
+    for(pCur2=pBt->pCursor; pCur2; pCur2 = pCur2->pNext){
+      if( pCur2->pPage==leafCur.pPage && pCur2->idx>leafCur.idx ){
+        TRACE(("DELETE: Cursor %p migrates from %d,%d to %d,%d\n", 
+            pCur2, pPage->pgno, pCur2->idx, pPage->pgno, pCur2->idx-1));
+        pCur2->idx--;
+        pCur2->info.nSize = 0;
+      }
+    }
+
     rc = balance(leafCur.pPage);
     releaseTempCursor(&leafCur);
   }else{
-    TRACE(("DELETE: table=%d delete from leaf %d\n",
-       pCur->pgnoRoot, pPage->pgno));
-    dropCell(pPage, pCur->idx, cellSizePtr(pPage, pCell));
-    pCur->isValid = 0;
+    TRACE(("DELETE: table=%d delete %d from leaf %d\n",
+       pCur->pgnoRoot, idx, pPage->pgno));
+    dropCell(pPage, idx, cellSizePtr(pPage, pCell));
+
+    /* If there were cursors pointing to cells on pPage with index values
+    ** greater than idx, decrement the index values now.
+    */
+    for(pCur2=pBt->pCursor; pCur2; pCur2 = pCur2->pNext){
+      assert( !pCur2->isValid || pCur2->pPage!=pPage || pCur2->idx!=idx );
+      if( pCur2->pPage==pPage && pCur2->idx>idx ){
+        TRACE(("DELETE: Cursor %p migrates from %d,%d to %d,%d\n", 
+            pCur2, pPage->pgno, pCur2->idx, pPage->pgno, pCur2->idx-1));
+        pCur2->idx--;
+        pCur2->info.nSize = 0;
+      }
+    }
+
     rc = balance(pPage);
   }
-  moveToRoot(pCur);
+
+delete_out:
+  releasePage(pPage);
   return rc;
 }
 
index 33a404950585560387c05a2e07ab80cfa550a1c9..e45569a5b42090fc51e3c9d56a6aabfde352973e 100644 (file)
@@ -9,9 +9,11 @@
 #
 #***********************************************************************
 # This file implements regression tests for SQLite library.  The
-# focus of this script is btree database backend.
+# focus of this script is btree database backend. Specifically,
+# this file tests that existing cursors are correctly repositioned 
+# when entries are inserted into or deleted from btrees.
 #
-# $Id: btree8.test,v 1.1 2004/11/13 13:19:56 danielk1977 Exp $
+# $Id: btree8.test,v 1.2 2004/11/16 04:57:25 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -55,13 +57,12 @@ foreach key $keys {
 }
 btree_commit $::bt
 
-# set btree_trace 1
-
 # Now write more entries to the table (and overwriting the ones that exist).
 # After each write, check that the cursors created above still point to the
 # same entries.
 btree_begin_transaction $::bt
 set ::write_csr [btree_cursor $::bt $::tnum 1]
+set first_entry $testnum
 for {set i $testnum} {$i < 5000 && $nErr==0 } {incr i} {
   set datalen [expr int(rand()*20.0)]
 
@@ -77,9 +78,34 @@ for {set i $testnum} {$i < 5000 && $nErr==0 } {incr i} {
     } $key
   }
 }
+
+# Now delete entries from the table.
+btree_first $::write_csr
+for {set i $first_entry} {$i < 5000 && $nErr==0 } {incr i} {
+
+  do_test btree8-2.$i.1 {
+    btree_key $::write_csr
+  } $i
+  do_test btree8-2.$i.2 {
+    btree_delete $::write_csr
+    btree_next $::write_csr
+    expr 0
+  } {0}
+  set testnum 2
+  foreach csr $csr_list key $keys {
+    incr testnum
+    if {$key <= $i } {
+      set key 0
+    }
+    do_test btree8-2.$i.$testnum {
+      btree_key $::csr
+    } $key
+  }
+}
+
 btree_close_cursor $::write_csr
 btree_commit $::bt
-if {$::nErr>0} { puts $::csr_list }
+if {$::nErr>0} { puts $::csr_list ; exit }
 foreach csr $csr_list {
   btree_close_cursor $csr
 }
@@ -92,6 +118,7 @@ set csr_list [list]
 # [num_to_string 456 10] -> "456.456.45"
 #
 proc num_to_string {num len} {
+  set num [format %.4d $num]
   return [string range [string repeat "$num." $len] 0 [expr $len-1]]
 }
 
@@ -108,7 +135,7 @@ btree_begin_transaction $::bt
 set testnum 0
 foreach key $skeys {
   incr testnum 
-  do_test btree-8-2.$testnum {
+  do_test btree-8-3.$testnum {
     set csr [btree_cursor $::bt $::inum 1]
     btree_insert $csr $key ""
     lappend csr_list $csr
@@ -124,21 +151,73 @@ btree_commit $::bt
 # same entries.
 btree_begin_transaction $::bt
 set ::write_csr [btree_cursor $::bt $::inum 1]
+set first_entry $testnum
 for {set i $testnum} {$i < 5000 && $nErr==0 } {incr i} {
   set skey [num_to_string $i 20]
 
-  do_test btree8-2.$i.1 {
+  do_test btree-8-3.$i.1 {
     btree_insert $::write_csr $skey ""
   } {}
 
   set testnum 1
   foreach csr $csr_list key $skeys {
     incr testnum
-    do_test btree8-2.$i.$testnum {
+    do_test btree-8-3.$i.$testnum {
       btree_key $::csr
     } $key
   }
 }
+btree_commit $::bt
+btree_begin_transaction $::bt
+
+proc lremove {l key} {
+  set idx [lsearch $l $key]
+  return [concat [lrange $l 0 [expr $idx-1]] [lrange $l [expr $idx+1] end]]
+}
+proc K {x y} {set x}
+proc lshuffle { list } {
+    set n [llength $list]
+    while {$n>0} {
+        set j [expr {int(rand()*$n)}]
+        lappend slist [lindex $list $j]
+        set list [lreplace [K $list [set list {}]] $j $j]
+        incr n -1
+    }
+    return $slist
+}
+
+# Now delete entries from the index. Do this in a random order, to try to
+# ensure that internal and external nodes are deleted.
+for {set i $first_entry} {$i < 5000} {incr i} {
+  lappend delete_order $i
+}
+set delete_order [lshuffle $delete_order]
+
+btree_first $::write_csr
+foreach i $delete_order { 
+  do_test btree8-4.$i.1 {
+    btree_move_to $::write_csr [num_to_string $i 20]
+    btree_key $::write_csr
+  } [num_to_string $i 20]
+  do_test btree8-4.$i.2 {
+    btree_delete $::write_csr
+  } {}
+
+  set delete_order [lremove $delete_order $i]
+  set testnum 2
+  foreach csr $csr_list key $keys {
+    incr testnum
+    if { [lsearch $delete_order $key]==-1 } {
+      set skey ""
+    } else {
+      set skey [num_to_string $key 20]
+    }
+    do_test btree8-4.$i.$testnum {
+      btree_key $::csr
+    } $skey
+  }
+}
+
 btree_close_cursor $::write_csr
 btree_commit $::bt
 if {$::nErr>0} { puts $::csr_list }