aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/btree.c148
-rw-r--r--src/btree.h6
-rw-r--r--src/pager.c52
-rw-r--r--src/pager.h3
-rw-r--r--src/test3.c37
5 files changed, 194 insertions, 52 deletions
diff --git a/src/btree.c b/src/btree.c
index ada01d974..46d2332a1 100644
--- a/src/btree.c
+++ b/src/btree.c
@@ -21,7 +21,7 @@
** http://www.hwaci.com/drh/
**
*************************************************************************
-** $Id: btree.c,v 1.19 2001/07/01 22:12:01 drh Exp $
+** $Id: btree.c,v 1.20 2001/07/02 17:51:46 drh Exp $
**
** This file implements a external (disk-based) database using BTrees.
** For a detailed discussion of BTrees, refer to
@@ -199,14 +199,19 @@ struct CellHdr {
#define MX_CELL ((SQLITE_PAGE_SIZE-sizeof(PageHdr))/MIN_CELL_SIZE)
/*
+** The amount of usable space on a single page of the BTree. This is the
+** page size minus the overhead of the page header.
+*/
+#define USABLE_SPACE (SQLITE_PAGE_SIZE - sizeof(PageHdr))
+
+/*
** The maximum amount of payload (in bytes) that can be stored locally for
** a database entry. If the entry contains more data than this, the
** extra goes onto overflow pages.
**
** This number is chosen so that at least 4 cells will fit on every page.
*/
-#define MX_LOCAL_PAYLOAD \
- (((SQLITE_PAGE_SIZE-sizeof(PageHdr))/4-(sizeof(CellHdr)+sizeof(Pgno)))&~3)
+#define MX_LOCAL_PAYLOAD ((USABLE_SPACE/4-(sizeof(CellHdr)+sizeof(Pgno)))&~3)
/*
** Data on a database page is stored as a linked list of Cell structures.
@@ -361,6 +366,7 @@ static void defragmentPage(MemPage *pPage){
FreeBlk *pFBlk;
char newPage[SQLITE_PAGE_SIZE];
+ assert( sqlitepager_iswriteable(pPage) );
pc = sizeof(PageHdr);
pPage->u.hdr.firstCell = pc;
memcpy(newPage, pPage->u.aDisk, pc);
@@ -409,6 +415,7 @@ static int allocateSpace(MemPage *pPage, int nByte){
int start;
int cnt = 0;
+ assert( sqlitepager_iswriteable(pPage) );
assert( nByte==ROUNDUP(nByte) );
if( pPage->nFree<nByte || pPage->isOverfull ) return 0;
pIdx = &pPage->u.hdr.firstFree;
@@ -454,6 +461,7 @@ static void freeSpace(MemPage *pPage, int start, int size){
FreeBlk *pNew;
FreeBlk *pNext;
+ assert( sqlitepager_iswriteable(pPage) );
assert( size == ROUNDUP(size) );
assert( start == ROUNDUP(start) );
pIdx = &pPage->u.hdr.firstFree;
@@ -518,7 +526,7 @@ static int initPage(MemPage *pPage, Pgno pgnoThis, MemPage *pParent){
if( pPage->isInit ) return SQLITE_OK;
pPage->isInit = 1;
pPage->nCell = 0;
- freeSpace = SQLITE_PAGE_SIZE - sizeof(PageHdr);
+ freeSpace = USABLE_SPACE;
idx = pPage->u.hdr.firstCell;
while( idx!=0 ){
if( idx>SQLITE_PAGE_SIZE-MIN_CELL_SIZE ) goto page_format_error;
@@ -560,6 +568,7 @@ page_format_error:
static void zeroPage(MemPage *pPage){
PageHdr *pHdr;
FreeBlk *pFBlk;
+ assert( sqlitepager_iswriteable(pPage) );
memset(pPage, 0, SQLITE_PAGE_SIZE);
pHdr = &pPage->u.hdr;
pHdr->firstCell = 0;
@@ -593,7 +602,12 @@ static void pageDestructor(void *pData){
** for accessing the database. We do not open the database file
** until the first page is loaded.
*/
-int sqliteBtreeOpen(const char *zFilename, int mode, Btree **ppBtree){
+int sqliteBtreeOpen(
+ const char *zFilename, /* Name of the file containing the BTree database */
+ int mode, /* Not currently used */
+ int nCache, /* How many pages in the page cache */
+ Btree **ppBtree /* Pointer to new Btree object written here */
+){
Btree *pBt;
int rc;
@@ -602,7 +616,8 @@ int sqliteBtreeOpen(const char *zFilename, int mode, Btree **ppBtree){
*ppBtree = 0;
return SQLITE_NOMEM;
}
- rc = sqlitepager_open(&pBt->pPager, zFilename, 100, EXTRA_SIZE);
+ if( nCache<10 ) nCache = 10;
+ rc = sqlitepager_open(&pBt->pPager, zFilename, nCache, EXTRA_SIZE);
if( rc!=SQLITE_OK ){
if( pBt->pPager ) sqlitepager_close(pBt->pPager);
sqliteFree(pBt);
@@ -1071,10 +1086,9 @@ static int moveToChild(BtCursor *pCur, int newPgno){
MemPage *pNewPage;
rc = sqlitepager_get(pCur->pBt->pPager, newPgno, (void**)&pNewPage);
- if( rc ){
- return rc;
- }
- initPage(pNewPage, newPgno, pCur->pPage);
+ if( rc ) return rc;
+ rc = initPage(pNewPage, newPgno, pCur->pPage);
+ if( rc ) return rc;
sqlitepager_unref(pCur->pPage);
pCur->pPage = pNewPage;
pCur->idx = 0;
@@ -1118,6 +1132,8 @@ static int moveToRoot(BtCursor *pCur){
rc = sqlitepager_get(pCur->pBt->pPager, pCur->pgnoRoot, (void**)&pNew);
if( rc ) return rc;
+ rc = initPage(pNew, pCur->pgnoRoot, 0);
+ if( rc ) return rc;
sqlitepager_unref(pCur->pPage);
pCur->pPage = pNew;
pCur->idx = 0;
@@ -1437,7 +1453,7 @@ static void reparentPage(Pager *pPager, Pgno pgno, MemPage *pNewParent){
if( pgno==0 ) return;
assert( pPager!=0 );
pThis = sqlitepager_lookup(pPager, pgno);
- if( pThis ){
+ if( pThis && pThis->isInit ){
if( pThis->pParent!=pNewParent ){
if( pThis->pParent ) sqlitepager_unref(pThis->pParent);
pThis->pParent = pNewParent;
@@ -1480,6 +1496,7 @@ static void dropCell(MemPage *pPage, int idx, int sz){
int j;
assert( idx>=0 && idx<pPage->nCell );
assert( sz==cellSize(pPage->apCell[idx]) );
+ assert( sqlitepager_iswriteable(pPage) );
freeSpace(pPage, Addr(pPage->apCell[idx]) - Addr(pPage), sz);
for(j=idx; j<pPage->nCell-1; j++){
pPage->apCell[j] = pPage->apCell[j+1];
@@ -1504,6 +1521,7 @@ static void insertCell(MemPage *pPage, int i, Cell *pCell, int sz){
int idx, j;
assert( i>=0 && i<=pPage->nCell );
assert( sz==cellSize(pCell) );
+ assert( sqlitepager_iswriteable(pPage) );
idx = allocateSpace(pPage, sz);
for(j=pPage->nCell; j>i; j--){
pPage->apCell[j] = pPage->apCell[j-1];
@@ -1527,6 +1545,7 @@ static void insertCell(MemPage *pPage, int i, Cell *pCell, int sz){
static void relinkCellList(MemPage *pPage){
int i;
u16 *pIdx;
+ assert( sqlitepager_iswriteable(pPage) );
pIdx = &pPage->u.hdr.firstCell;
for(i=0; i<pPage->nCell; i++){
int idx = Addr(pPage->apCell[i]) - Addr(pPage);
@@ -1620,9 +1639,10 @@ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){
int nxDiv; /* Next divider slot in pParent->apCell[] */
int rc; /* The return code */
int iCur; /* apCell[iCur] is the cell of the cursor */
- int usedPerPage; /* Memory needed for each page */
- int freePerPage; /* Average free space per page */
int totalSize; /* Total bytes for all cells */
+ int subtotal; /* Subtotal of bytes in cells on one page */
+ int cntNew[4]; /* Index in apCell[] of cell after i-th page */
+ int szNew[4]; /* Combined size of cells place on i-th page */
MemPage *extraUnref = 0; /* A page that needs to be unref-ed */
Pgno pgno; /* Page number */
Cell *apCell[MX_CELL*3+5]; /* All cells from pages being balanceed */
@@ -1634,7 +1654,8 @@ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){
** Return without doing any work if pPage is neither overfull nor
** underfull.
*/
- if( !pPage->isOverfull && pPage->nFree<SQLITE_PAGE_SIZE/2 ){
+ assert( sqlitepager_iswriteable(pPage) );
+ if( !pPage->isOverfull && pPage->nFree<SQLITE_PAGE_SIZE/3 ){
relinkCellList(pPage);
return SQLITE_OK;
}
@@ -1655,14 +1676,13 @@ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){
** into the root page and return. This reduces the depth
** of the BTree by one.
*/
- rc = sqlitepager_write(pPage);
- if( rc ) return rc;
pgnoChild = pPage->u.hdr.rightChild;
rc = sqlitepager_get(pBt->pPager, pgnoChild, (void**)&pChild);
if( rc ) return rc;
memcpy(pPage, pChild, SQLITE_PAGE_SIZE);
pPage->isInit = 0;
- initPage(pPage, sqlitepager_pagenumber(pPage), 0);
+ rc = initPage(pPage, sqlitepager_pagenumber(pPage), 0);
+ assert( rc==SQLITE_OK );
reparentChildPages(pBt->pPager, pPage);
freePage(pBt, pChild, pgnoChild);
sqlitepager_unref(pChild);
@@ -1689,6 +1709,7 @@ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){
if( rc ) return rc;
rc = allocatePage(pBt, &pChild, &pgnoChild);
if( rc ) return rc;
+ assert( sqlitepager_iswriteable(pChild) );
copyPage(pChild, pPage);
pChild->pParent = pPage;
sqlitepager_ref(pPage);
@@ -1703,10 +1724,9 @@ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){
pPage->u.hdr.rightChild = pgnoChild;
pParent = pPage;
pPage = pChild;
- }else{
- rc = sqlitepager_write(pPage);
- if( rc ) return rc;
}
+ rc = sqlitepager_write(pParent);
+ if( rc ) return rc;
/*
** Find the Cell in the parent page whose h.leftChild points back
@@ -1762,6 +1782,8 @@ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){
}
rc = sqlitepager_get(pBt->pPager, pgnoOld[i], (void**)&apOld[i]);
if( rc ) goto balance_cleanup;
+ rc = initPage(apOld[i], pgnoOld[i], pParent);
+ if( rc ) goto balance_cleanup;
nOld++;
}
@@ -1818,28 +1840,50 @@ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){
}
/*
- ** Estimate the number of pages needed. Record this number in "k"
- ** for now. It will get transferred to nNew as we allocate the
- ** new pages.
+ ** Figure out the number of pages needed to hold all nCell cells.
+ ** Store this number in "k". Also compute szNew[] which is the total
+ ** size of all cells on the i-th page and cntNew[] which is the index
+ ** in apCell[] of the cell that divides path i from path i+1.
+ ** cntNew[k] should equal nCell.
+ **
+ ** This little patch of code is critical for keeping the tree
+ ** balanced.
*/
totalSize = 0;
for(i=0; i<nCell; i++){
totalSize += szCell[i];
}
- k = (totalSize + (SQLITE_PAGE_SIZE - sizeof(PageHdr) - 1)) /
- (SQLITE_PAGE_SIZE - sizeof(PageHdr));
- usedPerPage = (totalSize+k-1)/k;
- freePerPage = SQLITE_PAGE_SIZE - usedPerPage;
-
+ for(subtotal=k=i=0; i<nCell; i++){
+ subtotal += szCell[i];
+ if( subtotal > USABLE_SPACE ){
+ szNew[k] = subtotal - szCell[i];
+ cntNew[k] = i;
+ subtotal = 0;
+ k++;
+ }
+ }
+ szNew[k] = subtotal;
+ cntNew[k] = nCell;
+ k++;
+ for(i=k-1; i>0; i--){
+ while( szNew[i]<USABLE_SPACE/2 ){
+ cntNew[i-1]--;
+ assert( cntNew[i-1]>0 );
+ szNew[i] += szCell[cntNew[i-1]];
+ szNew[i-1] -= szCell[cntNew[i-1]-1];
+ }
+ }
+ assert( cntNew[0]>0 );
/*
- ** Allocate new pages
+ ** Allocate k new pages
*/
for(i=0; i<k; i++){
rc = allocatePage(pBt, &apNew[i], &pgnoNew[i]);
if( rc ) goto balance_cleanup;
nNew++;
zeroPage(apNew[i]);
+ apNew[i]->isInit = 1;
}
/*
@@ -1849,11 +1893,13 @@ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){
j = 0;
for(i=0; i<nNew; i++){
MemPage *pNew = apNew[i];
- while( j<nCell && pNew->nFree>freePerPage && szCell[j]<=pNew->nFree ){
+ while( j<cntNew[i] ){
+ assert( pNew->nFree>=szCell[j] );
if( pCur && iCur==j ){ pCur->pPage = pNew; pCur->idx = pNew->nCell; }
insertCell(pNew, pNew->nCell, apCell[j], szCell[j]);
j++;
}
+ assert( pNew->nCell>0 );
assert( !pNew->isOverfull );
relinkCellList(pNew);
if( i<nNew-1 && j<nCell ){
@@ -1865,6 +1911,7 @@ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){
nxDiv++;
}
}
+ assert( j==nCell );
apNew[nNew-1]->u.hdr.rightChild = apOld[nOld-1]->u.hdr.rightChild;
if( nxDiv==pParent->nCell ){
pParent->u.hdr.rightChild = pgnoNew[nNew-1];
@@ -1999,6 +2046,8 @@ int sqliteBtreeDelete(BtCursor *pCur){
if( rc!=SQLITE_OK ){
return SQLITE_CORRUPT;
}
+ rc = sqlitepager_write(leafCur.pPage);
+ if( rc ) return rc;
dropCell(pPage, pCur->idx, cellSize(pCell));
pNext = leafCur.pPage->apCell[leafCur.idx];
szNext = cellSize(pNext);
@@ -2012,8 +2061,12 @@ int sqliteBtreeDelete(BtCursor *pCur){
releaseTempCursor(&leafCur);
}else{
dropCell(pPage, pCur->idx, cellSize(pCell));
+ if( pCur->idx>=pPage->nCell && pCur->idx>0 ){
+ pCur->idx--;
+ }else{
+ pCur->bSkipNext = 1;
+ }
rc = balance(pCur->pBt, pPage, pCur);
- pCur->bSkipNext = 1;
}
return rc;
}
@@ -2031,7 +2084,7 @@ int sqliteBtreeCreateTable(Btree *pBt, int *piTable){
}
rc = allocatePage(pBt, &pRoot, &pgnoRoot);
if( rc ) return rc;
- sqlitepager_write(pRoot);
+ assert( sqlitepager_iswriteable(pRoot) );
zeroPage(pRoot);
sqlitepager_unref(pRoot);
*piTable = (int)pgnoRoot;
@@ -2050,6 +2103,8 @@ static int clearDatabasePage(Btree *pBt, Pgno pgno, int freePageFlag){
rc = sqlitepager_get(pBt->pPager, pgno, (void**)&pPage);
if( rc ) return rc;
+ rc = sqlitepager_write(pPage);
+ if( rc ) return rc;
idx = pPage->u.hdr.firstCell;
while( idx>0 ){
pCell = (Cell*)&pPage->u.aDisk[idx];
@@ -2157,7 +2212,7 @@ int sqliteBtreeUpdateMeta(Btree *pBt, int *aMeta){
** Print a disassembly of the given page on standard output. This routine
** is used for debugging and testing only.
*/
-int sqliteBtreePageDump(Btree *pBt, int pgno){
+int sqliteBtreePageDump(Btree *pBt, int pgno, int recursive){
int rc;
MemPage *pPage;
int i, j;
@@ -2169,6 +2224,7 @@ int sqliteBtreePageDump(Btree *pBt, int pgno){
if( rc ){
return rc;
}
+ if( recursive ) printf("PAGE %d:\n", pgno);
i = 0;
idx = pPage->u.hdr.firstCell;
while( idx>0 && idx<=SQLITE_PAGE_SIZE-MIN_CELL_SIZE ){
@@ -2183,11 +2239,11 @@ int sqliteBtreePageDump(Btree *pBt, int pgno){
}
payload[sz] = 0;
printf(
- "cell %2d: i=%-10s chld=%-4d nk=%-3d nd=%-3d payload=%s\n",
+ "cell %2d: i=%-10s chld=%-4d nk=%-4d nd=%-4d payload=%s\n",
i, range, (int)pCell->h.leftChild, pCell->h.nKey, pCell->h.nData,
payload
);
- if( pPage->apCell[i]!=pCell ){
+ if( pPage->isInit && pPage->apCell[i]!=pCell ){
printf("**** apCell[%d] does not match on prior entry ****\n", i);
}
i++;
@@ -2212,6 +2268,15 @@ int sqliteBtreePageDump(Btree *pBt, int pgno){
if( idx!=0 ){
printf("ERROR: next freeblock index out of range: %d\n", idx);
}
+ if( recursive && pPage->u.hdr.rightChild!=0 ){
+ idx = pPage->u.hdr.firstCell;
+ while( idx>0 && idx<SQLITE_PAGE_SIZE-MIN_CELL_SIZE ){
+ Cell *pCell = (Cell*)&pPage->u.aDisk[idx];
+ sqliteBtreePageDump(pBt, pCell->h.leftChild, 1);
+ idx = pCell->h.iNext;
+ }
+ sqliteBtreePageDump(pBt, pPage->u.hdr.rightChild, 1);
+ }
sqlitepager_unref(pPage);
return SQLITE_OK;
}
@@ -2274,6 +2339,8 @@ struct SanityCheck {
Pager *pPager; // The associated pager. Also accessible by pBt->pPager
int nPage; // Number of pages in the database
int *anRef; // Number of times each page is referenced
+ int nTreePage; // Number of BTree pages
+ int nByte; // Number of bytes of data stored on BTree pages
char *zErrMsg; // An error message. NULL of no errors seen.
};
@@ -2355,7 +2422,7 @@ static void checkList(SanityCheck *pCheck, int iPage, int N, char *zContext){
** 5. Check the integrity of overflow pages.
** 6. Recursively call checkTreePage on all children.
** 7. Verify that the depth of all children is the same.
-** 8. Make sure this page is at least 50% full or else it is
+** 8. Make sure this page is at least 33% full or else it is
** the root of the tree.
*/
static int checkTreePage(
@@ -2465,11 +2532,18 @@ static int checkTreePage(
/* Check that free space is kept to a minimum
*/
- if( pParent && pPage->nFree>SQLITE_PAGE_SIZE/3 ){
+#if 0
+ if( pParent && pParent->nCell>2 && pPage->nFree>3*SQLITE_PAGE_SIZE/4 ){
sprintf(zMsg, "free space (%d) greater than max (%d)", pPage->nFree,
SQLITE_PAGE_SIZE/3);
checkAppendMsg(pCheck, zContext, zMsg);
}
+#endif
+
+ /* Update freespace totals.
+ */
+ pCheck->nTreePage++;
+ pCheck->nByte += USABLE_SPACE - pPage->nFree;
sqlitepager_unref(pPage);
return depth;
diff --git a/src/btree.h b/src/btree.h
index a5de692f2..6c54b6708 100644
--- a/src/btree.h
+++ b/src/btree.h
@@ -24,13 +24,13 @@
** This header file defines the interface that the sqlite B-Tree file
** subsystem.
**
-** @(#) $Id: btree.h,v 1.8 2001/06/30 21:53:53 drh Exp $
+** @(#) $Id: btree.h,v 1.9 2001/07/02 17:51:46 drh Exp $
*/
typedef struct Btree Btree;
typedef struct BtCursor BtCursor;
-int sqliteBtreeOpen(const char *zFilename, int mode, Btree **ppBtree);
+int sqliteBtreeOpen(const char *zFilename, int mode, int nPg, Btree **ppBtree);
int sqliteBtreeClose(Btree*);
int sqliteBtreeBeginTrans(Btree*);
@@ -58,7 +58,7 @@ int sqliteBtreeUpdateMeta(Btree*, int*);
#ifdef SQLITE_TEST
-int sqliteBtreePageDump(Btree*, int);
+int sqliteBtreePageDump(Btree*, int, int);
int sqliteBtreeCursorDump(BtCursor*, int*);
Pager *sqliteBtreePager(Btree*);
char *sqliteBtreeSanityCheck(Btree*, int*, int);
diff --git a/src/pager.c b/src/pager.c
index ebd641f34..57866918a 100644
--- a/src/pager.c
+++ b/src/pager.c
@@ -27,7 +27,7 @@
** all writes in order to support rollback. Locking is used to limit
** access to one or more reader or one writer.
**
-** @(#) $Id: pager.c,v 1.12 2001/06/28 01:54:49 drh Exp $
+** @(#) $Id: pager.c,v 1.13 2001/07/02 17:51:46 drh Exp $
*/
#include "sqliteInt.h"
#include "pager.h"
@@ -122,6 +122,7 @@ struct Pager {
int nHit, nMiss, nOvfl; /* Cache hits, missing, and LRU overflows */
unsigned char state; /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */
unsigned char errMask; /* One of several kinds of errors */
+ unsigned char *aInJournal; /* One bit for each page in the database file */
PgHdr *pFirst, *pLast; /* List of free pages */
PgHdr *pAll; /* List of all pages */
PgHdr *aHash[N_PG_HASH]; /* Hash table to map page number of PgHdr */
@@ -210,6 +211,7 @@ static int pager_unlock(fd){
** the beginning of the file.
*/
static int pager_seek(int fd, off_t whereto){
+ /*printf("SEEK to page %d\n", whereto/SQLITE_PAGE_SIZE + 1);*/
lseek(fd, whereto, SEEK_SET);
return SQLITE_OK;
}
@@ -232,6 +234,7 @@ static int pager_truncate(int fd, Pgno mxPg){
*/
static int pager_read(int fd, void *pBuf, int nByte){
int rc;
+ /* printf("READ\n");*/
rc = read(fd, pBuf, nByte);
if( rc<0 ){
memset(pBuf, 0, nByte);
@@ -253,6 +256,7 @@ static int pager_read(int fd, void *pBuf, int nByte){
*/
static int pager_write(int fd, const void *pBuf, int nByte){
int rc;
+ /*printf("WRITE\n");*/
rc = write(fd, pBuf, nByte);
if( rc<nByte ){
return SQLITE_FULL;
@@ -338,6 +342,8 @@ static int pager_unwritelock(Pager *pPager){
unlink(pPager->zJournal);
close(pPager->jfd);
pPager->jfd = -1;
+ sqliteFree( pPager->aInJournal );
+ pPager->aInJournal = 0;
for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
pPg->inJournal = 0;
pPg->dirty = 0;
@@ -434,6 +440,7 @@ static int pager_playback(Pager *pPager){
pPg = pager_lookup(pPager, pgRec.pgno);
if( pPg ){
memcpy(PGHDR_TO_DATA(pPg), pgRec.aData, SQLITE_PAGE_SIZE);
+ memset(PGHDR_TO_EXTRA(pPg), 0, pPager->nExtra);
}
rc = pager_seek(pPager->fd, (pgRec.pgno-1)*SQLITE_PAGE_SIZE);
if( rc!=SQLITE_OK ) break;
@@ -719,9 +726,9 @@ int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage){
/* Recycle an older page. First locate the page to be recycled.
** Try to find one that is not dirty and is near the head of
** of the free list */
- int cnt = 4;
+ int cnt = pPager->mxPage/2;
pPg = pPager->pFirst;
- while( pPg->dirty && 0<cnt-- ){
+ while( pPg->dirty && 0<cnt-- && pPg->pNextFree ){
pPg = pPg->pNextFree;
}
if( pPg==0 || pPg->dirty ) pPg = pPager->pFirst;
@@ -752,12 +759,19 @@ int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage){
/* Unlink the old page from the free list and the hash table
*/
- pPager->pFirst = pPg->pNextFree;
- if( pPager->pFirst ){
- pPager->pFirst->pPrevFree = 0;
+ if( pPg->pPrevFree ){
+ pPg->pPrevFree->pNextFree = pPg->pNextFree;
+ }else{
+ assert( pPager->pFirst==pPg );
+ pPager->pFirst = pPg->pNextFree;
+ }
+ if( pPg->pNextFree ){
+ pPg->pNextFree->pPrevFree = pPg->pPrevFree;
}else{
- pPager->pLast = 0;
+ assert( pPager->pLast==pPg );
+ pPager->pLast = pPg->pPrevFree;
}
+ pPg->pNextFree = pPg->pPrevFree = 0;
if( pPg->pNextHash ){
pPg->pNextHash->pPrevHash = pPg->pPrevHash;
}
@@ -768,10 +782,15 @@ int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage){
assert( pPager->aHash[h]==pPg );
pPager->aHash[h] = pPg->pNextHash;
}
+ pPg->pNextHash = pPg->pPrevHash = 0;
pPager->nOvfl++;
}
pPg->pgno = pgno;
- pPg->inJournal = 0;
+ if( pPager->aInJournal && pgno<=pPager->origDbSize ){
+ pPg->inJournal = (pPager->aInJournal[pgno/8] & (1<<(pgno&7)))!=0;
+ }else{
+ pPg->inJournal = 0;
+ }
pPg->dirty = 0;
pPg->nRef = 1;
REFINFO(pPg);
@@ -910,6 +929,11 @@ int sqlitepager_write(void *pData){
if( pPg->inJournal ){ return SQLITE_OK; }
assert( pPager->state!=SQLITE_UNLOCK );
if( pPager->state==SQLITE_READLOCK ){
+ assert( pPager->aInJournal==0 );
+ pPager->aInJournal = sqliteMalloc( pPager->dbSize/8 + 1 );
+ if( pPager->aInJournal==0 ){
+ return SQLITE_NOMEM;
+ }
pPager->jfd = open(pPager->zJournal, O_RDWR|O_CREAT, 0644);
if( pPager->jfd<0 ){
return SQLITE_CANTOPEN;
@@ -952,6 +976,8 @@ int sqlitepager_write(void *pData){
pPager->errMask |= PAGER_ERR_FULL;
return rc;
}
+ assert( pPager->aInJournal!=0 );
+ pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7);
}
pPg->inJournal = 1;
if( pPager->dbSize<pPg->pgno ){
@@ -961,6 +987,16 @@ int sqlitepager_write(void *pData){
}
/*
+** Return TRUE if the page given in the argument was previous passed
+** to sqlitepager_write(). In other words, return TRUE if it is ok
+** to change the content of the page.
+*/
+int sqlitepager_iswriteable(void *pData){
+ PgHdr *pPg = DATA_TO_PGHDR(pData);
+ return pPg->dirty;
+}
+
+/*
** Commit all changes to the database and release the write lock.
**
** If the commit fails for any reason, a rollback attempt is made
diff --git a/src/pager.h b/src/pager.h
index 208d898c0..1fefbec46 100644
--- a/src/pager.h
+++ b/src/pager.h
@@ -25,7 +25,7 @@
** subsystem. The page cache subsystem reads and writes a file a page
** at a time and provides a journal for rollback.
**
-** @(#) $Id: pager.h,v 1.6 2001/06/28 01:54:49 drh Exp $
+** @(#) $Id: pager.h,v 1.7 2001/07/02 17:51:46 drh Exp $
*/
/*
@@ -53,6 +53,7 @@ int sqlitepager_ref(void*);
int sqlitepager_unref(void*);
Pgno sqlitepager_pagenumber(void*);
int sqlitepager_write(void*);
+int sqlitepager_iswriteable(void*);
int sqlitepager_pagecount(Pager*);
int sqlitepager_commit(Pager*);
int sqlitepager_rollback(Pager*);
diff --git a/src/test3.c b/src/test3.c
index 0c135a74c..27745079a 100644
--- a/src/test3.c
+++ b/src/test3.c
@@ -25,7 +25,7 @@
** is not included in the SQLite library. It is used for automated
** testing of the SQLite library.
**
-** $Id: test3.c,v 1.6 2001/07/01 22:12:02 drh Exp $
+** $Id: test3.c,v 1.7 2001/07/02 17:51:46 drh Exp $
*/
#include "sqliteInt.h"
#include "pager.h"
@@ -79,7 +79,7 @@ static int btree_open(
" FILENAME\"", 0);
return TCL_ERROR;
}
- rc = sqliteBtreeOpen(argv[1], 0666, &pBt);
+ rc = sqliteBtreeOpen(argv[1], 0666, 10, &pBt);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
@@ -376,7 +376,37 @@ static int btree_page_dump(
}
if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
if( Tcl_GetInt(interp, argv[2], &iPage) ) return TCL_ERROR;
- rc = sqliteBtreePageDump(pBt, iPage);
+ rc = sqliteBtreePageDump(pBt, iPage, 0);
+ if( rc!=SQLITE_OK ){
+ Tcl_AppendResult(interp, errorName(rc), 0);
+ return TCL_ERROR;
+ }
+ return TCL_OK;
+}
+
+/*
+** Usage: btree_tree_dump ID PAGENUM
+**
+** Print a disassembly of a page and all its child pages on standard output
+*/
+static int btree_tree_dump(
+ void *NotUsed,
+ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
+ int argc, /* Number of arguments */
+ char **argv /* Text of each argument */
+){
+ Btree *pBt;
+ int iPage;
+ int rc;
+
+ if( argc!=3 ){
+ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+ " ID\"", 0);
+ return TCL_ERROR;
+ }
+ if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+ if( Tcl_GetInt(interp, argv[2], &iPage) ) return TCL_ERROR;
+ rc = sqliteBtreePageDump(pBt, iPage, 1);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
@@ -795,6 +825,7 @@ int Sqlitetest3_Init(Tcl_Interp *interp){
Tcl_CreateCommand(interp, "btree_get_meta", btree_get_meta, 0, 0);
Tcl_CreateCommand(interp, "btree_update_meta", btree_update_meta, 0, 0);
Tcl_CreateCommand(interp, "btree_page_dump", btree_page_dump, 0, 0);
+ Tcl_CreateCommand(interp, "btree_tree_dump", btree_tree_dump, 0, 0);
Tcl_CreateCommand(interp, "btree_pager_stats", btree_pager_stats, 0, 0);
Tcl_CreateCommand(interp, "btree_pager_ref_dump", btree_pager_ref_dump, 0, 0);
Tcl_CreateCommand(interp, "btree_cursor", btree_cursor, 0, 0);