From: drh Date: Sat, 30 Jun 2001 21:53:53 +0000 (+0000) Subject: Implemented the sqliteBtreeSanityCheck() test function. (CVS 232) X-Git-Tag: version-3.6.10~5818 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5eddca6cfc983d71814054a42dc1c658a5186b83;p=thirdparty%2Fsqlite.git Implemented the sqliteBtreeSanityCheck() test function. (CVS 232) FossilOrigin-Name: 42486880ed31318ef36a8831b959e9115b4fbac6 --- diff --git a/manifest b/manifest index ddb29e4fe1..e823fb6d44 100644 --- 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 diff --git a/manifest.uuid b/manifest.uuid index 9c251141e7..4062ecd017 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2c9127943cd5a541613924d2df773c4e8df4c1a6 \ No newline at end of file +42486880ed31318ef36a8831b959e9115b4fbac6 \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index f701535476..232fa914fe 100644 --- a/src/btree.c +++ b/src/btree.c @@ -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; inCell; 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 && iu.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 && iu.aDisk[i]; + int j; + for(j=i+pFBlk->iSize-1; j>=i; j--) hit[j]++; + i = pFBlk->iNext; + } + for(i=0; i1 ){ + 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; ipPager) ){ + 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 */ diff --git a/src/btree.h b/src/btree.h index 5229b98891..a5de692f2c 100644 --- a/src/btree.h +++ b/src/btree.h @@ -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 diff --git a/src/test3.c b/src/test3.c index e4ed48ac34..13f2ab709e 100644 --- a/src/test3.c +++ b/src/test3.c @@ -25,7 +25,7 @@ ** is not included in the SQLite library. It is used for automated ** testing of the SQLite library. ** -** $Id: test3.c,v 1.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