]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Implemented the sqliteBtreeSanityCheck() test function. (CVS 232)
authordrh <drh@noemail.net>
Sat, 30 Jun 2001 21:53:53 +0000 (21:53 +0000)
committerdrh <drh@noemail.net>
Sat, 30 Jun 2001 21:53:53 +0000 (21:53 +0000)
FossilOrigin-Name: 42486880ed31318ef36a8831b959e9115b4fbac6

manifest
manifest.uuid
src/btree.c
src/btree.h
src/test3.c
test/btree.test

index ddb29e4fe107ed0950b15253585c68134c955006..e823fb6d4498ff2107d8cfe47af79456385cd5dd 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C More\sBTree\stests\sand\sa\sfew\sbug\sfixes.\s(CVS\s231)
-D 2001-06-28T11:50:22
+C Implemented\sthe\ssqliteBtreeSanityCheck()\stest\sfunction.\s(CVS\s232)
+D 2001-06-30T21:53:53
 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4
 F Makefile.in 63bc9a6a39b7160ce8d2392ae74eb4ca4ca84c6e
 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958
@@ -12,8 +12,8 @@ F notes/notes1.txt b7c0812b704a022e88c621146ae50955c923d464
 F notes/notes2.txt 7e3fafd5e25906c1fe1e95f13b089aa398ca403e
 F notes/notes3.txt 985bf688b59f1f52bfe6e4b1f896efdeffac1432
 F src/TODO 38a68a489e56e9fd4a96263e0ff9404a47368ad4
-F src/btree.c 19c77416536851667983be43af62c631fb395a92
-F src/btree.h d327e9ad671d41d41aa2dd376c9230c8d2167c8e
+F src/btree.c 26f04c3fc1bb5bc9ba598c373cba09acaf82d6ce
+F src/btree.h 987d80658ae67f0e4d8b849539c113d4f9a7e835
 F src/build.c 4f6a2d551c56342cd4a0420654835be3ad179651
 F src/dbbe.c b18259f99d87240cbe751021cf14dd3aa83a48af
 F src/dbbe.h 7235b15c6c5d8be0c4da469cef9620cee70b1cc8
@@ -45,7 +45,7 @@ F src/table.c adcaf074f6c1075e86359174e68701fa2acfc4d6
 F src/tclsqlite.c af29a45cb4c2244a6fd032568a22d26516472b2c
 F src/test1.c abb3cb427e735ae87e6533f5b3b7164b7da91bc4
 F src/test2.c 0183625225a860397b4fd3041aefb48f77e4630a
-F src/test3.c b55fd9d2af55b29e0a906851bb09de3006a28629
+F src/test3.c 6b5a099476ab96e7bca8bb6c48bc28700157a314
 F src/tokenize.c 0118b57702cb6550769316e8443b06760b067acf
 F src/update.c 0cf789656a936d4356668393267692fa4b03ffc6
 F src/util.c 1b396ac34e30dd6222d82e996c17b161bbc906bc
@@ -53,7 +53,7 @@ F src/vdbe.c f93be4414ba892df9c5589815d2a57c1fb12c820
 F src/vdbe.h dc1205da434c6a9da03b5d6b089270bbc8e6d437
 F src/where.c 0c542fc44bd85152dfb8507862cfe2e60c629e9f
 F test/all.test 21d55a97e39e7ec5776751dc9dd8b1b51ef4a048
-F test/btree.test 6299ba795987b28fddd62e0869211c97ba311bcc
+F test/btree.test 2463425e01ef94ec123fdbfb0dcae33f5303d5b1
 F test/copy.test b77a1214bd7756f2849d5c4fa6e715c0ff0c34eb
 F test/dbbe.test a022fe2d983848f786e17ef1fc6809cfd37fb02c
 F test/delete.test 50b9b1f06c843d591741dba7869433a105360dbf
@@ -108,7 +108,7 @@ F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f
 F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f
 F www/tclsqlite.tcl 06f81c401f79a04f2c5ebfb97e7c176225c0aef2
 F www/vdbe.tcl 0c8aaa529dd216ccbf7daaabd80985e413d5f9ad
-P 9cfeeb5896d2a17c8c7904136d346a6245c9e497
-R 143955071809794a38422b6ba77e94d9
+P 2c9127943cd5a541613924d2df773c4e8df4c1a6
+R 28adcf006d91ae47e28b3894b3bf7acf
 U drh
-Z c80cdc0e1cd9e727a881d0fcbcd59b2a
+Z a0a81713c9302268fb4ff7274344753c
index 9c251141e7217def4a31430e476cfa4f70049851..4062ecd0170b989a90cab3b68bd5a9a3608205f1 100644 (file)
@@ -1 +1 @@
-2c9127943cd5a541613924d2df773c4e8df4c1a6
\ No newline at end of file
+42486880ed31318ef36a8831b959e9115b4fbac6
\ No newline at end of file
index f70153547677ef4eb686d508d8926cad1e90d147..232fa914fe40ed32faa8557517846416b5194127 100644 (file)
@@ -21,7 +21,7 @@
 **   http://www.hwaci.com/drh/
 **
 *************************************************************************
-** $Id: btree.c,v 1.17 2001/06/28 11:50:22 drh Exp $
+** $Id: btree.c,v 1.18 2001/06/30 21:53:53 drh Exp $
 **
 ** This file implements a external (disk-based) database using BTrees.
 ** For a detailed discussion of BTrees, refer to
@@ -2142,7 +2142,15 @@ int sqliteBtreeUpdateMeta(Btree *pBt, int *aMeta){
   return SQLITE_OK;
 }
 
+/******************************************************************************
+** The complete implementation of the BTree subsystem is above this line.
+** All the code the follows is for testing and troubleshooting the BTree
+** subsystem.  None of the code that follows is used during normal operation.
+** All of the following code is omitted unless the library is compiled with
+** the -DSQLITE_TEST=1 compiler option.
+******************************************************************************/
 #ifdef SQLITE_TEST
+
 /*
 ** Print a disassembly of the given page on standard output.  This routine
 ** is used for debugging and testing only.
@@ -2205,9 +2213,7 @@ int sqliteBtreePageDump(Btree *pBt, int pgno){
   sqlitepager_unref(pPage);
   return SQLITE_OK;
 }
-#endif
 
-#ifdef SQLITE_TEST
 /*
 ** Fill aResult[] with information about the entry and page that the
 ** cursor is pointing to.
@@ -2220,6 +2226,8 @@ int sqliteBtreePageDump(Btree *pBt, int pgno){
 **   aResult[5] =  Number of free blocks on the page
 **   aResult[6] =  Page number of the left child of this entry
 **   aResult[7] =  Page number of the right child for the whole page
+**
+** This routine is used for testing and debugging only.
 */
 int sqliteBtreeCursorDump(BtCursor *pCur, int *aResult){
   int cnt, idx;
@@ -2245,13 +2253,285 @@ int sqliteBtreeCursorDump(BtCursor *pCur, int *aResult){
   aResult[7] = pPage->u.hdr.rightChild;
   return SQLITE_OK;
 }
-#endif
 
-#ifdef SQLITE_TEST
 /*
-** Return the pager associated with a BTree
+** Return the pager associated with a BTree.  This routine is used for
+** testing and debugging only.
 */
 Pager *sqliteBtreePager(Btree *pBt){
   return pBt->pPager;
 }
-#endif
+
+/*
+** This structure is passed around through all the sanity checking routines
+** in order to keep track of some global state information.
+*/
+typedef struct SanityCheck SanityCheck;
+struct SanityCheck {
+  Btree *pBt;    // The tree being checked out
+  Pager *pPager; // The associated pager.  Also accessible by pBt->pPager
+  int nPage;     // Number of pages in the database
+  int *anRef;    // Number of times each page is referenced
+  char *zErrMsg; // An error message.  NULL of no errors seen.
+};
+
+/*
+** Append a message to the error message string.
+*/
+static void checkAppendMsg(SanityCheck *pCheck, char *zMsg1, char *zMsg2){
+  if( pCheck->zErrMsg ){
+    char *zOld = pCheck->zErrMsg;
+    pCheck->zErrMsg = 0;
+    sqliteSetString(&pCheck->zErrMsg, zOld, "\n", zMsg1, zMsg2, 0);
+    sqliteFree(zOld);
+  }else{
+    sqliteSetString(&pCheck->zErrMsg, zMsg1, zMsg2, 0);
+  }
+}
+
+/*
+** Add 1 to the reference count for page iPage.  If this is the second
+** reference to the page, add an error message to pCheck->zErrMsg.
+** Return 1 if there are 2 ore more references to the page and 0 if
+** if this is the first reference to the page.
+**
+** Also check that the page number is in bounds.
+*/
+static int checkRef(SanityCheck *pCheck, int iPage, char *zContext){
+  if( iPage==0 ) return 1;
+  if( iPage>pCheck->nPage ){
+    char zBuf[100];
+    sprintf(zBuf, "invalid page number %d", iPage);
+    checkAppendMsg(pCheck, zContext, zBuf);
+    return 1;
+  }
+  if( pCheck->anRef[iPage]==1 ){
+    char zBuf[100];
+    sprintf(zBuf, "2nd reference to page %d", iPage);
+    checkAppendMsg(pCheck, zContext, zBuf);
+    return 1;
+  }
+  return  (pCheck->anRef[iPage]++)>1;
+}
+
+/*
+** Check the integrity of the freelist or of an overflow page list.
+** Verify that the number of pages on the list is N.
+*/
+static void checkList(SanityCheck *pCheck, int iPage, int N, char *zContext){
+  char zMsg[100];
+  while( N-- ){
+    OverflowPage *pOvfl;
+    if( iPage<1 ){
+      sprintf(zMsg, "%d pages missing from overflow list", N+1);
+      checkAppendMsg(pCheck, zContext, zMsg);
+      break;
+    }
+    if( checkRef(pCheck, iPage, zContext) ) break;
+    if( sqlitepager_get(pCheck->pPager, (Pgno)iPage, (void**)&pOvfl) ){
+      sprintf(zMsg, "failed to get page %d", iPage);
+      checkAppendMsg(pCheck, zContext, zMsg);
+      break;
+    }
+    iPage = (int)pOvfl->iNext;
+    sqlitepager_unref(pOvfl);
+  }
+}
+
+/*
+** Do various sanity checks on a single page of a tree.  Return
+** the tree depth.  Root pages return 0.  Parents of root pages
+** return 1, and so forth.
+** 
+** These checks are done:
+**
+**      1.  Make sure that cells and freeblocks do not overlap
+**          but combine to completely cover the page.
+**      2.  Make sure cell keys are in order.
+**      3.  Make sure no key is less than or equal to zLowerBound.
+**      4.  Make sure no key is greater than or equal to zUpperBound.
+**      5.  Check the integrity of overflow pages.
+**      6.  Recursively call checkTreePage on all children.
+**      7.  Verify that the depth of all children is the same.
+**      8.  Make sure this page is at least 50% full or else it is
+**          the root of the tree.
+*/
+static int checkTreePage(
+  SanityCheck *pCheck,  /* Context for the sanity check */
+  int iPage,            /* Page number of the page to check */
+  MemPage *pParent,     /* Parent page */
+  char *zParentContext, /* Parent context */
+  char *zLowerBound,    /* All keys should be greater than this, if not NULL */
+  char *zUpperBound     /* All keys should be less than this, if not NULL */
+){
+  MemPage *pPage;
+  int i, rc, depth, d2, pgno;
+  char *zKey1, *zKey2;
+  BtCursor cur;
+  char zMsg[100];
+  char zContext[100];
+  char hit[SQLITE_PAGE_SIZE];
+
+  /* Check that the page exists
+  */
+  if( iPage==0 ) return 0;
+  if( checkRef(pCheck, iPage, zParentContext) ) return 0;
+  sprintf(zContext, "On tree page %d: ", iPage);
+  if( (rc = sqlitepager_get(pCheck->pPager, (Pgno)iPage, (void**)&pPage))!=0 ){
+    sprintf(zMsg, "unable to get the page. error code=%d", rc);
+    checkAppendMsg(pCheck, zContext, zMsg);
+    return 0;
+  }
+  if( (rc = initPage(pPage, (Pgno)iPage, pParent))!=0 ){
+    sprintf(zMsg, "initPage() returns error code %d", rc);
+    checkAppendMsg(pCheck, zContext, zMsg);
+    sqlitepager_unref(pPage);
+    return 0;
+  }
+
+  /* Check out all the cells.
+  */
+  depth = 0;
+  zKey1 = zLowerBound ? sqliteStrDup(zLowerBound) : 0;
+  cur.pPage = pPage;
+  cur.pBt = pCheck->pBt;
+  for(i=0; i<pPage->nCell; i++){
+    Cell *pCell = pPage->apCell[i];
+    int sz;
+
+    /* Check payload overflow pages
+    */
+    sz = pCell->h.nKey + pCell->h.nData;
+    sprintf(zContext, "On page %d cell %d: ", iPage, i);
+    if( sz>MX_LOCAL_PAYLOAD ){
+      int nPage = (sz - MX_LOCAL_PAYLOAD + OVERFLOW_SIZE - 1)/OVERFLOW_SIZE;
+      checkList(pCheck, pCell->ovfl, nPage, zContext);
+    }
+
+    /* Check that keys are in the right order
+    */
+    cur.idx = i;
+    zKey2 = sqliteMalloc( pCell->h.nKey+1 );
+    getPayload(&cur, 0, pCell->h.nKey, zKey2);
+    if( zKey1 && strcmp(zKey1,zKey2)>=0 ){
+      checkAppendMsg(pCheck, zContext, "Key is out of order");
+    }
+
+    /* Check sanity of left child page.
+    */
+    pgno = (int)pCell->h.leftChild;
+    d2 = checkTreePage(pCheck, pgno, pPage, zContext, zKey1, zKey2);
+    if( i>0 && d2!=depth ){
+      checkAppendMsg(pCheck, zContext, "Child page depth differs");
+    }
+    depth = d2;
+    sqliteFree(zKey1);
+    zKey1 = zKey2;
+  }
+  pgno = pPage->u.hdr.rightChild;
+  sprintf(zContext, "On page %d at right child: ", iPage);
+  checkTreePage(pCheck, pgno, pPage, zContext, zKey1, zUpperBound);
+  sqliteFree(zKey1);
+  /* Check for complete coverage of the page
+  */
+  memset(hit, 0, sizeof(hit));
+  memset(hit, 1, sizeof(PageHdr));
+  for(i=pPage->u.hdr.firstCell; i>0 && i<SQLITE_PAGE_SIZE; ){
+    Cell *pCell = (Cell*)&pPage->u.aDisk[i];
+    int j;
+    for(j=i+cellSize(pCell)-1; j>=i; j--) hit[j]++;
+    i = pCell->h.iNext;
+  }
+  for(i=pPage->u.hdr.firstFree; i>0 && i<SQLITE_PAGE_SIZE; ){
+    FreeBlk *pFBlk = (FreeBlk*)&pPage->u.aDisk[i];
+    int j;
+    for(j=i+pFBlk->iSize-1; j>=i; j--) hit[j]++;
+    i = pFBlk->iNext;
+  }
+  for(i=0; i<SQLITE_PAGE_SIZE; i++){
+    if( hit[i]==0 ){
+      sprintf(zMsg, "Unused space at byte %d of page %d", i, iPage);
+      checkAppendMsg(pCheck, zMsg, 0);
+      break;
+    }else if( hit[i]>1 ){
+      sprintf(zMsg, "Multiple uses for byte %d of page %d", i, iPage);
+      checkAppendMsg(pCheck, zMsg, 0);
+      break;
+    }
+  }
+
+  /* Check that free space is kept to a minimum
+  */
+  if( pParent && pPage->nFree>SQLITE_PAGE_SIZE/3 ){
+    sprintf(zMsg, "free space (%d) greater than max (%d)", pPage->nFree,
+       SQLITE_PAGE_SIZE/3);
+    checkAppendMsg(pCheck, zContext, zMsg);
+  }
+
+  sqlitepager_unref(pPage);
+  return depth;
+}
+
+/*
+** This routine does a complete check of the given BTree file.  aRoot[] is
+** an array of pages numbers were each page number is the root page of
+** a table.  nRoot is the number of entries in aRoot.
+**
+** If everything checks out, this routine returns NULL.  If something is
+** amiss, an error message is written into memory obtained from malloc()
+** and a pointer to that error message is returned.  The calling function
+** is responsible for freeing the error message when it is done.
+*/
+char *sqliteBtreeSanityCheck(Btree *pBt, int *aRoot, int nRoot){
+  int i;
+  int nRef;
+  SanityCheck sCheck;
+
+  nRef = *sqlitepager_stats(pBt->pPager);
+  sCheck.pBt = pBt;
+  sCheck.pPager = pBt->pPager;
+  sCheck.nPage = sqlitepager_pagecount(sCheck.pPager);
+  sCheck.anRef = sqliteMalloc( (sCheck.nPage+1)*sizeof(sCheck.anRef[0]) );
+  sCheck.anRef[1] = 1;
+  for(i=2; i<=sCheck.nPage; i++){ sCheck.anRef[i] = 0; }
+  sCheck.zErrMsg = 0;
+
+  /* Check the integrity of the freelist
+  */
+  checkList(&sCheck, pBt->page1->freeList, pBt->page1->nFree,"Main freelist: ");
+
+  /* Check all the tables.
+  */
+  for(i=0; i<nRoot; i++){
+    checkTreePage(&sCheck, aRoot[i], 0, "List of tree roots: ", 0, 0);
+  }
+
+  /* Make sure every page in the file is referenced
+  */
+  for(i=1; i<=sCheck.nPage; i++){
+    if( sCheck.anRef[i]==0 ){
+      char zBuf[100];
+      sprintf(zBuf, "Page %d is never used", i);
+      checkAppendMsg(&sCheck, zBuf, 0);
+    }
+  }
+
+  /* Make sure this analysis did not leave any unref() pages
+  */
+  if( nRef != *sqlitepager_stats(pBt->pPager) ){
+    char zBuf[100];
+    sprintf(zBuf, 
+      "Outstanding page count goes from %d to %d during this analysis",
+      nRef, *sqlitepager_stats(pBt->pPager)
+    );
+    checkAppendMsg(&sCheck, zBuf, 0);
+  }
+
+  /* Clean  up and report errors.
+  */
+  sqliteFree(sCheck.anRef);
+  return sCheck.zErrMsg;
+}
+
+#endif /* SQLITE_TEST */
index 5229b988916b1b26c503bacb492a8de144e64b4e..a5de692f2cbb686be279ceac4dbd616bc69fbae5 100644 (file)
@@ -24,7 +24,7 @@
 ** This header file defines the interface that the sqlite B-Tree file
 ** subsystem.
 **
-** @(#) $Id: btree.h,v 1.7 2001/06/28 01:54:49 drh Exp $
+** @(#) $Id: btree.h,v 1.8 2001/06/30 21:53:53 drh Exp $
 */
 
 typedef struct Btree Btree;
@@ -61,4 +61,5 @@ int sqliteBtreeUpdateMeta(Btree*, int*);
 int sqliteBtreePageDump(Btree*, int);
 int sqliteBtreeCursorDump(BtCursor*, int*);
 Pager *sqliteBtreePager(Btree*);
+char *sqliteBtreeSanityCheck(Btree*, int*, int);
 #endif
index e4ed48ac34bbfc249b63aaa90b6f2c7b67678d17..13f2ab709ebb35ab8d53e4404cabc95312c9a826 100644 (file)
@@ -25,7 +25,7 @@
 ** is not included in the SQLite library.  It is used for automated
 ** testing of the SQLite library.
 **
-** $Id: test3.c,v 1.4 2001/06/28 01:54:49 drh Exp $
+** $Id: test3.c,v 1.5 2001/06/30 21:53:53 drh Exp $
 */
 #include "sqliteInt.h"
 #include "pager.h"
@@ -413,6 +413,44 @@ static int btree_pager_ref_dump(
   return TCL_OK;
 }
 
+/*
+** Usage:   btree_sanity_check ID ROOT ...
+**
+** Look through every page of the given BTree file to verify correct
+** formatting and linkage.  Return a line of text for each problem found.
+** Return an empty string if everything worked.
+*/
+static int btree_sanity_check(
+  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;
+  char *zResult;
+  int nRoot;
+  int *aRoot;
+  int i;
+
+  if( argc<3 ){
+    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
+       " ID ROOT ...\"", 0);
+    return TCL_ERROR;
+  }
+  if( Tcl_GetInt(interp, argv[1], (int*)&pBt) ) return TCL_ERROR;
+  nRoot = argc-2;
+  aRoot = malloc( sizeof(int)*(argc-2) );
+  for(i=0; i<argc-2; i++){
+    if( Tcl_GetInt(interp, argv[i+2], &aRoot[i]) ) return TCL_ERROR;
+  }
+  zResult = sqliteBtreeSanityCheck(pBt, aRoot, nRoot);
+  if( zResult ){
+    Tcl_AppendResult(interp, zResult, 0);
+    free(zResult); 
+  }
+  return TCL_OK;
+}
+
 /*
 ** Usage:   btree_cursor ID TABLENUM
 **
@@ -738,6 +776,7 @@ int Sqlitetest3_Init(Tcl_Interp *interp){
   Tcl_CreateCommand(interp, "btree_key", btree_key, 0, 0);
   Tcl_CreateCommand(interp, "btree_data", btree_data, 0, 0);
   Tcl_CreateCommand(interp, "btree_cursor_dump", btree_cursor_dump, 0, 0);
+  Tcl_CreateCommand(interp, "btree_sanity_check", btree_sanity_check, 0, 0);
   Tcl_LinkVar(interp, "pager_refinfo_enable", (char*)&pager_refinfo_enable,
      TCL_LINK_INT);
   return TCL_OK;
index b73a29bfc558da0ba72d47aaa8d840f1e13a6a8f..3108557d98efd8374c3cf3023d7286841e91fae1 100644 (file)
@@ -23,7 +23,7 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this script is btree database backend
 #
-# $Id: btree.test,v 1.4 2001/06/28 11:50:22 drh Exp $
+# $Id: btree.test,v 1.5 2001/06/30 21:53:53 drh Exp $
 
 
 set testdir [file dirname $argv0]
@@ -990,6 +990,9 @@ do_test btree-12.12 {
   btree_next $::c1
   btree_key $::c1
 } {402}
+do_test btree-13.1 {
+  btree_sanity_check $::b1 2 3
+} {}
 
 # To Do:
 #