Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -45,17 +45,18 @@ # LIBREADLINE = @TARGET_READLINE_LIBS@ # Object files for the SQLite library. # -LIBOBJ = build.o dbbe.o dbbegdbm.o dbbemem.o delete.o expr.o insert.o \ +LIBOBJ = btree.o build.o dbbe.o dbbegdbm.o dbbemem.o delete.o expr.o insert.o \ main.o pager.o parse.o printf.o random.o select.o table.o \ tokenize.o update.o util.o vdbe.o where.o tclsqlite.o # All of the source code files. # SRC = \ + $(TOP)/src/btree.c \ $(TOP)/src/build.c \ $(TOP)/src/dbbe.c \ $(TOP)/src/dbbe.h \ $(TOP)/src/dbbegdbm.c \ $(TOP)/src/dbbemem.c \ @@ -82,11 +83,12 @@ # Source code to the test files. # TESTSRC = \ $(TOP)/src/test1.c \ - $(TOP)/src/test2.c + $(TOP)/src/test2.c \ + $(TOP)/src/test3.c # This is the default Makefile target. The objects listed here # are what get build when you type just "make" with no arguments. # all: sqlite.h libsqlite.a sqlite @@ -115,14 +117,19 @@ # Header files used by all library source files. # HDR = \ sqlite.h \ $(TOP)/src/sqliteInt.h \ + $(TOP)/src/btree.h \ $(TOP)/src/dbbe.h \ + $(TOP)/src/pager.h \ $(TOP)/src/vdbe.h \ parse.h +btree.o: $(TOP)/src/btree.c $(HDR) + $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/btree.c + build.o: $(TOP)/src/build.c $(HDR) $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/build.c dbbe.o: $(TOP)/src/dbbe.c $(HDR) $(TCC) $(GDBM_FLAGS) -c $(TOP)/src/dbbe.c @@ -199,11 +206,11 @@ $(TCC) $(TCL_FLAGS) -DTCLSH=1 -o tclsqlite \ $(TOP)/src/tclsqlite.c libsqlite.a $(LIBGDBM) $(LIBTCL) testfixture: $(TOP)/src/tclsqlite.c libsqlite.a $(TESTSRC) $(TCC) $(TCL_FLAGS) -DTCLSH=1 -DSQLITE_TEST=1 -o testfixture \ - $(TESTSRC) $(TOP)/src/tclsqlite.c \ + $(TESTSRC) $(TOP)/src/tclsqlite.c $(TOP)/src/btree.c \ libsqlite.a $(LIBGDBM) $(LIBTCL) test: testfixture sqlite ./testfixture $(TOP)/test/all.test Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -19,11 +19,11 @@ ** Author contact information: ** drh@hwaci.com ** http://www.hwaci.com/drh/ ** ************************************************************************* -** $Id: btree.c,v 1.12 2001/06/10 19:56:59 drh Exp $ +** $Id: btree.c,v 1.13 2001/06/22 19:15:00 drh Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to ** ** Donald E. Knuth, THE ART OF COMPUTER PROGRAMMING, Volume 3: @@ -71,14 +71,20 @@ ** Primitive data types. u32 must be 4 bytes and u16 must be 2 bytes. ** The uptr type must be big enough to hold a pointer. ** Change these typedefs when porting to new architectures. */ typedef unsigned int uptr; -typedef unsigned int u32; +/* typedef unsigned int u32; -- already defined in sqliteInt.h */ typedef unsigned short int u16; typedef unsigned char u8; +/* +** This macro casts a pointer to an integer. Useful for doing +** pointer arithmetic. +*/ +#define addr(X) ((uptr)X) + /* ** Forward declarations of structures used only in this file. */ typedef struct PageOne PageOne; typedef struct MemPage MemPage; @@ -97,16 +103,28 @@ */ #define ROUNDUP(X) ((X+3) & ~3) /* ** This is a magic string that appears at the beginning of every -** SQLite database in order to identify the fail as a real database. +** SQLite database in order to identify the file as a real database. */ static const char zMagicHeader[] = - "** This file contains an SQLite 2.0 database **" + "** This file contains an SQLite 2.0 database **"; #define MAGIC_SIZE (sizeof(zMagicHeader)) +/* +** This is a magic integer also used to the integrety of the database +** file. This integer is used in addition to the string above so that +** if the file is written on a little-endian architecture and read +** on a big-endian architectures (or vice versa) we can detect the +** problem. +** +** The number used was obtained at random and has no special +** significance. +*/ +#define MAGIC 0xdae37528 + /* ** The first page of the database file contains a magic header string ** to identify the file as an SQLite database file. It also contains ** a pointer to the first free page of the file. Page 2 contains the ** root of the principle BTree. The file might contain other BTrees @@ -119,11 +137,12 @@ ** for additional information.) Page 0 does not exist and a page ** number of 0 is used to mean "no such page". */ struct PageOne { char zMagic[MAGIC_SIZE]; /* String that identifies the file as a database */ - Pgno firstList; /* First free page in a list of all free pages */ + int iMagic; /* Integer to verify correct byte order */ + Pgno freeList; /* First free page in a list of all free pages */ int aMeta[SQLITE_N_BTREE_META]; /* User defined integers */ }; /* ** Each database page has a header that is an instance of this @@ -154,19 +173,19 @@ ** has a header and data. This structure defines the header. The ** key and data (collectively the "payload") follow this header on ** the database page. ** ** A definition of the complete Cell structure is given below. The -** header for the cell must be defined separately in order to do some +** header for the cell must be defined first in order to do some ** of the sizing #defines that follow. */ struct CellHdr { Pgno leftChild; /* Child page that comes before this cell */ u16 nKey; /* Number of bytes in the key */ u16 iNext; /* Index in MemPage.u.aDisk[] of next cell in sorted order */ u32 nData; /* Number of bytes of data */ -} +}; /* ** The minimum size of a complete Cell. The Cell must contain a header ** and at least 4 bytes of payload. */ @@ -177,12 +196,12 @@ ** page of the database. */ #define MX_CELL ((SQLITE_PAGE_SIZE-sizeof(PageHdr))/MIN_CELL_SIZE) /* -** The maximum amount of data (in bytes) that can be stored locally for a -** database entry. If the entry contains more data than this, the +** 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 \ @@ -224,11 +243,11 @@ */ #define OVERFLOW_SIZE (SQLITE_PAGE_SIZE-sizeof(Pgno)) /* ** When the key and data for a single entry in the BTree will not fit in -** the MX_LOACAL_PAYLOAD bytes of space available on the database page, +** the MX_LOCAL_PAYLOAD bytes of space available on the database page, ** then all extra bytes are written to a linked list of overflow pages. ** Each overflow page is an instance of the following structure. ** ** Unused pages in the database are also represented by instances of ** the OverflowPage structure. The PageOne.freeList field is the @@ -276,11 +295,11 @@ MemPage *pParent; /* The parent of this page. NULL for root */ int nFree; /* Number of free bytes in u.aDisk[] */ int nCell; /* Number of entries on this page */ int isOverfull; /* Some apCell[] points outside u.aDisk[] */ Cell *apCell[MX_CELL+2]; /* All data entires in sorted order */ -} +}; /* ** The in-memory image of a disk page has the auxiliary information appended ** to the end. EXTRA_SIZE is the number of bytes of space needed to hold ** that extra information. @@ -306,20 +325,20 @@ struct BtCursor { Btree *pBt; /* The Btree to which this cursor belongs */ BtCursor *pNext, *pPrev; /* Forms a linked list of all cursors */ Pgno pgnoRoot; /* The root page of this tree */ MemPage *pPage; /* Page that contains the entry */ - u16 idx; /* Index of the entry in pPage->apCell[] */ + int idx; /* Index of the entry in pPage->apCell[] */ u8 bSkipNext; /* sqliteBtreeNext() is no-op if true */ u8 iMatch; /* compare result from last sqliteBtreeMoveto() */ }; /* ** Compute the total number of bytes that a Cell needs on the main ** database page. The number returned includes the Cell header, ** local payload storage, and the pointer to overflow pages (if -** applicable). Additional spaced allocated on overflow pages +** applicable). Additional space allocated on overflow pages ** is NOT included in the value returned from this routine. */ static int cellSize(Cell *pCell){ int n = pCell->h.nKey + pCell->h.nData; if( n>MX_LOCAL_PAYLOAD ){ @@ -343,20 +362,26 @@ pc = sizeof(PageHdr); pPage->u.hdr.firstCell = pc; memcpy(newPage, pPage->u.aDisk, pc); for(i=0; inCell; i++){ - Cell *pCell = &pPage->apCell[i]; + Cell *pCell = (Cell*)&pPage->apCell[i]; + + /* This routine should never be called on an overfull page. The + ** following asserts verify that constraint. */ + assert( addr(pCell) > addr(pPage) ); + assert( addr(pCell) < addr(pPage) + SQLITE_PAGE_SIZE ); + n = cellSize(pCell); pCell->h.iNext = inCell-1 ? pc + n : 0; memcpy(&newPage[pc], pCell, n); pPage->apCell[i] = (Cell*)&pPage->u.aDisk[pc]; pc += n; } assert( pPage->nFree==SQLITE_PAGE_SIZE-pc ); memcpy(pPage->u.aDisk, newPage, pc); - pFBlk = &pPage->u.aDisk[pc]; + pFBlk = (FreeBlk*)&pPage->u.aDisk[pc]; pFBlk->iSize = SQLITE_PAGE_SIZE - pc; pFBlk->iNext = 0; pPage->u.hdr.firstFree = pc; memset(&pFBlk[1], 0, SQLITE_PAGE_SIZE - pc - sizeof(FreeBlk)); } @@ -376,16 +401,18 @@ */ static int allocateSpace(MemPage *pPage, int nByte){ FreeBlk *p; u16 *pIdx; int start; + int cnt = 0; assert( nByte==ROUNDUP(nByte) ); if( pPage->nFreeisOverfull ) return 0; pIdx = &pPage->u.hdr.firstFree; p = (FreeBlk*)&pPage->u.aDisk[*pIdx]; while( p->iSizeiNext==0 ){ defragmentPage(pPage); pIdx = &pPage->u.hdr.firstFree; }else{ pIdx = &p->iNext; @@ -394,12 +421,13 @@ } if( p->iSize==nByte ){ start = *pIdx; *pIdx = p->iNext; }else{ + FreeBlk *pNew; start = *pIdx; - FreeBlk *pNew = (FreeBlk*)&pPage->u.aDisk[start + nByte]; + pNew = (FreeBlk*)&pPage->u.aDisk[start + nByte]; pNew->iNext = p->iNext; pNew->iSize = p->iSize - nByte; *pIdx = start + nByte; } pPage->nFree -= nByte; @@ -429,11 +457,11 @@ while( idx!=0 && idxu.aDisk[idx]; if( idx + pFBlk->iSize == start ){ pFBlk->iSize += size; if( idx + pFBlk->iSize == pFBlk->iNext ){ - pNext = (FreeBlk*)&pPage->u.aDisk[pFblk->iNext]; + pNext = (FreeBlk*)&pPage->u.aDisk[pFBlk->iNext]; pFBlk->iSize += pNext->iSize; pFBlk->iNext = pNext->iNext; } pPage->nFree += size; return; @@ -487,12 +515,13 @@ pPage->isInit = 1; pPage->nCell = 0; freeSpace = SQLITE_PAGE_SIZE - sizeof(PageHdr); idx = pPage->u.hdr.firstCell; while( idx!=0 ){ - if( idx>SQLITE_PAGE_SIZE-MN_CELL_SIZE ) goto page_format_error; + if( idx>SQLITE_PAGE_SIZE-MIN_CELL_SIZE ) goto page_format_error; if( idxu.aDisk[idx]; sz = cellSize(pCell); if( idx+sz > SQLITE_PAGE_SIZE ) goto page_format_error; freeSpace -= sz; pPage->apCell[pPage->nCell++] = pCell; @@ -532,10 +561,13 @@ pHdr->firstCell = 0; pHdr->firstFree = sizeof(*pHdr); pFBlk = (FreeBlk*)&pHdr[1]; pFBlk->iNext = 0; pFBlk->iSize = SQLITE_PAGE_SIZE - sizeof(*pHdr); + pPage->nFree = pFBlk->iSize; + pPage->nCell = 0; + pPage->isOverfull = 0; } /* ** This routine is called when the reference count for a page ** reaches zero. We need to unref the pParent pointer when that @@ -557,17 +589,18 @@ ** 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){ Btree *pBt; + int rc; pBt = sqliteMalloc( sizeof(*pBt) ); if( pBt==0 ){ - **ppBtree = 0; + *ppBtree = 0; return SQLITE_NOMEM; } - rc = sqlitepager_open(&pBt->pPager, zFilename, 100, EXTRA_SPACE); + rc = sqlitepager_open(&pBt->pPager, zFilename, 100, EXTRA_SIZE); if( rc!=SQLITE_OK ){ if( pBt->pPager ) sqlitepager_close(pBt->pPager); sqliteFree(pBt); *ppBtree = 0; return rc; @@ -602,19 +635,19 @@ ** if there is a locking protocol violation. */ static int lockBtree(Btree *pBt){ int rc; if( pBt->page1 ) return SQLITE_OK; - rc = sqlitepager_get(pBt->pPager, 1, &pBt->page1); + rc = sqlitepager_get(pBt->pPager, 1, (void**)&pBt->page1); if( rc!=SQLITE_OK ) return rc; /* Do some checking to help insure the file we opened really is ** a valid database file. */ if( sqlitepager_pagecount(pBt->pPager)>0 ){ PageOne *pP1 = pBt->page1; - if( strcmp(pP1->zMagic1,zMagicHeader)!=0 ){ + if( strcmp(pP1->zMagic,zMagicHeader)!=0 || pP1->iMagic!=MAGIC ){ rc = SQLITE_CORRUPT; goto page1_init_failed; } } return rc; @@ -624,27 +657,30 @@ pBt->page1 = 0; return rc; } /* -** Create a new database by initializing the first two pages. +** Create a new database by initializing the first two pages of the +** file. */ static int newDatabase(Btree *pBt){ MemPage *pRoot; PageOne *pP1; + int rc; if( sqlitepager_pagecount(pBt->pPager)>0 ) return SQLITE_OK; pP1 = pBt->page1; rc = sqlitepager_write(pBt->page1); if( rc ) return rc; - rc = sqlitepager_get(pBt->pPager, 2, &pRoot); + rc = sqlitepager_get(pBt->pPager, 2, (void**)&pRoot); if( rc ) return rc; rc = sqlitepager_write(pRoot); if( rc ){ sqlitepager_unref(pRoot); return rc; } strcpy(pP1->zMagic, zMagicHeader); + pP1->iMagic = MAGIC; zeroPage(pRoot); sqlitepager_unref(pRoot); return SQLITE_OK; } @@ -662,21 +698,24 @@ ** sqliteBtreeDelete() ** sqliteBtreeUpdateMeta() */ int sqliteBtreeBeginTrans(Btree *pBt){ int rc; - PageOne *pP1; if( pBt->inTrans ) return SQLITE_ERROR; if( pBt->page1==0 ){ rc = lockBtree(pBt); - if( rc!=SQLITE_OK ) return rc; + if( rc!=SQLITE_OK ){ + return rc; + } } rc = sqlitepager_write(pBt->page1); - if( rc==SQLITE_OK ){ - pBt->inTrans = 1; + if( rc!=SQLITE_OK ){ + return rc; } - return newDatabase(pBt); + pBt->inTrans = 1; + rc = newDatabase(pBt); + return rc; } /* ** Remove the last reference to the database file. This will ** remove the read lock. @@ -732,11 +771,11 @@ if( pCur==0 ){ rc = SQLITE_NOMEM; goto create_cursor_exception; } pCur->pgnoRoot = (Pgno)iTable; - rc = sqlitepager_get(pBt->pPager, pCur->pgnoRoot, &pCur->pPage); + rc = sqlitepager_get(pBt->pPager, pCur->pgnoRoot, (void**)&pCur->pPage); if( rc!=SQLITE_OK ){ goto create_cursor_exception; } rc = initPage(pCur->pPage, pCur->pgnoRoot, 0); if( rc!=SQLITE_OK ){ @@ -757,21 +796,20 @@ *ppCur = 0; if( pCur ){ if( pCur->pPage ) sqlitepager_unref(pCur->pPage); sqliteFree(pCur); } - unlinkBtree(pBt); + unlockBtree(pBt); return rc; } /* ** Close a cursor. The lock on the database file is released ** when the last cursor is closed. */ int sqliteBtreeCloseCursor(BtCursor *pCur){ Btree *pBt = pCur->pBt; - int i; if( pCur->pPrev ){ pCur->pPrev->pNext = pCur->pNext; }else{ pBt->pCursor = pCur->pNext; } @@ -779,10 +817,11 @@ pCur->pNext->pPrev = pCur->pPrev; } sqlitepager_unref(pCur->pPage); unlockBtree(pBt); sqliteFree(pCur); + return SQLITE_OK; } /* ** Make a temporary cursor by filling in the fields of pTempCur. ** The temporary cursor is not on the cursor list for the Btree. @@ -817,11 +856,11 @@ assert( pPage!=0 ); if( pCur->idx >= pPage->nCell ){ *pSize = 0; }else{ pCell = pPage->apCell[pCur->idx]; - *psize = pCell->h.nKey; + *pSize = pCell->h.nKey; } return SQLITE_OK; } /* @@ -833,13 +872,14 @@ ** It just reads bytes from the payload area. */ static int getPayload(BtCursor *pCur, int offset, int amt, char *zBuf){ char *aPayload; Pgno nextPage; + int rc; assert( pCur!=0 && pCur->pPage!=0 ); - assert( pCur->idx>=0 && pCur->idxnCell ); - aPayload = pCur->pPage->apCell[pCur->idx].aPayload; + assert( pCur->idx>=0 && pCur->idxpPage->nCell ); + aPayload = pCur->pPage->apCell[pCur->idx]->aPayload; if( offsetMX_LOCAL_PAYLOAD ){ a = MX_LOCAL_PAYLOAD - offset; } @@ -850,15 +890,15 @@ offset += a; zBuf += a; amt -= a; } if( amt>0 ){ - nextPage = pCur->pPage->apCell[pCur->idx].ovfl; + nextPage = pCur->pPage->apCell[pCur->idx]->ovfl; } while( amt>0 && nextPage ){ OverflowPage *pOvfl; - rc = sqlitepager_get(pCur->pBt->pPager, nextPage, &pOvfl); + rc = sqlitepager_get(pCur->pBt->pPager, nextPage, (void**)&pOvfl); if( rc!=0 ){ return rc; } nextPage = pOvfl->iNext; if( offsetpPage ); assert( pCur->idx>=0 && pCur->idxpPage->nCell ); pCell = pCur->pPage->apCell[pCur->idx]; @@ -988,11 +1028,11 @@ while( nKey>0 ){ OverflowPage *pOvfl; if( nextPage==0 ){ return SQLITE_CORRUPT; } - rc = sqlitepager_get(pCur->pBt->pPager, nextPage, &pOvfl); + rc = sqlitepager_get(pCur->pBt->pPager, nextPage, (void**)&pOvfl); if( rc ){ return rc; } nextPage = pOvfl->iNext; n = nKey; @@ -1018,11 +1058,11 @@ */ static int moveToChild(BtCursor *pCur, int newPgno){ int rc; MemPage *pNewPage; - rc = sqlitepager_get(pCur->pBt->pPager, newPgno, &pNewPage); + rc = sqlitepager_get(pCur->pBt->pPager, newPgno, (void**)&pNewPage); if( rc ){ return rc; } initPage(pNewPage, newPgno, pCur->pPage); sqlitepager_unref(pCur->pPage); @@ -1040,20 +1080,20 @@ ** the largest cell index. */ static int moveToParent(BtCursor *pCur){ Pgno oldPgno; MemPage *pParent; - + int i; pParent = pCur->pPage->pParent; if( pParent==0 ) return SQLITE_INTERNAL; oldPgno = sqlitepager_pagenumber(pCur->pPage); sqlitepager_ref(pParent); sqlitepager_unref(pCur->pPage); pCur->pPage = pParent; - pCur->idx = pPage->nCell; - for(i=0; inCell; i++){ - if( pPage->apCell[i].h.leftChild==oldPgno ){ + pCur->idx = pParent->nCell; + for(i=0; inCell; i++){ + if( pParent->apCell[i]->h.leftChild==oldPgno ){ pCur->idx = i; break; } } return SQLITE_OK; @@ -1064,11 +1104,11 @@ */ static int moveToRoot(BtCursor *pCur){ MemPage *pNew; int rc; - rc = sqlitepager_get(pCur->pBt->pPager, pCur->pgnoRoot, &pNew); + rc = sqlitepager_get(pCur->pBt->pPager, pCur->pgnoRoot, (void**)&pNew); if( rc ) return rc; sqlitepager_unref(pCur->pPage); pCur->pPage = pNew; pCur->idx = 0; return SQLITE_OK; @@ -1168,20 +1208,20 @@ if( pRes ) *pRes = 0; return SQLITE_OK; } pCur->idx++; if( pCur->idx>=pCur->pPage->nCell ){ - if( pPage->u.hdr.rightChild ){ - rc = moveToChild(pCur, pPage->u.hdr.rightChild); + if( pCur->pPage->u.hdr.rightChild ){ + rc = moveToChild(pCur, pCur->pPage->u.hdr.rightChild); if( rc ) return rc; rc = moveToLeftmost(pCur); if( rc ) return rc; if( pRes ) *pRes = 0; return SQLITE_OK; } do{ - if( pCur->pParent==0 ){ + if( pCur->pPage->pParent==0 ){ if( pRes ) *pRes = 1; return SQLITE_OK; } rc = moveToParent(pCur); if( rc ) return rc; @@ -1207,16 +1247,17 @@ ** an error. *ppPage and *pPgno are undefined in the event of an error. ** Do not invoke sqlitepager_unref() on *ppPage if an error is returned. */ static int allocatePage(Btree *pBt, MemPage **ppPage, Pgno *pPgno){ PageOne *pPage1 = pBt->page1; + int rc; if( pPage1->freeList ){ OverflowPage *pOvfl; rc = sqlitepager_write(pPage1); if( rc ) return rc; *pPgno = pPage1->freeList; - rc = sqlitepager_get(pBt->pPager, pPage1->freeList, &pOvfl); + rc = sqlitepager_get(pBt->pPager, pPage1->freeList, (void**)&pOvfl); if( rc ) return rc; rc = sqlitepager_write(pOvfl); if( rc ){ sqlitepager_unref(pOvfl); return rc; @@ -1223,11 +1264,11 @@ } pPage1->freeList = pOvfl->iNext; *ppPage = (MemPage*)pOvfl; }else{ *pPgno = sqlitepager_pagecount(pBt->pPager); - rc = sqlitepager_get(pBt->pPager, *pPgno, ppPage); + rc = sqlitepager_get(pBt->pPager, *pPgno, (void**)ppPage); if( rc ) return rc; rc = sqlitepager_write(*ppPage); } return rc; } @@ -1253,11 +1294,11 @@ if( rc ){ return rc; } if( pOvfl==0 ){ assert( pgno>0 ); - rc = sqlitepager_get(pBt->pPager, pgno, &pOvfl); + rc = sqlitepager_get(pBt->pPager, pgno, (void**)&pOvfl); if( rc ) return rc; needOvflUnref = 1; } rc = sqlitepager_write(pOvfl); if( rc ){ @@ -1265,12 +1306,12 @@ return rc; } pOvfl->iNext = pPage1->freeList; pPage1->freeList = pgno; memset(pOvfl->aPayload, 0, OVERFLOW_SIZE); - pPage->isInit = 0; - assert( pPage->pParent==0 ); + ((MemPage*)pPage)->isInit = 0; + assert( ((MemPage*)pPage)->pParent==0 ); rc = sqlitepager_unref(pOvfl); return rc; } /* @@ -1287,11 +1328,11 @@ return SQLITE_OK; } ovfl = pCell->ovfl; pCell->ovfl = 0; while( ovfl ){ - rc = sqlitepager_get(pPager, ovfl, &pOvfl); + rc = sqlitepager_get(pPager, ovfl, (void**)&pOvfl); if( rc ) return rc; nextOvfl = pOvfl->iNext; rc = freePage(pBt, pOvfl, ovfl); if( rc ) return rc; ovfl = nextOvfl; @@ -1308,14 +1349,14 @@ Btree *pBt, /* The whole Btree. Needed to allocate pages */ Cell *pCell, /* Populate this Cell structure */ void *pKey, int nKey, /* The key */ void *pData,int nData /* The data */ ){ - int OverflowPage *pOvfl; + OverflowPage *pOvfl; Pgno *pNext; int spaceLeft; - int n; + int n, rc; int nPayload; char *pPayload; char *pSpace; pCell->h.leftChild = 0; @@ -1329,19 +1370,19 @@ pPayload = pKey; pKey = 0; nPayload = nKey; while( nPayload>0 ){ if( spaceLeft==0 ){ - rc = allocatePage(pBt, &pOvfl, pNext); + rc = allocatePage(pBt, (MemPage**)&pOvfl, pNext); if( rc ){ *pNext = 0; clearCell(pBt, pCell); return rc; } spaceLeft = OVERFLOW_SIZE; pSpace = pOvfl->aPayload; - pNextPg = &pOvfl->iNext; + pNext = &pOvfl->iNext; } n = nPayload; if( n>spaceLeft ) n = spaceLeft; memcpy(pSpace, pPayload, n); nPayload -= n; @@ -1381,14 +1422,14 @@ ** to make sure that child knows that pPage is its parent. ** ** This routine gets called after you memcpy() one page into ** another. */ -static void reparentChildPages(Pager *pPager, Page *pPage){ +static void reparentChildPages(Pager *pPager, MemPage *pPage){ int i; for(i=0; inCell; i++){ - reparentPage(pPager, pPage->apCell[i]->leftChild, pPage); + reparentPage(pPager, pPage->apCell[i]->h.leftChild, pPage); } reparentPage(pPager, pPage->u.hdr.rightChild, pPage); } /* @@ -1398,20 +1439,20 @@ ** removes the reference to the cell from pPage. ** ** "sz" must be the number of bytes in the cell. ** ** Do not bother maintaining the integrity of the linked list of Cells. -** Only pPage->apCell[] is important. The relinkCellList() routine -** will be called soon after this routine in order to rebuild the -** linked list. +** Only the pPage->apCell[] array is important. The relinkCellList() +** routine will be called soon after this routine in order to rebuild +** the linked list. */ -static void dropCell(MemPage *pPage, int i, int sz){ +static void dropCell(MemPage *pPage, int idx, int sz){ int j; - assert( i>=0 && inCell ); - assert( sz==cellSize(pPage->apCell[i]); + assert( idx>=0 && idxnCell ); + assert( sz==cellSize(pPage->apCell[idx]) ); freeSpace(pPage, idx, sz); - for(j=i, jnCell-2; j++){ + for(j=idx; jnCell-2; j++){ pPage->apCell[j] = pPage->apCell[j+1]; } pPage->nCell--; } @@ -1422,13 +1463,13 @@ ** If the cell content will fit on the page, then put it there. If it ** will not fit, then just make pPage->apCell[i] point to the content ** and set pPage->isOverfull. ** ** Do not bother maintaining the integrity of the linked list of Cells. -** Only pPage->apCell[] is important. The relinkCellList() routine -** will be called soon after this routine in order to rebuild the -** linked list. +** Only the pPage->apCell[] array is important. The relinkCellList() +** routine will be called soon after this routine in order to rebuild +** the linked list. */ static void insertCell(MemPage *pPage, int i, Cell *pCell, int sz){ int idx, j; assert( i>=0 && i<=pPage->nCell ); assert( sz==cellSize(pCell) ); @@ -1440,26 +1481,27 @@ if( idx<=0 ){ pPage->isOverfull = 1; pPage->apCell[i] = pCell; }else{ memcpy(&pPage->u.aDisk[idx], pCell, sz); - pPage->apCell[i] = (Cell*)&pPage->u.aDisk[idx]); + pPage->apCell[i] = (Cell*)&pPage->u.aDisk[idx]; } } /* ** Rebuild the linked list of cells on a page so that the cells -** occur in the order specified by pPage->apCell[]. Invoke this -** routine once to repair damage after one or more invocations -** of either insertCell() or dropCell(). +** occur in the order specified by the pPage->apCell[] array. +** Invoke this routine once to repair damage after one or more +** invocations of either insertCell() or dropCell(). */ static void relinkCellList(MemPage *pPage){ int i; u16 *pIdx; pIdx = &pPage->u.hdr.firstCell; for(i=0; inCell; i++){ - int idx = ((uptr)pPage->apCell[i]) - (uptr)pPage; + int idx = addr(pPage->apCell[i]) - addr(pPage); + assert( idx>0 && idxapCell[i]->h.iNext; } *pIdx = 0; } @@ -1477,16 +1519,16 @@ pTo->pParent = pFrom->pParent; pTo->isInit = 1; pTo->nCell = pFrom->nCell; pTo->nFree = pFrom->nFree; pTo->isOverfull = pFrom->isOverfull; - to = (unsigned int)pTo; - from = (unsigned int)pFrom; + to = addr(pTo); + from = addr(pFrom); for(i=0; inCell; i++){ - uptr addr = (uptr)(pFrom->apCell[i]); - if( addr>from && addrapCell[i]) = addr + to - from; + uptr x = addr(pFrom->apCell[i]); + if( x>from && xapCell[i]) = x + to - from; } } } /* @@ -1497,12 +1539,13 @@ ** or last child of its parent. If pPage has fewer than two siblings ** (something which can only happen if pPage is the root page or a ** child of root) then all available siblings participate in the balancing. ** ** The number of siblings of pPage might be increased or decreased by -** one in order to keep all pages between 2/3 and completely full. If -** pPage is the root page, then the depth of the tree might be increased +** one in an effort to keep pages between 66% and 100% full. The root page +** is special and is allowed to be less than 66% full. If pPage is +** the root page, then the depth of the tree might be increased ** or decreased by one, as necessary, to keep the root page from being ** overfull or empty. ** ** This routine calls relinkCellList() on its input page regardless of ** whether or not it does any real balancing. Client routines will typically @@ -1509,16 +1552,22 @@ ** invoke insertCell() or dropCell() before calling this routine, so we ** need to call relinkCellList() to clean up the mess that those other ** routines left behind. ** ** pCur is left pointing to the same cell as when this routine was called -** event if that cell gets moved to a different page. pCur may be NULL. +** even if that cell gets moved to a different page. pCur may be NULL. +** Set the pCur parameter to NULL if you do not care about keeping track +** of a cell as that will save this routine the work of keeping track of it. ** ** Note that when this routine is called, some of the Cells on pPage ** might not actually be stored in pPage->u.aDisk[]. This can happen ** if the page is overfull. Part of the job of this routine is to ** make sure all Cells for pPage once again fit in pPage->u.aDisk[]. +** +** In the course of balancing the siblings of pPage, the parent of pPage +** might become overfull or underfull. If that happens, then this routine +** is called recursively on the parent. ** ** If this routine fails for any reason, it means the database may have ** been left in a corrupted state and should be rolled back. */ static int balance(Btree *pBt, MemPage *pPage, BtCursor *pCur){ @@ -1530,19 +1579,20 @@ int idxDiv[3]; /* Indices of divider cells in pParent */ Cell *apDiv[3]; /* Divider cells in pParent */ int nCell; /* Number of cells in apCell[] */ int nOld; /* Number of pages in apOld[] */ int nNew; /* Number of pages in apNew[] */ - int perPage; /* Approximate number of bytes per page */ int nDiv; /* Number of cells in apDiv[] */ int i, j, k; /* Loop counters */ int idx; /* Index of pPage in pParent->apCell[] */ 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 */ + Pgno pgno; /* Page number */ Cell *apCell[MX_CELL*3+5]; /* All cells from pages being balanceed */ int szCell[MX_CELL*3+5]; /* Local size of all cells */ Cell aTemp[2]; /* Temporary holding area for apDiv[] */ MemPage aOld[3]; /* Temporary copies of pPage and its siblings */ @@ -1561,11 +1611,11 @@ ** special rules apply. */ pParent = pPage->pParent; if( pParent==0 ){ Pgno pgnoChild; - Page *pChild; + MemPage *pChild; if( pPage->nCell==0 ){ if( pPage->u.hdr.rightChild ){ /* ** The root page is empty. Copy the one child page ** into the root page and return. This reduces the depth @@ -1572,11 +1622,11 @@ ** of the BTree by one. */ rc = sqlitepager_write(pPage); if( rc ) return rc; pgnoChild = pPage->u.hdr.rightChild; - rc = sqlitepager_get(pBt, pgnoChild, &pChild); + 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); reparentChildPages(pBt->pPager, pPage); @@ -1666,15 +1716,15 @@ idxDiv[i] = k; apDiv[i] = pParent->apCell[k]; nDiv++; pgnoOld[i] = apDiv[i]->h.leftChild; }else if( k==pParent->nCell ){ - pgnoOld[i] = pParent->rightChild; + pgnoOld[i] = pParent->u.hdr.rightChild; }else{ break; } - rc = sqlitepager_get(pBt, pgnoOld[i], &apOld[i]); + rc = sqlitepager_get(pBt->pPager, pgnoOld[i], (void**)&apOld[i]); if( rc ) goto balance_cleanup; nOld++; } /* @@ -1717,11 +1767,11 @@ szCell[nCell] = cellSize(apCell[nCell]); nCell++; } if( ih.leftChild==pgnoOld[i] ); apCell[nCell]->h.leftChild = pOld->u.hdr.rightChild; nCell++; @@ -1876,10 +1926,11 @@ */ int sqliteBtreeDelete(BtCursor *pCur){ MemPage *pPage = pCur->pPage; Cell *pCell; int rc; + Pgno pgnoChild; if( !pCur->pBt->inTrans ){ return SQLITE_ERROR; /* Must start a transaction first */ } if( pCur->idx >= pPage->nCell ){ @@ -1887,17 +1938,17 @@ } rc = sqlitepager_write(pPage); if( rc ) return rc; pCell = pPage->apCell[pCur->idx]; pgnoChild = pCell->h.leftChild; - clearCell(pCell); + clearCell(pCur->pBt, pCell); dropCell(pPage, pCur->idx, cellSize(pCell)); if( pgnoChild ){ /* ** If the entry we just deleted is not a leaf, then we've left a - ** whole in an internal page. We have to fill the whole by moving - ** in a page from a leaf. The next Cell after the one just deleted + ** hole in an internal page. We have to fill the hole by moving + ** in a cell from a leaf. The next Cell after the one just deleted ** is guaranteed to exist and to be a leaf so we can use it. */ BtCursor leafCur; Cell *pNext; int szNext; @@ -1904,19 +1955,20 @@ getTempCursor(pCur, &leafCur); rc = sqliteBtreeNext(&leafCur, 0); if( rc!=SQLITE_OK ){ return SQLITE_CORRUPT; } - pNext = leafCur.pPage->apCell[leafCur.idx] + pNext = leafCur.pPage->apCell[leafCur.idx]; szNext = cellSize(pNext); + pNext->h.leftChild = pgnoChild; insertCell(pPage, pCur->idx, pNext, szNext); rc = balance(pCur->pBt, pPage, pCur); if( rc ) return rc; pCur->bSkipNext = 1; dropCell(leafCur.pPage, leafCur.idx, szNext); rc = balance(pCur->pBt, leafCur.pPage, 0); - releaseTempCur(&leafCur); + releaseTempCursor(&leafCur); }else{ rc = balance(pCur->pBt, pPage, pCur); pCur->bSkipNext = 1; } return rc; @@ -1947,27 +1999,28 @@ ** the page to the freelist. */ static int clearDatabasePage(Btree *pBt, Pgno pgno){ MemPage *pPage; int rc; - int i; Cell *pCell; int idx; - rc = sqlitepager_get(pBt->pPager, pgno, &pPage); + rc = sqlitepager_get(pBt->pPager, pgno, (void**)&pPage); if( rc ) return rc; idx = pPage->u.hdr.firstCell; while( idx>0 ){ pCell = (Cell*)&pPage->u.aDisk[idx]; idx = pCell->h.iNext; if( pCell->h.leftChild ){ rc = clearDatabasePage(pBt, pCell->h.leftChild); if( rc ) return rc; } - rc = clearCell(pCell); + rc = clearCell(pBt, pCell); if( rc ) return rc; } + rc = clearDatabasePage(pBt, pPage->u.hdr.rightChild); + if( rc ) return rc; return freePage(pBt, pPage, pgno); } /* ** Delete all information from a single table in the database. @@ -1978,12 +2031,12 @@ return SQLITE_ERROR; /* Must start a transaction first */ } rc = clearDatabasePage(pBt, (Pgno)iTable); if( rc ){ sqliteBtreeRollback(pBt); - return rc; } + return rc; } /* ** Erase all information in a table and add the root of the table to ** the freelist. Except, the root of the principle table (the one on @@ -1993,11 +2046,11 @@ int rc; MemPage *pPage; if( !pBt->inTrans ){ return SQLITE_ERROR; /* Must start a transaction first */ } - rc = sqlitepager_get(pBt->pPager, (Pgno)iTable, &pPage); + rc = sqlitepager_get(pBt->pPager, (Pgno)iTable, (void**)&pPage); if( rc==SQLITE_OK ){ rc = sqliteBtreeClearTable(pBt, iTable); } if( rc==SQLITE_OK && iTable!=2 ){ rc = freePage(pBt, pPage, (Pgno)iTable); @@ -2011,11 +2064,11 @@ */ int sqliteBtreeGetMeta(Btree *pBt, int *aMeta){ PageOne *pP1; int rc; - rc = sqlitepager_get(pBt->pPager, 1, &pP1); + rc = sqlitepager_get(pBt->pPager, 1, (void**)&pP1); if( rc ) return rc; memcpy(aMeta, pP1->aMeta, sizeof(pP1->aMeta)); sqlitepager_unref(pP1); return SQLITE_OK; } @@ -2033,5 +2086,76 @@ rc = sqlitepager_write(pP1); if( rc ) return rc; memcpy(pP1->aMeta, aMeta, sizeof(pP1->aMeta)); return SQLITE_OK; } + +#ifdef SQLITE_TEST +/* +** 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 rc; + MemPage *pPage; + int i, j; + int nFree; + u16 idx; + char range[20]; + unsigned char payload[20]; + rc = sqlitepager_get(pBt->pPager, (Pgno)pgno, (void**)&pPage); + if( rc ){ + return rc; + } + i = 0; + idx = pPage->u.hdr.firstCell; + while( idx>0 && idx<=SQLITE_PAGE_SIZE-MIN_CELL_SIZE ){ + Cell *pCell = (Cell*)&pPage->u.aDisk[idx]; + int sz = cellSize(pCell); + sprintf(range,"%d..%d", idx, idx+sz-1); + if( sz>sizeof(payload)-1 ) sz = sizeof(payload)-1; + memcpy(payload, pCell->aPayload, sz); + for(j=0; j0x7f ) payload[j] = '.'; + } + payload[sz] = 0; + printf( + "cell %2d: i=%-10s chld=%-4d nk=%-3d nd=%-3d payload=%s\n", + i, range, (int)pCell->h.leftChild, pCell->h.nKey, pCell->h.nData, + pCell->aPayload + ); + idx = pCell->h.iNext; + } + if( idx!=0 ){ + printf("ERROR: next cell index out of range: %d\n", idx); + } + printf("right_child: %d\n", pPage->u.hdr.rightChild); + nFree = 0; + i = 0; + idx = pPage->u.hdr.firstFree; + while( idx>0 && idxu.aDisk[idx]; + sprintf(range,"%d..%d", idx, idx+p->iSize-1); + nFree += p->iSize; + printf("freeblock %2d: i=%-10s size=%-4d total=%d\n", + i, range, p->iSize, nFree); + idx = p->iNext; + } + if( idx!=0 ){ + printf("ERROR: next freeblock index out of range: %d\n", idx); + } + sqlitepager_unref(pPage); + return SQLITE_OK; +} +#endif + +#ifdef SQLITE_TEST +/* +** Put the page number and index of a cursor into aResult[0] and aResult[1] +** This routine is used for debugging and testing only. +*/ +int sqliteBtreeCursorDump(BtCursor *pCur, int *aResult){ + aResult[0] = sqlitepager_pagenumber(pCur->pPage); + aResult[1] = pCur->idx; + return SQLITE_OK; +} +#endif Index: src/btree.h ================================================================== --- src/btree.h +++ src/btree.h @@ -22,11 +22,11 @@ ** ************************************************************************* ** This header file defines the interface that the sqlite B-Tree file ** subsystem. ** -** @(#) $Id: btree.h,v 1.4 2001/06/08 00:21:53 drh Exp $ +** @(#) $Id: btree.h,v 1.5 2001/06/22 19:15:00 drh Exp $ */ typedef struct Btree Btree; typedef struct BtCursor BtCursor; @@ -40,11 +40,11 @@ int sqliteBtreeCreateTable(Btree*, int*); int sqliteBtreeDropTable(Btree*, int); int sqliteBtreeClearTable(Btree*, int); int sqliteBtreeCursor(Btree*, int iTable, BtCursor **ppCur); -int sqliteBtreeMoveto(BtCursor*, void *pKey, int nKey, *pRes); +int sqliteBtreeMoveto(BtCursor*, void *pKey, int nKey, int *pRes); int sqliteBtreeDelete(BtCursor*); int sqliteBtreeInsert(BtCursor*, void *pKey, int nKey, void *pData, int nData); int sqliteBtreeNext(BtCursor*, int *pRes); int sqliteBtreeKeySize(BtCursor*, int *pSize); int sqliteBtreeKey(BtCursor*, int offset, int amt, char *zBuf); @@ -53,5 +53,11 @@ int sqliteBtreeCloseCursor(BtCursor*); #define SQLITE_N_BTREE_META 3 int sqliteBtreeGetMeta(Btree*, int*); int sqliteBtreeUpdateMeta(Btree*, int*); + + +#ifdef SQLITE_TEST +int sqliteBtreePageDump(Btree*, int); +int sqliteBtreeCursorDump(BtCursor*, int*); +#endif Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -25,11 +25,11 @@ ** ** The page cache is used to access a database file. The pager journals ** 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.8 2001/06/02 02:40:57 drh Exp $ +** @(#) $Id: pager.c,v 1.9 2001/06/22 19:15:00 drh Exp $ */ #include "sqliteInt.h" #include "pager.h" #include #include @@ -560,11 +560,12 @@ /* ** Increment the reference count for a page. If the page is ** currently on the freelist (the reference count is zero) then ** remove it from the freelist. */ -static void sqlitepager_ref(PgHdr *pPg){ +int sqlitepager_ref(void *pData){ + PgHdr *pPg = DATA_TO_PGHDR(pData); if( pPg->nRef==0 ){ /* The page is currently on the freelist. Remove it. */ if( pPg->pPrevFree ){ pPg->pPrevFree->pNextFree = pPg->pNextFree; }else{ @@ -576,10 +577,11 @@ pPg->pPager->pLast = pPg->pPrevFree; } pPg->pPager->nRef++; } pPg->nRef++; + return SQLITE_OK; } /* ** Acquire a page. ** Index: src/pager.h ================================================================== --- src/pager.h +++ src/pager.h @@ -23,11 +23,11 @@ ************************************************************************* ** This header file defines the interface that the sqlite page cache ** 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.4 2001/05/24 21:06:36 drh Exp $ +** @(#) $Id: pager.h,v 1.5 2001/06/22 19:15:01 drh Exp $ */ /* ** The size of one page */ @@ -43,16 +43,17 @@ ** Each open file is managed by a separate instance of the "Pager" structure. */ typedef struct Pager Pager; int sqlitepager_open(Pager **ppPager,const char *zFilename,int nPage,int nEx); -void sqiltepager_set_destructor(Pager*, void(*)(void*)); +void sqlitepager_set_destructor(Pager*, void(*)(void*)); int sqlitepager_close(Pager *pPager); int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage); void *sqlitepager_lookup(Pager *pPager, Pgno pgno); +int sqlitepager_ref(void*); int sqlitepager_unref(void*); Pgno sqlitepager_pagenumber(void*); int sqlitepager_write(void*); int sqlitepager_pagecount(Pager*); int sqlitepager_commit(Pager*); int sqlitepager_rollback(Pager*); int *sqlitepager_stats(Pager*); Index: src/tclsqlite.c ================================================================== --- src/tclsqlite.c +++ src/tclsqlite.c @@ -21,11 +21,11 @@ ** http://www.hwaci.com/drh/ ** ************************************************************************* ** A TCL Interface to SQLite ** -** $Id: tclsqlite.c,v 1.18 2001/04/15 00:37:09 drh Exp $ +** $Id: tclsqlite.c,v 1.19 2001/06/22 19:15:01 drh Exp $ */ #ifndef NO_TCL /* Omit this whole file if TCL is unavailable */ #include "sqlite.h" #include "tcl.h" @@ -509,12 +509,14 @@ Sqlite_Init(interp); #ifdef SQLITE_TEST { extern int Sqlitetest1_Init(Tcl_Interp*); extern int Sqlitetest2_Init(Tcl_Interp*); + extern int Sqlitetest3_Init(Tcl_Interp*); Sqlitetest1_Init(interp); Sqlitetest2_Init(interp); + Sqlitetest3_Init(interp); } #endif if( argc>=2 ){ int i; Tcl_SetVar(interp,"argv0",argv[1],TCL_GLOBAL_ONLY); Index: src/test3.c ================================================================== --- src/test3.c +++ src/test3.c @@ -23,11 +23,11 @@ ************************************************************************* ** Code for testing the btree.c module in SQLite. This code ** is not included in the SQLite library. It is used for automated ** testing of the SQLite library. ** -** $Id: test3.c,v 1.1 2001/06/02 02:40:57 drh Exp $ +** $Id: test3.c,v 1.2 2001/06/22 19:15:01 drh Exp $ */ #include "sqliteInt.h" #include "pager.h" #include "btree.h" #include "tcl.h" @@ -69,12 +69,11 @@ 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 nPage; + Btree *pBt; int rc; char zBuf[100]; if( argc!=2 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " FILENAME\"", 0); @@ -153,11 +152,11 @@ 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 + Btree *pBt; int rc; if( argc!=2 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " ID\"", 0); return TCL_ERROR; @@ -237,27 +236,411 @@ void *NotUsed, Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ int argc, /* Number of arguments */ char **argv /* Text of each argument */ ){ - Pager *pPager; + Btree *pBt; int iTable; - char zBuf[100]; + int rc; if( argc!=3 ){ Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " ID TABLENUM\"", 0); return TCL_ERROR; } if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR; - if( Tcl_GetInt(interp, argv[2], &iTable ) return TCL_ERROR; + if( Tcl_GetInt(interp, argv[2], &iTable) ) return TCL_ERROR; rc = sqliteBtreeDropTable(pBt, iTable); if( rc!=SQLITE_OK ){ Tcl_AppendResult(interp, errorName(rc), 0); return TCL_ERROR; } return TCL_OK; } + +/* +** Usage: btree_get_meta ID +** +** Return meta data +*/ +static int btree_get_meta( + 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 rc; + int i; + int aMeta[SQLITE_N_BTREE_META]; + if( argc!=2 ){ + 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; + rc = sqliteBtreeGetMeta(pBt, aMeta); + if( rc!=SQLITE_OK ){ + Tcl_AppendResult(interp, errorName(rc), 0); + return TCL_ERROR; + } + for(i=0; i