** May you share freely, never taking more than you give.
**
*************************************************************************
-** $Id: btree.c,v 1.116 2004/05/08 20:07:40 drh Exp $
+** $Id: btree.c,v 1.117 2004/05/09 00:40:52 drh Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
u8 leaf; /* True if leaf flag is set */
u8 zeroData; /* True if zero data flag is set */
u8 hdrOffset; /* 100 for page 1. 0 otherwise */
+ u8 needRelink; /* True if need to run relinkCellList() */
int idxParent; /* Index in pParent->aCell[] of this node */
int nFree; /* Number of free bytes on the page */
int nCell; /* Number of entries on this page */
return n + nPayload;
}
+/*
+** Do sanity checking on a page. Throw an exception if anything is
+** not right.
+**
+** This routine is used for internal error checking only. It is omitted
+** from most builds.
+*/
+#if defined(BTREE_DEBUG) && !defined(NDEBUG) && 0
+static void _pageIntegrity(MemPage *pPage){
+ int pageSize;
+ u8 *data;
+ int i, idx, c, pc, hdr, nFree;
+ u8 used[MX_PAGE_SIZE];
+
+ pageSize = pPage->pBt->pageSize;
+ assert( pPage->aData==&((unsigned char*)pPage)[-pageSize] );
+ hdr = pPage->hdrOffset;
+ assert( hdr==(pPage->pgno==1 ? 100 : 0) );
+ assert( pPage->pgno==sqlite3pager_pagenumber(pPage->aData) );
+ c = pPage->aData[hdr];
+ if( pPage->isInit ){
+ assert( pPage->leaf == ((c & PTF_LEAF)!=0) );
+ assert( pPage->zeroData == ((c & PTF_ZERODATA)!=0) );
+ assert( pPage->intKey == ((c & PTF_INTKEY)!=0) );
+ }
+ data = pPage->aData;
+ memset(used, 0, pageSize);
+ for(i=0; i<hdr+10-pPage->leaf*4; i++) used[i] = 1;
+ nFree = 0;
+ pc = get2byte(&data[hdr+1]);
+ while( pc ){
+ int size;
+ assert( pc>0 && pc<pageSize-4 );
+ size = get2byte(&data[pc+2]);
+ assert( pc+size<=pageSize );
+ nFree += size;
+ for(i=pc; i<pc+size; i++){
+ assert( used[i]==0 );
+ used[i] = 1;
+ }
+ pc = get2byte(&data[pc]);
+ }
+ assert( pPage->isInit==0 || pPage->nFree==nFree+data[hdr+5] );
+ idx = 0;
+ pc = get2byte(&data[hdr+3]);
+ while( pc ){
+ int size;
+ assert( pPage->isInit==0 || idx<pPage->nCell );
+ assert( pc>0 && pc<pageSize-4 );
+ assert( pPage->isInit==0 || pPage->aCell[idx]==&data[pc] );
+ size = cellSize(pPage, &data[pc]);
+ assert( pc+size<=pageSize );
+ for(i=pc; i<pc+size; i++){
+ assert( used[i]==0 );
+ used[i] = 1;
+ }
+ pc = get2byte(&data[pc]);
+ idx++;
+ }
+ assert( idx==pPage->nCell );
+ nFree = 0;
+ for(i=0; i<pageSize; i++){
+ assert( used[i]<=1 );
+ if( used[i]==0 ) nFree++;
+ }
+ assert( nFree==data[hdr+5] );
+}
+#define pageIntegrity(X) _pageIntegrity(X)
+#else
+# define pageIntegrity(X)
+#endif
+
/*
** Defragment the page given. All Cells are moved to the
** beginning of the page and all free space is collected
assert( sqlite3pager_iswriteable(pPage->aData) );
assert( pPage->pBt!=0 );
assert( pPage->pBt->pageSize <= MX_PAGE_SIZE );
+ assert( !pPage->needRelink );
+ assert( !pPage->isOverfull );
oldPage = pPage->aData;
hdr = pPage->hdrOffset;
addr = 3+hdr;
size = cellSize(pPage, &oldPage[pc]);
memcpy(&newPage[n], &oldPage[pc], size);
put2byte(&newPage[addr],n);
+ assert( pPage->aCell[i]==&oldPage[pc] );
pPage->aCell[i++] = &oldPage[n];
+ addr = n;
n += size;
- addr = pc;
pc = get2byte(&oldPage[pc]);
}
assert( i==pPage->nCell );
pPage->zeroData = (c & PTF_ZERODATA)!=0;
pPage->leaf = (c & PTF_LEAF)!=0;
pPage->isOverfull = 0;
+ pPage->needRelink = 0;
pPage->idxShift = 0;
pageSize = pPage->pBt->pageSize;
}
pPage->isInit = 1;
+ pageIntegrity(pPage);
return SQLITE_OK;
}
pPage->zeroData = (flags & PTF_ZERODATA)!=0;
pPage->hdrOffset = hdr;
pPage->isOverfull = 0;
+ pPage->needRelink = 0;
pPage->idxShift = 0;
pPage->isInit = 1;
+ pageIntegrity(pPage);
}
/*
*/
static void pageDestructor(void *pData){
MemPage *pPage = (MemPage*)&((char*)pData)[SQLITE_PAGE_SIZE];
+ assert( pPage->isInit==0 || pPage->needRelink==0 );
if( pPage->pParent ){
MemPage *pParent = pPage->pParent;
pPage->pParent = 0;
BtCursor *pCur;
for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){
MemPage *pPage = pCur->pPage;
- if( pPage && !pPage->isInit ){
+ if( pPage /* && !pPage->isInit */ ){
+ pageIntegrity(pPage);
releasePage(pPage);
pCur->pPage = 0;
pCur->isValid = 0;
*pSize = 0;
}else{
pPage = pCur->pPage;
+ pageIntegrity(pPage);
assert( pPage!=0 );
assert( pCur->idx>=0 && pCur->idx<pPage->nCell );
cell = pPage->aCell[pCur->idx];
assert( pCur->isValid );
pBt = pCur->pBt;
pPage = pCur->pPage;
+ pageIntegrity(pPage);
assert( pCur->idx>=0 && pCur->idx<pPage->nCell );
aPayload = pPage->aCell[pCur->idx];
aPayload += 2; /* Skip the next cell index */
assert( pCur->idx>=0 && pCur->idx<pCur->pPage->nCell );
pBt = pCur->pBt;
pPage = pCur->pPage;
+ pageIntegrity(pPage);
assert( pCur->idx>=0 && pCur->idx<pPage->nCell );
assert( pPage->intKey==0 );
aPayload = pPage->aCell[pCur->idx];
pPage = pCur->pPage;
assert( pPage!=0 );
assert( pPage->isInit );
+ pageIntegrity(pPage);
if( pPage->zeroData ){
*pSize = 0;
}else{
assert( pCur->isValid );
rc = getAndInitPage(pBt, newPgno, &pNewPage, pCur->pPage);
if( rc ) return rc;
+ pageIntegrity(pNewPage);
pNewPage->idxParent = pCur->idx;
pOldPage = pCur->pPage;
pOldPage->idxShift = 0;
pPage = pCur->pPage;
assert( pPage!=0 );
assert( !isRootPage(pPage) );
+ pageIntegrity(pPage);
pParent = pPage->pParent;
assert( pParent!=0 );
+ pageIntegrity(pParent);
idxParent = pPage->idxParent;
sqlite3pager_ref(pParent->aData);
oldPgno = pPage->pgno;
return rc;
}
releasePage(pCur->pPage);
+ pageIntegrity(pRoot);
pCur->pPage = pRoot;
pCur->idx = 0;
if( pRoot->nCell==0 && !pRoot->leaf ){
int c = -1; /* pRes return if table is empty must be -1 */
lwr = 0;
upr = pPage->nCell-1;
+ pageIntegrity(pPage);
while( lwr<=upr ){
void *pCellKey;
u64 nCellKey;
if( pgno==0 ) return;
assert( pBt->pPager!=0 );
aData = sqlite3pager_lookup(pBt->pPager, pgno);
- pThis = (MemPage*)&aData[pBt->pageSize];
- if( pThis && pThis->isInit ){
- if( pThis->pParent!=pNewParent ){
- if( pThis->pParent ) sqlite3pager_unref(pThis->pParent->aData);
- pThis->pParent = pNewParent;
- if( pNewParent ) sqlite3pager_ref(pNewParent->aData);
- }
- pThis->idxParent = idx;
+ if( aData ){
+ pThis = (MemPage*)&aData[pBt->pageSize];
+ if( pThis->isInit ){
+ if( pThis->pParent!=pNewParent ){
+ if( pThis->pParent ) sqlite3pager_unref(pThis->pParent->aData);
+ pThis->pParent = pNewParent;
+ if( pNewParent ) sqlite3pager_ref(pNewParent->aData);
+ }
+ pThis->idxParent = idx;
+ }
sqlite3pager_unref(aData);
}
}
**
** "sz" must be the number of bytes in the cell.
**
-** Do not bother maintaining the integrity of the linked list of Cells.
-** Only the pPage->aCell[] array is important. The relinkCellList()
-** routine will be called soon after this routine in order to rebuild
-** the linked list.
+** Try to maintain the integrity of the linked list of cells. But if
+** the cell being inserted does not fit on the page, this will not be
+** possible. If the linked list is not maintained, then just update
+** pPage->aCell[] and set the pPage->needRelink flag so that we will
+** know to rebuild the linked list later.
*/
static void dropCell(MemPage *pPage, int idx, int sz){
int j, pc;
+ u8 *data;
assert( idx>=0 && idx<pPage->nCell );
assert( sz==cellSize(pPage, pPage->aCell[idx]) );
assert( sqlite3pager_iswriteable(pPage->aData) );
assert( pPage->aCell[idx]>=pPage->aData );
assert( pPage->aCell[idx]<&pPage->aData[pPage->pBt->pageSize-sz] );
- pc = Addr(pPage->aCell[idx]) - Addr(pPage->aData);
+ data = pPage->aData;
+ pc = Addr(pPage->aCell[idx]) - Addr(data);
assert( pc>pPage->hdrOffset && pc+sz<=pPage->pBt->pageSize );
freeSpace(pPage, pc, sz);
for(j=idx; j<pPage->nCell-1; j++){
pPage->aCell[j] = pPage->aCell[j+1];
}
pPage->nCell--;
+ if( !pPage->isOverfull && !pPage->needRelink ){
+ u8 *pPrev;
+ if( idx==0 ){
+ pPrev = &data[pPage->hdrOffset+3];
+ }else{
+ pPrev = pPage->aCell[idx-1];
+ }
+ if( idx<pPage->nCell ){
+ pc = Addr(pPage->aCell[idx]) - Addr(data);
+ }else{
+ pc = 0;
+ }
+ put2byte(pPrev, pc);
+ pageIntegrity(pPage);
+ }else{
+ pPage->needRelink = 1;
+ }
pPage->idxShift = 1;
}
** will not fit, then just make pPage->aCell[i] point to the content
** and set pPage->isOverfull.
**
-** Do not bother maintaining the integrity of the linked list of Cells.
-** Only the pPage->aCell[] array is important. The relinkCellList()
-** routine will be called soon after this routine in order to rebuild
-** the linked list.
+** Try to maintain the integrity of the linked list of cells. But if
+** the cell being inserted does not fit on the page, this will not be
+** possible. If the linked list is not maintained, then just update
+** pPage->aCell[] and set the pPage->needRelink flag so that we will
+** know to rebuild the linked list later.
*/
static void insertCell(MemPage *pPage, int i, unsigned char *pCell, int sz){
int idx, j;
assert( i>=0 && i<=pPage->nCell );
assert( sz==cellSize(pPage, pCell) );
assert( sqlite3pager_iswriteable(pPage->aData) );
- idx = allocateSpace(pPage, sz);
+ idx = pPage->needRelink ? 0 : allocateSpace(pPage, sz);
resizeCellArray(pPage, pPage->nCell+1);
for(j=pPage->nCell; j>i; j--){
pPage->aCell[j] = pPage->aCell[j-1];
pPage->isOverfull = 1;
pPage->aCell[i] = pCell;
}else{
- memcpy(&pPage->aData[idx], pCell, sz);
- pPage->aCell[i] = &pPage->aData[idx];
+ u8 *data = pPage->aData;
+ memcpy(&data[idx], pCell, sz);
+ pPage->aCell[i] = &data[idx];
+ }
+ if( !pPage->isOverfull && !pPage->needRelink ){
+ u8 *pPrev;
+ int pc;
+ if( i==0 ){
+ pPrev = &pPage->aData[pPage->hdrOffset+3];
+ }else{
+ pPrev = pPage->aCell[i-1];
+ }
+ pc = get2byte(pPrev);
+ put2byte(pPrev, idx);
+ put2byte(pPage->aCell[i], pc);
+ pageIntegrity(pPage);
+ }else{
+ pPage->needRelink = 1;
}
pPage->idxShift = 1;
}
static void relinkCellList(MemPage *pPage){
int i, idxFrom;
assert( sqlite3pager_iswriteable(pPage->aData) );
+ if( !pPage->needRelink ) return;
idxFrom = pPage->hdrOffset+3;
for(i=0; i<pPage->nCell; i++){
int idx = Addr(pPage->aCell[i]) - Addr(pPage->aData);
idxFrom = idx;
}
put2byte(&pPage->aData[idxFrom], 0);
+ pPage->needRelink = 0;
}
/*
}
}
+/*
+** For debugging...
+*/
+#if 1
+# define TRACE(X) if( pager3_refinfo_enable ) printf X
+#else
+# define TRACE(X)
+#endif
+
/*
** The following parameters determine how many adjacent pages get involved
** in a balancing operation. NN is the number of neighbors on either side
** it means this page is the root page and special rules apply.
*/
pParent = pPage->pParent;
+ TRACE(("BALANCE: begin page %d\n", pPage->pgno));
if( pParent==0 ){
Pgno pgnoChild;
MemPage *pChild;
if( pPage->leaf ){
/* The table is completely empty */
relinkCellList(pPage);
+ TRACE(("BALANCE: empty table\n"));
}else{
/* The root page is empty but has one child. Transfer the
** information from that one child into the root page if it
cellSize(pChild, pChild->aCell[i]));
}
freePage(pChild);
+ TRACE(("BALANCE: child %d transfer to page 1\n", pChild->pgno));
}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{
memcpy(pPage, pChild, pBt->pageSize);
rc = initPage(pPage, 0);
assert( rc==SQLITE_OK );
freePage(pChild);
+ TRACE(("BALANCE: transfer child %d into root\n", pChild->pgno));
}
reparentChildPages(pPage);
releasePage(pChild);
/* It is OK for the root page to be less than half full.
*/
relinkCellList(pPage);
+ TRACE(("BALANCE: Root page is underfull but that is ok\n"));
return SQLITE_OK;
}
/*
pParent = pPage;
pPage = pChild;
extraUnref = pChild;
+ TRACE(("BALANCE: Copy root into %d and blance\n", pPage->pgno));
}
rc = sqlite3pager_write(pParent->aData);
if( rc ) return rc;
*/
assert( pPage->isInit );
assert( pParent->isInit );
+ pageIntegrity(pPage);
rc = balance(pParent);
}
releasePage(pParent);
releasePage(extraUnref);
+ TRACE(("BALANCE: Finished with %d\n", pPage->pgno));
return rc;
}
pPage->intKey = (c & PTF_INTKEY)!=0;
pPage->zeroData = (c & PTF_ZERODATA)!=0;
pPage->leaf = (c & PTF_LEAF)!=0;
- printf("PAGE %d: flags=0x%02x frag=%d\n", pgno,
- data[hdr], data[hdr+5]);
+ printf("PAGE %d: flags=0x%02x frag=%d parent=%d\n", pgno,
+ data[hdr], data[hdr+5],
+ (pPage->isInit && pPage->pParent) ? pPage->pParent->pgno : 0);
i = 0;
assert( hdr == (pgno==1 ? 100 : 0) );
idx = get2byte(&data[hdr+3]);
**
** This routine is used for testing and debugging only.
*/
-int sqlite3BtreeCursorDump(BtCursor *pCur, int *aResult){
+int sqlite3BtreeCursorInfo(BtCursor *pCur, int *aResult){
int cnt, idx;
MemPage *pPage = pCur->pPage;
+
+ pageIntegrity(pPage);
assert( pPage->isInit );
aResult[0] = sqlite3pager_pagenumber(pPage->aData);
assert( aResult[0]==pPage->pgno );
# This file implements regression tests for SQLite library. The
# focus of this script is btree database backend
#
-# $Id: btree.test,v 1.20 2004/05/08 20:07:40 drh Exp $
+# $Id: btree.test,v 1.21 2004/05/09 00:40:52 drh Exp $
set testdir [file dirname $argv0]
set data [format %5d $i]
btree_insert $::c1 $key $data
}
- lrange [btree_cursor_dump $::c1] 4 5
+ lrange [btree_cursor_info $::c1] 4 5
} {8 1}
do_test btree-7.3 {
for {set i 1001} {$i<1091} {incr i 2} {
}
# Freed 45 blocks. Total freespace is 458
# Keys remaining are even numbers between 1000 and 1090, inclusive
- lrange [btree_cursor_dump $::c1] 4 5
+ lrange [btree_cursor_info $::c1] 4 5
} {458 46}
-#btree_page_dump $::b1 2
+#btree_tree_dump $::b1 1
do_test btree-7.4 {
# The largest free block is 10 bytes long. So if we insert
# a record bigger than 10 bytes it should force a defrag
btree_key $::c1
} {2000}
do_test btree-7.5 {
- lrange [btree_cursor_dump $::c1] 4 5
+ lrange [btree_cursor_info $::c1] 4 5
} {438 1}
-#btree_page_dump $::b1 2
+#btree_tree_dump $::b1 1
# Delete an entry to make a hole of a known size, then immediately recreate
# that entry. This tests the path into allocateSpace where the hole exactly
btree_delete $::c1
} {}
do_test btree-7.7 {
- lrange [btree_cursor_dump $::c1] 4 5
+ lrange [btree_cursor_info $::c1] 4 5
} {458 3} ;# Create two new holes of 10 bytes each
#btree_page_dump $::b1 2
do_test btree-7.8 {
btree_insert $::c1 1006 { 1006}
- lrange [btree_cursor_dump $::c1] 4 5
+ lrange [btree_cursor_info $::c1] 4 5
} {448 2} ;# Filled in the first hole
#btree_page_dump $::b1 2
do_test btree-7.9 {
btree_move_to $::c1 1012
btree_delete $::c1
- lrange [btree_cursor_dump $::c1] 4 5
+ lrange [btree_cursor_info $::c1] 4 5
} {458 2} ;# Coalesce with the whole before
do_test btree-7.10 {
btree_move_to $::c1 1008
btree_delete $::c1
- lrange [btree_cursor_dump $::c1] 4 5
+ lrange [btree_cursor_info $::c1] 4 5
} {468 2} ;# Coalesce with whole after
do_test btree-7.11 {
btree_move_to $::c1 1030
btree_delete $::c1
- lrange [btree_cursor_dump $::c1] 4 5
+ lrange [btree_cursor_info $::c1] 4 5
} {478 3} ;# Make a new hole
do_test btree-7.13 {
btree_move_to $::c1 1034
btree_delete $::c1
- lrange [btree_cursor_dump $::c1] 4 5
+ lrange [btree_cursor_info $::c1] 4 5
} {488 4} ;# Make another hole
do_test btree-7.14 {
btree_move_to $::c1 1032
btree_delete $::c1
- lrange [btree_cursor_dump $::c1] 4 5
+ lrange [btree_cursor_info $::c1] 4 5
} {498 3} ;# The freed space should coalesce on both ends
#btree_page_dump $::b1 2
do_test btree-7.15 {
# Check to see that data on overflow pages work correctly.
#
+#btree_page_dump $::b1 1
do_test btree-8.1 {
set data "*** This is a very long key "
while {[string length $data]<1234} {append data $data}
set ::data $data
btree_insert $::c1 2020 $data
} {}
-#btree_page_dump $::b1 2
+#btree_page_dump $::b1 1
do_test btree-8.1.1 {
lindex [btree_pager_stats $::b1] 1
} {1}
btree_insert $::c1 020 {*** 020 *** 020 *** 020 *** 020 ***}
select_keys $::c1
} {001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020}
-#btree_page_dump $::b1 5
#btree_page_dump $::b1 2
-#btree_page_dump $::b1 7
#btree_pager_ref_dump $::b1
#set pager_refinfo_enable 0
#
catch {unset ::data}
catch {unset ::key}
-for {set i 31} {$i<=1000} {incr i} {
-if {$i==88} {
-set pager_refinfo_enable 1
-btree_tree_dump $b1 2
-btree_pager_ref_dump $b1
-btree_cursor_list $b1
-}
+for {set i 31} {$i<=999} {incr i} {
do_test btree-11.1.$i.1 {
set key [format %03d $i]
set ::data "*** $key *** $key *** $key *** $key ***"
btree_move_to $::c1 $key
btree_key $::c1
} [format %03d $i]
-if {$i==88} {
-btree_pager_ref_dump $b1
-btree_cursor_list $b1
-btree_tree_dump $b1 2
-exit
-}
do_test btree-11.1.$i.2 {
btree_data $::c1
} $::data
# Delete the dividers on the root page
#
do_test btree-11.4 {
- btree_move_to $::c1 257
- btree_delete $::c1
- btree_next $::c1
- btree_key $::c1
-} {258}
-do_test btree-11.4.1 {
- btree_move_to $::c1 256
- btree_key $::c1
-} {256}
-do_test btree-11.4.2 {
- btree_move_to $::c1 258
- btree_key $::c1
-} {258}
-do_test btree-11.4.3 {
- btree_move_to $::c1 259
- btree_key $::c1
-} {259}
-do_test btree-11.4.4 {
- btree_move_to $::c1 257
- set n [btree_key $::c1]
- expr {$n==256||$n==258}
-} {1}
-do_test btree-11.5 {
- btree_move_to $::c1 513
- btree_delete $::c1
- btree_next $::c1
- btree_key $::c1
-} {514}
-do_test btree-11.5.1 {
- btree_move_to $::c1 512
- btree_key $::c1
-} {512}
-do_test btree-11.5.2 {
- btree_move_to $::c1 514
- btree_key $::c1
-} {514}
-do_test btree-11.5.3 {
- btree_move_to $::c1 515
- btree_key $::c1
-} {515}
-do_test btree-11.5.4 {
- btree_move_to $::c1 513
- set n [btree_key $::c1]
- expr {$n==512||$n==514}
-} {1}
-do_test btree-11.6 {
- btree_move_to $::c1 769
+ btree_move_to $::c1 551
btree_delete $::c1
- btree_next $::c1
- btree_key $::c1
-} {770}
-do_test btree-11.6.1 {
- btree_move_to $::c1 768
- btree_key $::c1
-} {768}
-do_test btree-11.6.2 {
- btree_move_to $::c1 771
- btree_key $::c1
-} {771}
-do_test btree-11.6.3 {
- btree_move_to $::c1 770
+ btree_move_to $::c1 551
+ set k [btree_key $::c1]
+ if {$k==550} {
+ set k [btree_next $::c1]
+ }
btree_key $::c1
-} {770}
-do_test btree-11.6.4 {
- btree_move_to $::c1 769
- set n [btree_key $::c1]
- expr {$n==768||$n==770}
-} {1}
-#btree_page_dump $::b1 2
-#btree_page_dump $::b1 25
+} {552}
# Change the data on an intermediate node such that the node becomes overfull
# and has to split. We happen to know that intermediate nodes exist on
append ::data $::data
do_test btree-12.1 {
btree_insert $::c1 337 $::data
+ btree_move_to $::c1 337
btree_data $::c1
} $::data
do_test btree-12.2 {
btree_insert $::c1 401 $::data
+ btree_move_to $::c1 401
btree_data $::c1
} $::data
do_test btree-12.3 {
btree_insert $::c1 465 $::data
+ btree_move_to $::c1 465
btree_data $::c1
} $::data
do_test btree-12.4 {
btree_next $::c1
btree_key $::c1
} {402}
-do_test btree-13.1 {
- btree_integrity_check $::b1 2 3
-} {}
+#do_test btree-13.1 {
+# btree_integrity_check $::b1 1 2
+#} {}
# To Do:
#