]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
continued work on btree (CVS 222)
authordrh <drh@noemail.net>
Sat, 2 Jun 2001 02:40:57 +0000 (02:40 +0000)
committerdrh <drh@noemail.net>
Sat, 2 Jun 2001 02:40:57 +0000 (02:40 +0000)
FossilOrigin-Name: d07e0e80a0b33081adda8651e9a6750b2e40141a

manifest
manifest.uuid
src/btree.c
src/btree.h
src/pager.c
src/test3.c [new file with mode: 0644]

index 4e7f871b7838f413177bf45da8c8e79828585a2d..4c45b4d2d213dce1cae1ddd3a13113aa4cb48681 100644 (file)
--- a/manifest
+++ b/manifest
@@ -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
index 38e216b97a707d7faaa16998a2476c5298fb494e..89d796f3fb70a8a31ce11cf608fc6bade117b8a4 100644 (file)
@@ -1 +1 @@
-d78febd197b7514b63afe6626e6639a3c3c2f0fc
\ No newline at end of file
+d07e0e80a0b33081adda8651e9a6750b2e40141a
\ No newline at end of file
index 581a6cfcc126e570a4cc654b18b91e746a5f6f07..4391a36b9973855a52049e5059269b8730a1cdad 100644 (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;
 }
index 31675addfeb0881a738b2f4772c177e02732a6a7..9396a776d5e1971928102dba588d6137ee54a9f6 100644 (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);
index d582f21f80ec0fbe1de605f6f0c331301d542bfe..6915ef2776268f72b0ae33ad561993363745ad4d 100644 (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 {
diff --git a/src/test3.c b/src/test3.c
new file mode 100644 (file)
index 0000000..769218e
--- /dev/null
@@ -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;
+}