1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-07 02:42:48 +03:00

continued work on btree (CVS 222)

FossilOrigin-Name: d07e0e80a0b33081adda8651e9a6750b2e40141a
This commit is contained in:
drh
2001-06-02 02:40:57 +00:00
parent e339bb5fd6
commit bd03cae998
6 changed files with 668 additions and 145 deletions

View File

@@ -1,5 +1,5 @@
C :-)\s(CVS\s1720)
D 2001-05-28T00:41:24
C continued\swork\son\sbtree\s(CVS\s222)
D 2001-06-02T02:40:57
F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
F Makefile.in acef0f0275a5ca8e68bda165f7f05d810a207664
F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
@@ -12,8 +12,8 @@ F notes/notes1.txt b7c0812b704a022e88c621146ae50955c923d464
F notes/notes2.txt 7e3fafd5e25906c1fe1e95f13b089aa398ca403e
F notes/notes3.txt cd5e7bd2167d7ef89b1077abdfa68f0af6337744
F src/TODO 38a68a489e56e9fd4a96263e0ff9404a47368ad4
F src/btree.c a80c891f7bec7fb79b87136480743056be1f3fd4
F src/btree.h a0ef90514e16fab863c7825ab22724317894e1ac
F src/btree.c 3768a7dd7f2d37ef618b2dca55b8fffb76bce52b
F src/btree.h 4a50996c9bd912e8feeff28a45e936fe33f828c1
F src/build.c 4f6a2d551c56342cd4a0420654835be3ad179651
F src/dbbe.c b18259f99d87240cbe751021cf14dd3aa83a48af
F src/dbbe.h 7235b15c6c5d8be0c4da469cef9620cee70b1cc8
@@ -31,7 +31,7 @@ F src/ex/sizes.tcl f54bad4a2ac567624be59131a6ee42d71b41a3d7
F src/expr.c c4c24c3af1eba094a816522eb0e085bed518ee16
F src/insert.c aa528e20a787af85432a61daaea6df394bd251d7
F src/main.c 0a13c7a2beb8ce36aee43daf8c95989b200727a7
F src/pager.c debcf7b00e73c031c47ffc12cdeed5cb5f02b761
F src/pager.c 5224dc4b7f678af2b7e9affb933eb1cee5e7977e
F src/pager.h e527411d88e31085f07eba6776dc337b8b027921
F src/parse.y 8fc096948994a7ffbf61ba13129cc589f794a9cb
F src/printf.c b1e22a47be8cdf707815647239991e08e8cb69f9
@@ -45,6 +45,7 @@ F src/table.c adcaf074f6c1075e86359174e68701fa2acfc4d6
F src/tclsqlite.c 1f2bf4691a6bd81fbff1856ae4a12db24d1265f7
F src/test1.c abb3cb427e735ae87e6533f5b3b7164b7da91bc4
F src/test2.c 0183625225a860397b4fd3041aefb48f77e4630a
F src/test3.c a1868c55e03776f2e59f713247e77c734d8badfe
F src/tokenize.c 0118b57702cb6550769316e8443b06760b067acf
F src/update.c 0cf789656a936d4356668393267692fa4b03ffc6
F src/util.c 1b396ac34e30dd6222d82e996c17b161bbc906bc
@@ -106,7 +107,7 @@ F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f
F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f
F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2
F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad
P c8d3bdd91e333f3fc519558e40c07e7e7c2ebeec
R 94ebfe2075cd85e8e5b61fd444665d42
P d78febd197b7514b63afe6626e6639a3c3c2f0fc
R 84627a160819d6448d4e47c4eb2cbfd2
U drh
Z d7ae8aba50f98a47259bcacc564e5de1
Z 48913baed71abfbe21354883b544670d

View File

@@ -1 +1 @@
d78febd197b7514b63afe6626e6639a3c3c2f0fc
d07e0e80a0b33081adda8651e9a6750b2e40141a

View File

@@ -21,7 +21,7 @@
** http://www.hwaci.com/drh/
**
*************************************************************************
** $Id: btree.c,v 1.9 2001/05/28 00:41:15 drh Exp $
** $Id: btree.c,v 1.10 2001/06/02 02:40:57 drh Exp $
*/
#include "sqliteInt.h"
#include "pager.h"
@@ -40,7 +40,7 @@ typedef unsigned char u8;
/*
** Forward declarations of structures used only in this file.
*/
typedef struct Page1Header Page1Header;
typedef struct PageOne PageOne;
typedef struct MemPage MemPage;
typedef struct PageHdr PageHdr;
typedef struct Cell Cell;
@@ -58,26 +58,31 @@ typedef struct OverflowPage OverflowPage;
#define ROUNDUP(X) ((X+3) & ~3)
/*
** The first page of the database file contains some additional
** information used for housekeeping and sanity checking. Otherwise,
** the first page is just like any other. The additional information
** found on the first page is described by the following structure.
** This is a magic string that appears at the beginning of every
** SQLite database in order to identify the fail as a real database.
*/
struct Page1Header {
u32 magic1; /* A magic number to verify the file really is a database */
u32 magic2; /* A second magic number to be extra sure */
Pgno firstList; /* First free page in a list of all free pages */
};
#define MAGIC_1 0x7264dc61
#define MAGIC_2 0x54e55d9e
static const char zMagicHeader[] =
"** This file contains an SQLite 2.0 database **"
#define MAGIC_SIZE (sizeof(zMagicHeader))
/*
** Each database page has a header as follows:
** 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 BTree.
**
** page1_header Optional instance of Page1Header structure
** rightmost_pgno Page number of the right-most child page
** first_cell Index into MemPage.aDisk of first cell
** first_free Index of first free block
** Remember that pages are numbered beginning with 1. (See pager.c
** 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 */
};
/*
** Each database page has a header that is an instance of this
** structure.
**
** MemPage.pHdr always points to the rightmost_pgno. First_free is
** 0 if there is no free space on this page. Otherwise, first_free is
@@ -95,11 +100,15 @@ struct PageHdr {
u16 firstFree; /* Index in MemPage.aDisk[] of the first free block */
};
/*
** Entries on a page of the database are called "Cells". Each Cell
** has a header and data. This structure defines the header. The
** definition of the complete Cell including the data is given below.
** 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
** of the sizing #defines that follow.
*/
struct CellHdr {
Pgno leftChild; /* Child page that comes before this cell */
@@ -110,7 +119,7 @@ struct CellHdr {
/*
** The minimum size of a complete Cell. The Cell must contain a header
** and at least 4 bytes of data.
** and at least 4 bytes of payload.
*/
#define MIN_CELL_SIZE (sizeof(CellHdr)+4)
@@ -124,6 +133,8 @@ struct CellHdr {
** 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
** 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)))
@@ -137,7 +148,7 @@ struct CellHdr {
** page number of the first overflow page.
**
** Though this structure is fixed in size, the Cell on the database
** page varies in size. Very cell has a CellHdr and at least 4 bytes
** page varies in size. Every cell has a CellHdr and at least 4 bytes
** of payload space. Additional payload bytes (up to the maximum of
** MX_LOCAL_PAYLOAD) and the Cell.ovfl value are allocated only as
** needed.
@@ -171,7 +182,7 @@ struct FreeBlk {
** 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 Page1Header.freeList field is the
** the OverflowPage structure. The PageOne.freeList field is the
** page number of the first page in a linked list of unused database
** pages.
*/
@@ -182,27 +193,26 @@ struct OverflowPage {
/*
** For every page in the database file, an instance of the following structure
** is stored in memory. The aDisk[] array contains the data obtained from
** the disk. The rest is auxiliary data that held in memory only. The
** auxiliary data is only valid for regular database pages - the auxiliary
** data is meaningless for overflow pages and pages on the freelist.
** is stored in memory. The aDisk[] array contains the raw bits read from
** the disk. The rest is auxiliary information that held in memory only. The
** auxiliary info is only valid for regular database pages - it is not
** used for overflow pages and pages on the freelist.
**
** Of particular interest in the auxiliary data is the apCell[] entry. Each
** Of particular interest in the auxiliary info is the apCell[] entry. Each
** apCell[] entry is a pointer to a Cell structure in aDisk[]. The cells are
** put in this array so that they can be accessed in constant time, rather
** than in linear time which would be needed if we walked the linked list.
** than in linear time which would be needed if we had to walk the linked
** list on every access.
**
** The pParent field points back to the parent page. This allows us to
** walk up the BTree from any leaf to the root. Care must be taken to
** unref() the parent page pointer when this page is no longer referenced.
** The pageDestructor() routine handles that.
** The pageDestructor() routine handles that chore.
*/
struct MemPage {
char aDisk[SQLITE_PAGE_SIZE]; /* Page data stored on disk */
int isInit; /* True if auxiliary data is initialized */
MemPage *pParent; /* The parent of this page. NULL for root */
int idxStart; /* Index in aDisk[] of real data */
PageHdr *pHdr; /* Points to aDisk[idxStart] */
int nFree; /* Number of free bytes in aDisk[] */
int nCell; /* Number of entries on this page */
Cell *apCell[MX_CELL]; /* All data entires in sorted order */
@@ -221,7 +231,7 @@ struct MemPage {
struct Btree {
Pager *pPager; /* The page cache */
BtCursor *pCursor; /* A list of all open cursors */
MemPage *page1; /* First page of the database */
PageOne *page1; /* First page of the database */
int inTrans; /* True if a transaction is in progress */
};
typedef Btree Bt;
@@ -244,8 +254,8 @@ struct BtCursor {
** 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). The point of this routine is that it does not
** include payload storage on overflow pages.
** applicable). Additional spaced 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;
@@ -269,8 +279,8 @@ static void defragmentPage(MemPage *pPage){
FreeBlk *pFBlk;
char newPage[SQLITE_PAGE_SIZE];
pc = ROUNDUP(pPage->idxStart + sizeof(PageHdr));
pPage->pHdr->firstCell = pc;
pc = sizeof(PageHdr);
((PageHdr*)pPage)->firstCell = pc;
memcpy(newPage, pPage->aDisk, pc);
for(i=0; i<pPage->nCell; i++){
Cell *pCell = &pPage->apCell[i];
@@ -285,36 +295,36 @@ static void defragmentPage(MemPage *pPage){
pFBlk = &pPage->aDisk[pc];
pFBlk->iSize = SQLITE_PAGE_SIZE - pc;
pFBlk->iNext = 0;
pPage->pHdr->firstFree = pc;
((PageHdr*)pPage)->firstFree = pc;
memset(&pFBlk[1], 0, SQLITE_PAGE_SIZE - pc - sizeof(FreeBlk));
}
/*
** Allocate space on a page. The space needs to be at least
** nByte bytes in size. (Actually, all allocations are rounded
** up to the next even multiple of 4.) Return the index into
** pPage->aDisk[] of the first byte of the new allocation.
** Or return 0 if there is not enough free space on the page to
** satisfy the allocation request.
** nByte bytes in size. nByte must be a multiple of 4.
**
** Return the index into pPage->aDisk[] of the first byte of
** the new allocation. Or return 0 if there is not enough free
** space on the page to satisfy the allocation request.
**
** If the page contains nBytes of free space but does not contain
** nBytes of contiguous free space, then defragementPage() is
** called to consolidate all free space before allocating the
** new chunk.
*/
static int allocSpace(MemPage *pPage, int nByte){
static int allocateSpace(MemPage *pPage, int nByte){
FreeBlk *p;
u16 *pIdx;
int start;
assert( nByte==ROUNDUP(nByte) );
if( pPage->nFree<nByte ) return 0;
pIdx = &pPage->pHdr->firstFree;
pIdx = &((PageHdr*)pPage)->firstFree;
p = (FreeBlk*)&pPage->aDisk[*pIdx];
while( p->iSize<nByte ){
if( p->iNext==0 ){
defragmentPage(pPage);
pIdx = &pPage->pHdr->firstFree;
pIdx = &((PageHdr*)pPage)->firstFree;
}else{
pIdx = &p->iNext;
}
@@ -351,7 +361,7 @@ static void freeSpace(MemPage *pPage, int start, int size){
assert( size == ROUNDUP(size) );
assert( start == ROUNDUP(start) );
pIdx = &pPage->pHdr->firstFree;
pIdx = &((PageHdr*)pPage)->firstFree;
idx = *pIdx;
while( idx!=0 && idx<start ){
pFBlk = (FreeBlk*)&pPage->aDisk[idx];
@@ -384,7 +394,9 @@ static void freeSpace(MemPage *pPage, int start, int size){
/*
** Initialize the auxiliary information for a disk block.
**
** The pParent field is always
** The pParent parameter must be a pointer to the MemPage which
** is the parent of the page being initialized. The root of the
** BTree (page 2) has no parent and so for that page, pParent==NULL.
**
** Return SQLITE_OK on success. If we see that the page does
** not contained a well-formed database page, then return
@@ -408,15 +420,13 @@ static int initPage(MemPage *pPage, Pgno pgnoThis, MemPage *pParent){
sqlitepager_ref(pParent);
}
if( pPage->isInit ) return SQLITE_OK;
pPage->idxStart = (pgnoThis==1) ? sizeof(Page1Header) : 0;
pPage->pHdr = (PageHdr*)&pPage->aDisk[pPage->idxStart];
pPage->isInit = 1;
pPage->nCell = 0;
freeSpace = SQLITE_PAGE_SIZE - pPage->idxStart - sizeof(PageHeader);
idx = pPage->pHdr->firstCell;
freeSpace = SQLITE_PAGE_SIZE - sizeof(PageHdr);
idx = ((PageHdr*)pPage)->firstCell;
while( idx!=0 ){
if( idx>SQLITE_PAGE_SIZE-MN_CELL_SIZE ) goto page_format_error;
if( idx<pPage->idxStart + sizeof(PageHeader) ) goto page_format_error;
if( idx<sizeof(PageHdr) ) goto page_format_error;
pCell = (Cell*)&pPage->aDisk[idx];
sz = cellSize(pCell);
if( idx+sz > SQLITE_PAGE_SIZE ) goto page_format_error;
@@ -425,10 +435,10 @@ static int initPage(MemPage *pPage, Pgno pgnoThis, MemPage *pParent){
idx = pCell->h.iNext;
}
pPage->nFree = 0;
idx = pPage->pHdr->firstFree;
idx = ((PageHdr*)pPage)->firstFree;
while( idx!=0 ){
if( idx>SQLITE_PAGE_SIZE-sizeof(FreeBlk) ) goto page_format_error;
if( idx<pPage->idxStart + sizeof(PageHeader) ) goto page_format_error;
if( idx<sizeof(PageHdr) ) goto page_format_error;
pFBlk = (FreeBlk*)&pPage->aDisk[idx];
pPage->nFree += pFBlk->iSize;
if( pFBlk->iNext <= idx ) goto page_format_error;
@@ -441,6 +451,51 @@ page_format_error:
return SQLITE_CORRUPT;
}
/*
** Recompute the MemPage.apCell[], MemPage.nCell, and MemPage.nFree parameters
** for a cell after the content has be changed significantly.
**
** The computation here is similar to initPage() except that in this case
** the MemPage.aDisk[] field has been set up internally (instead of
** having been read from disk) so we do not need to do as much error
** checking.
*/
static void reinitPage(MemPage *pPage){
Cell *pCell;
pPage->nCell = 0;
idx = ((PageHdr*)pPage)->firstCell;
while( idx!=0 ){
pCell = (Cell*)&pPage->aDisk[idx];
sz = cellSize(pCell);
pPage->apCell[pPage->nCell++] = pCell;
idx = pCell->h.iNext;
}
pPage->nFree = 0;
idx = ((PageHdr*)pPage)->firstFree;
while( idx!=0 ){
pFBlk = (FreeBlk*)&pPage->aDisk[idx];
pPage->nFree += pFBlk->iSize;
idx = pFBlk->iNext;
}
return SQLITE_OK;
}
/*
** Initialize a database page so that it holds no entries at all.
*/
static void zeroPage(MemPage *pPage){
PageHdr *pHdr;
FreeBlk *pFBlk;
memset(pPage, 0, SQLITE_PAGE_SIZE);
pHdr = (PageHdr*)pPage;
pHdr->firstCell = 0;
pHdr->firstFree = sizeof(*pHdr);
pFBlk = (FreeBlk*)&pHdr[1];
pFBlk->iNext = 0;
pFBlk->iSize = SQLITE_PAGE_SIZE - sizeof(*pHdr);
}
/*
** This routine is called when the reference count for a page
** reaches zero. We need to unref the pParent pointer when that
@@ -511,15 +566,13 @@ static int lockBtree(Btree *pBt){
if( pBt->page1 ) return SQLITE_OK;
rc = sqlitepager_get(pBt->pPager, 1, &pBt->page1);
if( rc!=SQLITE_OK ) return rc;
rc = initPage(pBt->page1, 1, 0);
if( rc!=SQLITE_OK ) goto page1_init_failed;
/* Do some checking to help insure the file we opened really is
** a valid database file.
*/
if( sqlitepager_pagecount(pBt->pPager)>0 ){
Page1Header *pP1 = (Page1Header*)pBt->page1;
if( pP1->magic1!=MAGIC_1 || pP1->magic2!=MAGIC_2 ){
PageOne *pP1 = pBt->page1;
if( strcmp(pP1->zMagic1,zMagicHeader)!=0 ){
rc = SQLITE_CORRUPT;
goto page1_init_failed;
}
@@ -607,9 +660,16 @@ int sqliteBtreeCursor(Btree *pBt, BtCursor **ppCur){
}
pCur = sqliteMalloc( sizeof(*pCur) );
if( pCur==0 ){
*ppCur = 0;
unlockBtree(pBt);
return SQLITE_NOMEM;
rc = SQLITE_NOMEM;
goto create_cursor_exception;
}
rc = sqlitepager_get(pBt->pPager, 2, &pCur->pPage);
if( rc!=SQLITE_OK ){
goto create_cursor_exception;
}
rc = initPage(pCur->pPage, 2, 0);
if( rc!=SQLITE_OK ){
goto create_cursor_exception;
}
pCur->pPrev = 0;
pCur->pNext = pBt->pCursor;
@@ -618,20 +678,23 @@ int sqliteBtreeCursor(Btree *pBt, BtCursor **ppCur){
}
pBt->pCursor = pCur;
pCur->pBt = pBt;
rc = sqlitepager_get(pBt->pPager, 1, &pCur->pPage);
if( rc!=SQLITE_OK ){
sqliteFree(pCur);
*ppCur = 0;
return rc;
}
initPage(pCur->pPage, 1, 0);
pCur->idx = 0;
*ppCur = pCur;
return SQLITE_OK;
create_cursor_exception:
*ppCur = 0;
if( pCur ){
if( pCur->pPage ) sqlitepager_unref(pCur->pPage);
sqliteFree(pCur);
}
unlinkBtree(pBt);
return rc;
}
/*
** Close a cursor.
** 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;
@@ -645,9 +708,7 @@ int sqliteBtreeCloseCursor(BtCursor *pCur){
pCur->pNext->pPrev = pCur->pPrev;
}
sqlitepager_unref(pCur->pPage);
if( pBt->pCursor==0 && pBt->inTrans==0 ){
unlockBtree(pBt);
}
unlockBtree(pBt);
sqliteFree(pCur);
}
@@ -655,7 +716,7 @@ int sqliteBtreeCloseCursor(BtCursor *pCur){
** Make a temporary cursor by filling in the fields of pTempCur.
** The temporary cursor is not on the cursor list for the Btree.
*/
static void createTemporaryCursor(BtCursor *pCur, BtCursor *pTempCur){
static void CreateTemporaryCursor(BtCursor *pCur, BtCursor *pTempCur){
memcpy(pTempCur, pCur, sizeof(*pCur));
pTempCur->pNext = 0;
pTempCur->pPrev = 0;
@@ -663,17 +724,19 @@ static void createTemporaryCursor(BtCursor *pCur, BtCursor *pTempCur){
}
/*
** Delete a temporary cursor such as was made by the createTemporaryCursor()
** Delete a temporary cursor such as was made by the CreateTemporaryCursor()
** function above.
*/
static void destroyTemporaryCursor(BeCursor *pCur){
static void DestroyTemporaryCursor(BeCursor *pCur){
sqlitepager_unref(pCur->pPage);
}
/*
** Write the number of bytes of key for the entry the cursor is
** pointing to into *pSize. Return SQLITE_OK. Failure is not
** possible.
** Set *pSize to the number of bytes of key in the entry the
** cursor currently points to. Always return SQLITE_OK.
** Failure is not possible. If the cursor is not currently
** pointing to an entry (which can happen, for example, if
** the database is empty) then *pSize is set to 0.
*/
int sqliteBtreeKeySize(BtCursor *pCur, int *pSize){
Cell *pCell;
@@ -716,10 +779,9 @@ static int getPayload(BtCursor *pCur, int offset, int amt, char *zBuf){
offset += a;
zBuf += a;
amt -= a;
if( amt>0 ){
assert( a==ROUNDUP(a) );
nextPage = *(Pgno*)&aPayload[a];
}
}
if( amt>0 ){
nextPage = pCur->pPage->apCell[pCur->idx].ovfl;
}
while( amt>0 && nextPage ){
OverflowPage *pOvfl;
@@ -734,10 +796,10 @@ static int getPayload(BtCursor *pCur, int offset, int amt, char *zBuf){
a = OVERFLOW_SIZE - offset;
}
memcpy(zBuf, &pOvfl->aPayload[offset], a);
offset += a;
amt -= a;
zBuf += a;
}
offset -= OVERFLOW_SIZE;
sqlitepager_unref(pOvfl);
}
return amt==0 ? SQLITE_OK : SQLITE_CORRUPT;
@@ -764,13 +826,17 @@ int sqliteBtreeKey(BtCursor *pCur, int offset, int amt, char *zBuf){
}
pCell = pPage->apCell[pCur->idx];
if( amt+offset > pCell->h.nKey ){
return SQLITE_ERROR;
}
return getPayload(pCur, offset, amt, zBuf);
}
/*
** Write the number of bytes of data on the entry that the cursor
** is pointing to into *pSize. Return SQLITE_OK. Failure is
** not possible.
** Set *pSize to the number of bytes of data in the entry the
** cursor currently points to. Always return SQLITE_OK.
** Failure is not possible. If the cursor is not currently
** pointing to an entry (which can happen, for example, if
** the database is empty) then *pSize is set to 0.
*/
int sqliteBtreeDataSize(BtCursor *pCur, int *pSize){
Cell *pCell;
@@ -807,7 +873,9 @@ int sqliteBtreeData(BtCursor *pCur, int offset, int amt, char *zBuf){
return SQLITE_ERROR;
}
pCell = pPage->apCell[pCur->idx];
if( amt+offset > pCell->h.nKey ){
if( amt+offset > pCell->h.nData ){
return SQLITE_ERROR;
}
return getPayload(pCur, offset + pCell->h.nKey, amt, zBuf);
}
@@ -830,7 +898,7 @@ static int compareKey(BtCursor *pCur, char *pKey, int nKeyOrig, int *pResult){
assert( pCur->pPage );
assert( pCur->idx>=0 && pCur->idx<pCur->pPage->nCell );
pCell = &pCur->pPage->apCell[pCur->idx];
pCell = pCur->pPage->apCell[pCur->idx];
if( nKey > pCell->h.nKey ){
nKey = pCell->h.nKey;
}
@@ -898,17 +966,15 @@ static int moveToChild(BtCursor *pCur, int newPgno){
** pCur->idx is set to the cell index that contains the pointer
** to the page we are coming from. If we are coming from the
** right-most child page then pCur->idx is set to one more than
** the largets cell index.
** the largest cell index.
*/
static int moveToParent(BtCursor *pCur){
Pgno oldPgno;
MemPage *pParent;
pParent = pCur->pPage->pParent;
if( pParent==0 ) return SQLITE_INTERNAL;
oldPgno = sqlitepager_pagenumber(pCur->pPage);
if( pParent==0 ){
return SQLITE_INTERNAL;
}
sqlitepager_ref(pParent);
sqlitepager_unref(pCur->pPage);
pCur->pPage = pParent;
@@ -927,8 +993,10 @@ static int moveToParent(BtCursor *pCur){
*/
static int moveToRoot(BtCursor *pCur){
MemPage *pNew;
pNew = pCur->pBt->page1;
sqlitepager_ref(pNew);
int rc;
rc = sqlitepager_get(pCur->pBt->pPager, 2, &pNew);
if( rc ) return rc;
sqlitepager_unref(pCur->pPage);
pCur->pPage = pNew;
pCur->idx = 0;
@@ -955,17 +1023,23 @@ static int moveToLeftmost(BtCursor *pCur){
** Return a success code.
**
** If an exact match is not found, then the cursor is always
** left point at a root page which would hold the entry if it
** left pointing at a leaf page which would hold the entry if it
** were present. The cursor might point to an entry that comes
** before or after the key.
**
** If pRes!=NULL, then *pRes is written with an integer code to
** describe the results. *pRes is set to 0 if the cursor is left
** pointing at an entry that exactly matches pKey. *pRes is made
** negative if the cursor is on the largest entry less than pKey.
** *pRes is set positive if the cursor is on the smallest entry
** greater than pKey. *pRes is not changed if the return value
** is something other than SQLITE_OK;
** The result of comparing the key with the entry to which the
** cursor is left pointing is stored in pCur->iMatch. The same
** value is also written to *pRes if pRes!=NULL. The meaning of
** this value is as follows:
**
** *pRes<0 The cursor is left pointing at an entry that
** is larger than pKey.
**
** *pRes==0 The cursor is left pointing at an entry that
** exactly matches pKey.
**
** *pRes>0 The cursor is left pointing at an entry that
** is smaller than pKey.
*/
int sqliteBtreeMoveto(BtCursor *pCur, void *pKey, int nKey, int *pRes){
int rc;
@@ -995,7 +1069,7 @@ int sqliteBtreeMoveto(BtCursor *pCur, void *pKey, int nKey, int *pRes){
}
assert( lwr==upr+1 );
if( lwr>=pPage->nCell ){
chldPg = pPage->pHdr->rightChild;
chldPg = ((PageHdr*)pPage)->rightChild;
}else{
chldPg = pPage->apCell[lwr]->h.leftChild;
}
@@ -1007,12 +1081,14 @@ int sqliteBtreeMoveto(BtCursor *pCur, void *pKey, int nKey, int *pRes){
rc = moveToChild(pCur, chldPg);
if( rc ) return rc;
}
/* NOT REACHED */
}
/*
** Advance the cursor to the next entry in the database. If pRes!=NULL
** then set *pRes=0 on success and set *pRes=1 if the cursor was
** pointing to the last entry in the database.
** Advance the cursor to the next entry in the database. If
** successful and pRes!=NULL then set *pRes=0. If the cursor
** was already pointing to the last entry in the database before
** this routine was called, then set *pRes=1 if pRes!=NULL.
*/
int sqliteBtreeNext(BtCursor *pCur, int *pRes){
int rc;
@@ -1023,8 +1099,8 @@ int sqliteBtreeNext(BtCursor *pCur, int *pRes){
}
pCur->idx++;
if( pCur->idx>=pCur->pPage->nCell ){
if( pPage->pHdr->rightChild ){
rc = moveToChild(pCur, pPage->pHdr->rightChild);
if( ((PageHdr*)pPage)->rightChild ){
rc = moveToChild(pCur, ((PageHdr*)pPage)->rightChild);
if( rc ) return rc;
rc = moveToLeftmost(pCur);
if( rc ) return rc;
@@ -1061,7 +1137,7 @@ int sqliteBtreeNext(BtCursor *pCur, int *pRes){
** Do not invoke sqlitepager_unref() on *ppPage if an error is returned.
*/
static int allocatePage(Btree *pBt, MemPage **ppPage, Pgno *pPgno){
Page1Header *pPage1 = (Page1Header*)pBt->page1;
PageOne *pPage1 = pBt->page1;
if( pPage1->freeList ){
OverflowPage *pOvfl;
rc = sqlitepager_write(pPage1);
@@ -1093,7 +1169,7 @@ static int allocatePage(Btree *pBt, MemPage **ppPage, Pgno *pPgno){
** needs to do that.
*/
static int freePage(Btree *pBt, void *pPage, Pgno pgno){
Page1Header *pPage1 = (Page1Header*)pBt->page1;
PageOne *pPage1 = pBt->page1;
OverflowPage *pOvfl = (OverflowPage*)pPage;
int rc;
int needOvflUnref = 0;
@@ -1119,6 +1195,8 @@ static int freePage(Btree *pBt, void *pPage, Pgno pgno){
pOvfl->next = pPage1->freeList;
pPage1->freeList = pgno;
memset(pOvfl->aPayload, 0, OVERFLOW_SIZE);
pPage->isInit = 0;
assert( pPage->pParent==0 );
rc = sqlitepager_unref(pOvfl);
return rc;
}
@@ -1130,7 +1208,6 @@ static int freePage(Btree *pBt, void *pPage, Pgno pgno){
static int clearCell(Btree *pBt, Cell *pCell){
Pager *pPager = pBt->pPager;
OverflowPage *pOvfl;
Page1Header *pPage1 = (Page1Header*)pBt->page1;
Pgno ovfl, nextOvfl;
int rc;
@@ -1143,7 +1220,8 @@ static int clearCell(Btree *pBt, Cell *pCell){
rc = sqlitepager_get(pPager, ovfl, &pOvfl);
if( rc ) return rc;
nextOvfl = pOvfl->next;
freePage(pBt, pOvfl, ovfl);
rc = freePage(pBt, pOvfl, ovfl);
if( rc ) return rc;
ovfl = nextOvfl;
sqlitepager_unref(pOvfl);
}
@@ -1208,6 +1286,39 @@ static int fillInCell(
return SQLITE_OK;
}
/*
** Change the MemPage.pParent pointer on the page whose number is
** given in the second argument sot that MemPage.pParent holds the
** pointer in the third argument.
*/
static void reparentPage(Pager *pPager, Pgno pgno, MemPage *pNewParent){
MemPage *pThis;
assert( pPager!=0 && pgno!=0 );
pThis = sqlitepager_lookup(pPager, pgno);
if( pThis && pThis->pParent!=pNewParent ){
if( pThis->pParent ) sqlitepager_unref(pThis->pParent);
pThis->pParent = pNewParent;
if( pNewParent ) sqlitepager_ref(pNewParent);
}
}
/*
** Reparent all children of the given page to be the given page.
** In other words, for every child of pPage, invoke reparentPage()
** 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){
int i;
for(i=0; i<pPage->nCell; i++){
reparentPage(pPager, pPage->apCell[i]->leftChild, pPage);
}
reparentPage(pPager, ((PageHdr*)pPage)->rightChild, pPage);
}
/*
** Attempt to move N or more bytes out of the page that the cursor
** points to into the left sibling page. (The left sibling page
@@ -1222,7 +1333,7 @@ static int fillInCell(
** (3) some kind of file I/O error occurred
**
** Note that a partial rotation may have occurred even if this routine
** returns FALSE. Failure means we could not rotation a fill N bytes.
** returns FALSE. Failure means we could not rotation a full N bytes.
** If it is possible to rotation some smaller number M, then the
** rotation occurs but we still return false.
**
@@ -1267,6 +1378,36 @@ static int rotateRight(BtCursor *pCur, int N){
return 0;
}
/*
** Append a cell onto the end of a page.
**
** The child page of the cell is reparented if pPager!=NULL.
*/
static void appendCell(
Pager *pPager, /* The page cache. Needed for reparenting */
Cell *pSrc, /* The Cell to be copied onto a new page */
MemPage *pPage /* The page into which the cell is copied */
){
int pc;
int sz;
Cell *pDest;
sz = cellSize(pSrc);
pc = allocateSpace(pPage, sz);
assert( pc>0 ){
pDest = pPage->apCell[pPage->nCell] = &pPage->aDisk[pc];
memcpy(pDest, pSrc, sz);
pDest->h.iNext = 0;
if( pPage->nCell>0 ){
pPage->apCell[pPage->nCell-1]->h.iNext = pc;
}else{
((PageHdr*)pPage)->firstCell = pc;
}
if( pPager && pDest->h.leftChild ){
reparentPage(pPager, pDest->h.leftChild, pPage);
}
}
/*
** Split a single database page into two roughly equal-sized pages.
**
@@ -1291,7 +1432,73 @@ static int split(
Cell *pCenter, /* Write the cell that divides the two pages here */
MemPage **ppOut /* If not NULL, put larger cells in new page at *ppOut */
){
MemPage *pLeft, *pRight;
Pgno pgnoLeft, pgnoRight;
PageHdr *pHdr;
int rc;
Pager *pPager = pCur->pBt->pPager;
MemPage tempPage;
/* Allocate pages to hold cells after the split and make pRight and
** pLeft point to the newly allocated pages.
*/
rc = allocatePage(pCur->pBt, &pLeft, &pgnoLeft);
if( rc ) return rc;
if( ppOut ){
rc = allocatePage(pCur->pBt, &pRight, &pgnoRight);
if( rc ){
freePage(pCur->pBt, pLeft, pgnoLeft);
return rc;
}
*ppOut = pRight;
}else{
*ppOut = tempPage;
}
/* Copy the smaller cells from the original page into the left page
** of the split.
*/
zeroPage(pLeft);
if( pCur->idx==0 && pCur->match>0 ){
appendCell(pPager, pNewCell, pLeft);
}
do{
assert( i<pPage->nCell );
appendCell(pPager, pPage->apCell[i++], pLeft);
if( pCur->idx==i && pCur->iMatch>0 ){
appendCell(pPager, pNewCell, Left);
}
}while( pc < SQLITE_PAGE_SIZE/2 );
/* Copy the middle entry into *pCenter
*/
assert( i<pPage->nCell );
memcpy(pCenter, pPage->aCell[i], cellSize(pPage->aCell[i]));
i++;
pHdr = (PageHdr*)pLeft;
pHdr->rightChild = pCenter->h.leftChild;
if( pHdr->rightChild ){
reparentPage(pPager, pHdr->rightChild, pLeft);
}
pCenter->h.leftChild = pgnoLeft;
/* Copy the larger cells from the original page into the right
** page of the split
*/
zeroPage(pRight);
while( i<pPage->nCell ){
appendCell(0, pPage->apCell[i++], pRight);
}
/* If ppOut==NULL then copy the temporary right page over top of
** the original input page.
*/
if( ppOut==0 ){
pRight->pParent = pPage->pParent;
pRight->isInit = 1;
memcpy(pPage, pRight, sizeof(*pPage));
}
reparentChildPages(pPager, pPage);
}
/*
@@ -1310,6 +1517,7 @@ static void unlinkCell(BtCursor *pCur){
int i; /* Loop counter */
pPage = pCur->pPage;
sqlitepager_write(pPage);
idx = pCur->idx;
pCell = pPage->apCell[idx];
if( idx==0 ){
@@ -1333,7 +1541,7 @@ static void unlinkCell(BtCursor *pCur){
** database page that pCur points to. The calling routine has made
** sure it will fit. All this routine needs to do is add the Cell
** to the page. The addToPage() routine should be used for cases
** were it is not know if the new cell will fit.
** were it is not known if the new cell will fit.
**
** The new cell is added to the page either before or after the cell
** to which the cursor is pointing. The new cell is added before
@@ -1407,6 +1615,8 @@ static int addToPage(BtCursor *pCur, Cell *pNewCell){
for(;;){
MemPage *pPage = pCur->pPage;
rc = sqlitepager_write(pPage);
if( rc ) return rc;
int sz = cellSize(pNewCell);
if( sz<=pPage->nFree ){
insertCell(pCur, pNewCell);
@@ -1422,7 +1632,7 @@ static int addToPage(BtCursor *pCur, Cell *pNewCell){
pHdr = pPage->pHdr;
pHdr->right = sqlitepager_pagenumber(pRight);
sqlitepager_unref(pRight);
pHdr->firstCell = pc = pPage->idxStart + sizeof(*pHdr);
pHdr->firstCell = pc = sizeof(*pHdr);
sz = cellSize(&centerCell);
memcpy(&pPage->aDisk[pc], &centerCell, sz);
pc += sz;
@@ -1466,6 +1676,8 @@ int sqliteBtreeInsert(
rc = sqliteBtreeMoveTo(pCur, pKey, nKey, &loc);
if( rc ) return rc;
rc = sqlitepager_write(pCur->pPage);
if( rc ) return rc;
rc = fillInCell(pBt, &newCell, pKey, nKey, pData, nData);
if( rc ) return rc;
if( loc==0 ){
@@ -1478,23 +1690,51 @@ int sqliteBtreeInsert(
}
/*
** Check the page given as the argument to see if it is less than
** Check the page at which the cursor points to see if it is less than
** half full. If it is less than half full, then try to increase
** its fill factor by grabbing cells from siblings or by merging
** the page with siblings.
*/
static int refillPage(Btree *pBt, MemPage *pPage){
static int refillPage(BtCursor *pCur){
MemPage *pPage;
BtCursor tempCur;
int rc;
Pager *pPager;
pPage = pCur->pPage;
if( pPage->nFree < SQLITE_PAGE_SIZE/2 ){
return SQLITE_OK;
}
rc = sqlitepager_write(pPage);
if( rc ) return rc;
pPager = pCur->pBt->pPager;
if( pPage->nCell==0 ){
/* The page being refilled is the root of the BTree and it has
** no entries of its own. If there is a child page, then make the
** child become the new root.
*/
MemPage *pChild;
Pgno pgnoChild;
assert( pPage->pParent==0 );
if( pPage->pHdr->rightChild ){
assert( sqlitepager_pagenumber(pPage)==2 );
pgnoChild = ((PageHdr*)pPage)->rightChild;
if( pgnoChild==0 ){
return SQLITE_OK;
}
rc = sqlitepager_get(pPager, pgno, &pChild);
if( rc ) return rc;
memcpy(pPage, pChild, SQLITE_PAGE_SIZE);
memset(&pPage->aDisk[SQLITE_PAGE_SIZE], 0, EXTRA_SIZE);
freePage(pCur->pBt, pChild, pgnoChild);
sqlitepager_unref(pChild);
rc = initPage(pPage, 2, 0);
reparentChildPages(pPager, pPage);
return SQLITE_OK;
}
/** merge with siblings **/
/** borrow from siblings **/
}
@@ -1506,7 +1746,7 @@ static int refillPage(Btree *pBt, MemPage *pPage){
** If the size of pNewContent is greater than the current size of the
** cursor cell then the page that cursor points to might have to split.
*/
static int replaceContent(BtCursor *pCur, Cell *pNewContent){
static int ReplaceContentOfCell(BtCursor *pCur, Cell *pNewContent){
Cell *pCell; /* The cell whose content will be changed */
Pgno pgno; /* Temporary storage for a page number */
@@ -1522,19 +1762,24 @@ static int replaceContent(BtCursor *pCur, Cell *pNewContent){
}
/*
** Delete the record that the cursor is pointing to.
** Delete the entry that the cursor is pointing to.
**
** The cursor is left point at either the next or the previous
** entry. If left pointing to the next entry, then the pCur->bSkipNext
** flag is set which forces the next call to sqliteBtreeNext() to be
** a no-op. That way, you can always call sqliteBtreeNext() after
** a delete and the cursor will be left pointing to the first entry
** after the deleted entry.
** The cursor is left pointing at either the next or the previous
** entry. If the cursor is left pointing to the next entry, then
** the pCur->bSkipNext flag is set which forces the next call to
** sqliteBtreeNext() to be a no-op. That way, you can always call
** sqliteBtreeNext() after a delete and the cursor will be left
** pointing to the first entry after the deleted entry.
*/
int sqliteBtreeDelete(BtCursor *pCur){
MemPage *pPage = pCur->pPage;
Cell *pCell;
int rc;
if( pCur->idx >= pPage->nCell ){
return SQLITE_ERROR; /* The cursor is not pointing to anything */
}
rc = sqlitepager_write(pPage);
if( rc ) return rc;
pCell = pPage->apCell[pCur->idx];
if( pPage->pHdr->rightChild ){
/* The entry to be deleted is not on a leaf page. Non-leaf entries
@@ -1545,14 +1790,14 @@ int sqliteBtreeDelete(BtCursor *pCur){
** entry, then delete the next entry.
*/
BtCursor origCur;
createTemporaryCursor(pCur, &origCur);
CreateTemporaryCursor(pCur, &origCur);
rc = sqliteBtreeNext(pCur, 0);
if( rc==SQLITE_OK ){
pPage = pCur->pPage;
pCell = pPage->apCell[pCur->idx];
rc = replaceContent(&origCur, pCell);
rc = ReplaceContentOfCell(&origCur, pCell);
}
destroyTemporaryCursor(&origCur);
DestroyTemporaryCursor(&origCur);
if( rc ) return rc;
}
rc = clearCell(pCell);
@@ -1563,6 +1808,6 @@ int sqliteBtreeDelete(BtCursor *pCur){
}else{
pCur->idx--;
}
rc = refillPage(pCur->pBt, pPage);
rc = refillPage(pCur);
return rc;
}

View File

@@ -24,7 +24,7 @@
** This header file defines the interface that the sqlite B-Tree file
** subsystem.
**
** @(#) $Id: btree.h,v 1.2 2001/05/24 21:06:36 drh Exp $
** @(#) $Id: btree.h,v 1.3 2001/06/02 02:40:57 drh Exp $
*/
typedef struct Btree Btree;
@@ -37,8 +37,10 @@ int sqliteBtreeBeginTrans(Btree*);
int sqliteBtreeCommit(Btree*);
int sqliteBtreeRollback(Btree*);
int sqliteBtreeCreateTable(Btree*, int*);
int sqliteBtreeDropTable(Btree*, int);
int sqliteBtreeCursor(Btree*, BtCursor **ppCur);
int sqliteBtreeCursor(Btree*, int iTable, BtCursor **ppCur);
int sqliteBtreeMoveto(BtCursor*, void *pKey, int nKey, *pRes);
int sqliteBtreeDelete(BtCursor*);
int sqliteBtreeInsert(BtCursor*, void *pKey, int nKey, void *pData, int nData);

View File

@@ -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.7 2001/05/24 21:06:36 drh Exp $
** @(#) $Id: pager.c,v 1.8 2001/06/02 02:40:57 drh Exp $
*/
#include "sqliteInt.h"
#include "pager.h"
@@ -74,6 +74,8 @@
/*
** Each in-memory image of a page begins with the following header.
** This header is only visible to this pager module. The client
** code that calls pager sees only the data that follows the header.
*/
typedef struct PgHdr PgHdr;
struct PgHdr {

273
src/test3.c Normal file
View File

@@ -0,0 +1,273 @@
/*
** Copyright (c) 2001 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA 02111-1307, USA.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*************************************************************************
** 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 $
*/
#include "sqliteInt.h"
#include "pager.h"
#include "btree.h"
#include "tcl.h"
#include <stdlib.h>
#include <string.h>
/*
** Interpret an SQLite error number
*/
static char *errorName(int rc){
char *zName;
switch( rc ){
case SQLITE_OK: zName = "SQLITE_OK"; break;
case SQLITE_ERROR: zName = "SQLITE_ERROR"; break;
case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break;
case SQLITE_PERM: zName = "SQLITE_PERM"; break;
case SQLITE_ABORT: zName = "SQLITE_ABORT"; break;
case SQLITE_BUSY: zName = "SQLITE_BUSY"; break;
case SQLITE_NOMEM: zName = "SQLITE_NOMEM"; break;
case SQLITE_READONLY: zName = "SQLITE_READONLY"; break;
case SQLITE_INTERRUPT: zName = "SQLITE_INTERRUPT"; break;
case SQLITE_IOERR: zName = "SQLITE_IOERR"; break;
case SQLITE_CORRUPT: zName = "SQLITE_CORRUPT"; break;
case SQLITE_NOTFOUND: zName = "SQLITE_NOTFOUND"; break;
case SQLITE_FULL: zName = "SQLITE_FULL"; break;
case SQLITE_CANTOPEN: zName = "SQLITE_CANTOPEN"; break;
case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break;
default: zName = "SQLITE_Unknown"; break;
}
return zName;
}
/*
** Usage: btree_open FILENAME
**
** Open a new database
*/
static int btree_open(
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;
int rc;
char zBuf[100];
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" FILENAME\"", 0);
return TCL_ERROR;
}
rc = sqliteBtreeOpen(argv[1], 0666, &pBt);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
}
sprintf(zBuf,"0x%x",(int)pBt);
Tcl_AppendResult(interp, zBuf, 0);
return TCL_OK;
}
/*
** Usage: btree_close ID
**
** Close the given database.
*/
static int btree_close(
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;
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 = sqliteBtreeClose(pBt);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
}
return TCL_OK;
}
/*
** Usage: btree_begin_transaction ID
**
** Start a new transaction
*/
static int btree_begin_transaction(
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;
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 = sqliteBtreeBeginTrans(pBt);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
}
return TCL_OK;
}
/*
** Usage: btree_rollback ID
**
** Rollback changes
*/
static int btree_rollback(
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;
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 = sqliteBtreeRollback(pBt);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
}
return TCL_OK;
}
/*
** Usage: btree_commit ID
**
** Commit all changes
*/
static int btree_commit(
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;
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 = sqliteBtreeCommit(pBt);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
}
return TCL_OK;
}
/*
** Usage: btree_create_table ID
**
** Create a new table in the database
*/
static int btree_create_table(
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, iTable;
char zBuf[30];
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 = sqliteBtreeCreateTable(pBt, &iTable);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
}
sprintf(zBuf, "%d", iTable);
Tcl_AppendResult(interp, zBuf, 0);
return TCL_OK;
}
/*
** Usage: btree_drop_table ID TABLENUM
**
** Delete an entire table from the database
*/
static int btree_drop_table(
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;
int iTable;
char zBuf[100];
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;
rc = sqliteBtreeDropTable(pBt, iTable);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, errorName(rc), 0);
return TCL_ERROR;
}
return TCL_OK;
}
/*
** Register commands with the TCL interpreter.
*/
int Sqlitetest3_Init(Tcl_Interp *interp){
Tcl_CreateCommand(interp, "btree_open", btree_open, 0, 0);
Tcl_CreateCommand(interp, "btree_close", btree_close, 0, 0);
Tcl_CreateCommand(interp, "btree_begin_transaction",
btree_begin_transaction, 0, 0);
Tcl_CreateCommand(interp, "btree_commit", btree_commit, 0, 0);
Tcl_CreateCommand(interp, "btree_rollback", btree_rollback, 0, 0);
Tcl_CreateCommand(interp, "btree_create_table", btree_create_table, 0, 0);
Tcl_CreateCommand(interp, "btree_drop_table", btree_drop_table, 0, 0);
return TCL_OK;
}