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

Smaller and faster PRAGMA integrity_check that also does a better job of

detecting errors.  Some output text describing discovered file corruption
has changed for clarity.

FossilOrigin-Name: 251a7590ff4f65f59a1c871892533e4e2c544515
This commit is contained in:
drh
2015-07-02 16:17:30 +00:00
parent 3f09beda45
commit cbc6b71f39
6 changed files with 168 additions and 212 deletions

View File

@@ -8932,20 +8932,30 @@ static int btreeHeapPull(u32 *aHeap, u32 *pOut){
static int checkTreePage(
IntegrityCk *pCheck, /* Context for the sanity check */
int iPage, /* Page number of the page to check */
i64 *pnParentMinKey,
i64 *pnParentMaxKey
i64 *piMinKey, /* Write minimum integer primary key here */
i64 maxKey /* Error if integer primary key greater than this */
){
MemPage *pPage = 0;
int i, rc, depth, d2, pgno, cnt;
int hdr, cellStart;
int nCell;
u8 *data;
BtShared *pBt;
int usableSize;
u32 *heap = 0;
MemPage *pPage = 0; /* The page being analyzed */
int i; /* Loop counter */
int rc; /* Result code from subroutine call */
int depth = -1, d2; /* Depth of a subtree */
int pgno; /* Page number */
int nFrag; /* Number of fragmented bytes on the page */
int hdr; /* Offset to the page header */
int cellStart; /* Offset to the start of the cell pointer array */
int nCell; /* Number of cells */
int doCoverageCheck = 1; /* True if cell coverage checking should be done */
int keyCanBeEqual = 1; /* True if IPK can be equal to maxKey
** False if IPK must be strictly less than maxKey */
u8 *data; /* Page content */
u8 *pCell; /* Cell content */
u8 *pCellIdx; /* Next element of the cell pointer array */
BtShared *pBt; /* The BtShared object that owns pPage */
u32 pc; /* Address of a cell */
u32 usableSize; /* Usable size of the page */
u32 contentOffset; /* Offset to the start of the cell content area */
u32 *heap = 0; /* Min-heap used for checking cell coverage */
u32 x, prev = 0;
i64 nMinKey = 0;
i64 nMaxKey = 0;
const char *saved_zPfx = pCheck->zPfx;
int saved_v1 = pCheck->v1;
int saved_v2 = pCheck->v2;
@@ -8961,7 +8971,6 @@ static int checkTreePage(
if( (rc = btreeGetPage(pBt, (Pgno)iPage, &pPage, 0))!=0 ){
checkAppendMsg(pCheck,
"unable to get the page. error code=%d", rc);
depth = -1;
goto end_of_check;
}
@@ -8972,41 +8981,85 @@ static int checkTreePage(
assert( rc==SQLITE_CORRUPT ); /* The only possible error from InitPage */
checkAppendMsg(pCheck,
"btreeInitPage() returns error code %d", rc);
depth = -1;
goto end_of_check;
}
data = pPage->aData;
hdr = pPage->hdrOffset;
/* Check out all the cells.
*/
depth = 0;
/* Set up for cell analysis */
pCheck->zPfx = "On tree page %d cell %d: ";
for(i=0; i<pPage->nCell && pCheck->mxErr; i++){
u8 *pCell;
u32 sz;
contentOffset = get2byteNotZero(&data[hdr+5]);
assert( contentOffset<=usableSize ); /* Enforced by btreeInitPage() */
/* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the
** number of cells on the page. */
nCell = get2byte(&data[hdr+3]);
assert( pPage->nCell==nCell );
/* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page
** immediately follows the b-tree page header. */
cellStart = hdr + 12 - 4*pPage->leaf;
assert( pPage->aCellIdx==&data[cellStart] );
pCellIdx = &data[cellStart + 2*(nCell-1)];
if( !pPage->leaf ){
/* Analyze the right-child page of internal pages */
pgno = get4byte(&data[hdr+8]);
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pBt->autoVacuum ){
pCheck->zPfx = "On page %d at right child: ";
checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage);
}
#endif
depth = checkTreePage(pCheck, pgno, &maxKey, maxKey);
keyCanBeEqual = 0;
}else{
/* For leaf pages, the coverage check will occur in the same loop
** as the other cell checks, so initialize the heap. */
heap = pCheck->heap;
heap[0] = 0;
btreeHeapInsert(heap, contentOffset-1);
}
/* EVIDENCE-OF: R-02776-14802 The cell pointer array consists of K 2-byte
** integer offsets to the cell contents. */
for(i=nCell-1; i>=0 && pCheck->mxErr; i--){
CellInfo info;
/* Check payload overflow pages
*/
/* Check cell size */
pCheck->v2 = i;
pCell = findCell(pPage,i);
pPage->xParseCell(pPage, pCell, &info);
sz = info.nPayload;
/* For intKey pages, check that the keys are in order.
*/
if( pPage->intKey ){
if( i==0 ){
nMinKey = nMaxKey = info.nKey;
}else if( info.nKey <= nMaxKey ){
checkAppendMsg(pCheck,
"Rowid %lld out of order (previous was %lld)", info.nKey, nMaxKey);
}
nMaxKey = info.nKey;
assert( pCellIdx==&data[cellStart + i*2] );
pc = get2byteAligned(pCellIdx);
pCellIdx -= 2;
if( pc<contentOffset || pc>usableSize-4 ){
checkAppendMsg(pCheck, "Offset %d out of range %d..%d",
pc, contentOffset, usableSize-4);
doCoverageCheck = 0;
continue;
}
if( (sz>info.nLocal)
&& (&pCell[info.iOverflow]<=&pPage->aData[pBt->usableSize])
){
int nPage = (sz - info.nLocal + usableSize - 5)/(usableSize - 4);
Pgno pgnoOvfl = get4byte(&pCell[info.iOverflow]);
pCell = &data[pc];
pPage->xParseCell(pPage, pCell, &info);
if( pc+info.nSize>usableSize ){
checkAppendMsg(pCheck, "Extends off end of page");
doCoverageCheck = 0;
continue;
}
/* Check for integer primary key out of range */
if( pPage->intKey ){
if( keyCanBeEqual ? (info.nKey > maxKey) : (info.nKey >= maxKey) ){
checkAppendMsg(pCheck, "Rowid %lld out of order", info.nKey);
}
maxKey = info.nKey;
}
/* Check the content overflow list */
if( info.nPayload>info.nLocal ){
int nPage; /* Number of pages on the overflow chain */
Pgno pgnoOvfl; /* First page of the overflow chain */
assert( pc + info.iOverflow <= usableSize );
nPage = (info.nPayload - info.nLocal + usableSize - 5)/(usableSize - 4);
pgnoOvfl = get4byte(&pCell[info.iOverflow]);
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pBt->autoVacuum ){
checkPtrmap(pCheck, pgnoOvfl, PTRMAP_OVERFLOW1, iPage);
@@ -9015,107 +9068,50 @@ static int checkTreePage(
checkList(pCheck, 0, pgnoOvfl, nPage);
}
/* Check sanity of left child page.
*/
if( !pPage->leaf ){
/* Check sanity of left child page for internal pages */
pgno = get4byte(pCell);
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pBt->autoVacuum ){
checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage);
}
#endif
d2 = checkTreePage(pCheck, pgno, &nMinKey, i==0?NULL:&nMaxKey);
if( i>0 && d2!=depth ){
d2 = checkTreePage(pCheck, pgno, &maxKey, maxKey);
keyCanBeEqual = 0;
if( d2!=depth ){
checkAppendMsg(pCheck, "Child page depth differs");
depth = d2;
}
depth = d2;
}
}
if( !pPage->leaf ){
pgno = get4byte(&pPage->aData[pPage->hdrOffset+8]);
pCheck->zPfx = "On page %d at right child: ";
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pBt->autoVacuum ){
checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage);
}
#endif
d2 = checkTreePage(pCheck, pgno, NULL, !pPage->nCell?NULL:&nMaxKey);
if( d2!=depth && iPage!=1 ){
checkAppendMsg(pCheck, "Child page depth differs");
}
}
/* For intKey leaf pages, check that the min/max keys are in order
** with any left/parent/right pages.
*/
pCheck->zPfx = "Page %d: ";
if( pPage->leaf && pPage->intKey ){
/* if we are a left child page */
if( pnParentMinKey ){
/* if we are the left most child page */
if( !pnParentMaxKey ){
if( nMaxKey > *pnParentMinKey ){
checkAppendMsg(pCheck,
"Rowid %lld out of order (max larger than parent min of %lld)",
nMaxKey, *pnParentMinKey);
}
}else{
if( nMinKey <= *pnParentMinKey ){
checkAppendMsg(pCheck,
"Rowid %lld out of order (min less than parent min of %lld)",
nMinKey, *pnParentMinKey);
}
if( nMaxKey > *pnParentMaxKey ){
checkAppendMsg(pCheck,
"Rowid %lld out of order (max larger than parent max of %lld)",
nMaxKey, *pnParentMaxKey);
}
*pnParentMinKey = nMaxKey;
}
/* else if we're a right child page */
} else if( pnParentMaxKey ){
if( nMinKey <= *pnParentMaxKey ){
checkAppendMsg(pCheck,
"Rowid %lld out of order (min less than parent max of %lld)",
nMinKey, *pnParentMaxKey);
}
}else{
/* Populate the coverage-checking heap for leaf pages */
btreeHeapInsert(heap, (pc<<16)|(pc+info.nSize-1));
}
}
*piMinKey = maxKey;
/* Check for complete coverage of the page
*/
data = pPage->aData;
hdr = pPage->hdrOffset;
heap = pCheck->heap;
heap[0] = 0;
pCheck->zPfx = 0;
{
int contentOffset = get2byteNotZero(&data[hdr+5]);
assert( contentOffset<=usableSize ); /* Enforced by btreeInitPage() */
btreeHeapInsert(heap, contentOffset-1);
/* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the
** number of cells on the page. */
nCell = get2byte(&data[hdr+3]);
/* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page
** immediately follows the b-tree page header. */
cellStart = hdr + 12 - 4*pPage->leaf;
/* EVIDENCE-OF: R-02776-14802 The cell pointer array consists of K 2-byte
** integer offsets to the cell contents. */
for(i=nCell-1; i>=0; i--){
u32 pc = get2byteAligned(&data[cellStart+i*2]);
u32 size = pPage->xCellSize(pPage, &data[pc]);
if( (int)(pc+size-1)>=usableSize ){
pCheck->zPfx = 0;
checkAppendMsg(pCheck,
"Corruption detected in cell %d on page %d",i,iPage);
}else{
if( doCoverageCheck && pCheck->mxErr>0 ){
/* For leaf pages, the min-heap has already been initialized and the
** cells have already been inserted. But for internal pages, that has
** not yet been done, so do it now */
if( !pPage->leaf ){
heap = pCheck->heap;
heap[0] = 0;
btreeHeapInsert(heap, contentOffset-1);
for(i=nCell-1; i>=0; i--){
u32 pc = get2byteAligned(&data[cellStart+i*2]);
u32 size = pPage->xCellSize(pPage, &data[pc]);
btreeHeapInsert(heap, (pc<<16)|(pc+size-1));
}
}
/* EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header
/* Add the freeblocks to the min-heap
**
** EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header
** is the offset of the first freeblock, or zero if there are no
** freeblocks on the page. */
** freeblocks on the page.
*/
i = get2byte(&data[hdr+1]);
while( i>0 ){
int size, j;
@@ -9134,7 +9130,10 @@ static int checkTreePage(
assert( j<=usableSize-4 ); /* Enforced by btreeInitPage() */
i = j;
}
cnt = 0;
/* Analyze the min-heap looking for overlap between cells and/or
** freeblocks, and counting the number of untracked bytes in nFrag.
*/
nFrag = 0;
assert( heap[0]>0 );
assert( (heap[1]>>16)==0 );
btreeHeapPull(heap,&prev);
@@ -9144,20 +9143,20 @@ static int checkTreePage(
"Multiple uses for byte %u of page %d", x>>16, iPage);
break;
}else{
cnt += (x>>16) - (prev&0xffff) - 1;
nFrag += (x>>16) - (prev&0xffff) - 1;
prev = x;
}
}
cnt += usableSize - (prev&0xffff) - 1;
nFrag += usableSize - (prev&0xffff) - 1;
/* EVIDENCE-OF: R-43263-13491 The total number of bytes in all fragments
** is stored in the fifth field of the b-tree page header.
** EVIDENCE-OF: R-07161-27322 The one-byte integer at offset 7 gives the
** number of fragmented free bytes within the cell content area.
*/
if( heap[0]==0 && cnt!=data[hdr+7] ){
if( heap[0]==0 && nFrag!=data[hdr+7] ){
checkAppendMsg(pCheck,
"Fragmentation of %d bytes reported as %d on page %d",
cnt, data[hdr+7], iPage);
nFrag, data[hdr+7], iPage);
}
}
@@ -9192,10 +9191,11 @@ char *sqlite3BtreeIntegrityCheck(
int *pnErr /* Write number of errors seen to this variable */
){
Pgno i;
VVA_ONLY( int nRef );
IntegrityCk sCheck;
BtShared *pBt = p->pBt;
int savedDbFlags = pBt->db->flags;
char zErr[100];
VVA_ONLY( int nRef );
sqlite3BtreeEnter(p);
assert( p->inTrans>TRANS_NONE && pBt->inTransaction>TRANS_NONE );
@@ -9239,15 +9239,19 @@ char *sqlite3BtreeIntegrityCheck(
/* Check all the tables.
*/
testcase( pBt->db->flags & SQLITE_CellSizeCk );
pBt->db->flags &= ~SQLITE_CellSizeCk;
for(i=0; (int)i<nRoot && sCheck.mxErr; i++){
i64 notUsed;
if( aRoot[i]==0 ) continue;
#ifndef SQLITE_OMIT_AUTOVACUUM
if( pBt->autoVacuum && aRoot[i]>1 ){
checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0);
}
#endif
checkTreePage(&sCheck, aRoot[i], NULL, NULL);
checkTreePage(&sCheck, aRoot[i], &notUsed, LARGEST_INT64);
}
pBt->db->flags = savedDbFlags;
/* Make sure every page in the file is referenced
*/