diff --git a/manifest b/manifest index f5f0271a1c..af6c36a8f2 100644 --- a/manifest +++ b/manifest @@ -1,13 +1,14 @@ B d2aac001204621062e6cb3230ce2ac1b4545cb83b3ebb6bfebccee4d51162e97 -C Detect\sout-of-bounds\srootpage\svalues\sin\sthe\sschema. -D 2020-07-22T21:11:10.066 +C Merge\ssupport\sfor\spartial\sintegrity\schecks. +D 2020-07-23T09:14:25.312 F src/analyze.c 5cffff3d355858cd22bfc6e20ac7203510d2e1cc935086eb06f4abb2f579f628 -F src/btree.c a4720f51945a86379ecd962a715d6fe9de08651a67d1e6f7b4884612da83ceb5 +F src/btree.c 312780d344ab1c205b6571ef38757c7d5ea1cec539802cdd5a508381dd71be88 F src/btree.h 7af72bbb4863c331c8f6753277ab40ee67d2a2125a63256d5c25489722ec162b F src/btreeInt.h 83166f6daeb91062b6ae9ee6247b3ad07e40eba58f3c05ba9e8dedad4ab1ea38 F src/build.c 1b8436ed3ac339a0507e61b14e4bd823eb02b76a9499b2241fddc61a5ff38c1a F src/main.c eb8169cb49d36ef3481ed8f39459a4d1d61f07bd71ec26e6ee0b5da4ab73d49c F src/pager.c a5f65ff2cd73b8d381cc7b338cac382ca6978d578fa0b84fdaa11d3cdc3c3e18 +F src/pragma.c bdb600be936f66b9fe69d26dfbba4528beaaf4f95c479c85b328a92484e0bf71 F src/prepare.c 28193f0b7fc377e14682c56b10b9dd75cf7e41eb25b8ff1ce5a4536e680e1193 F src/select.c 0e75d64091200a2a8fdc02abafe176a0c2e9b2654c4cc34564f25f0b408e91de F src/sqliteInt.h ec260b2441d94ef0b5be424c323cf255ae30d23e2fb2bd1c42a3a59c2fbafedb @@ -23,10 +24,10 @@ F test/corruptL.test ddd255069ec87976587956c7afc1932005d7ee5eaf4fe426a8994d945b8 F test/dbfuzz001.test 55e1a3504f8dea84155e09912fe3b1c3ad77e0b1a938ec42ca03b8e51b321e30 F test/fts3corrupt4.test b352268a7092e5581b1c3fb29b7f19b424fefbc1edfd3bb9ee4eeb0d4beef970 F test/pager1.test 4fba160bf450cea19f6bf1d6483ef467545bac6405570e176c83c2c4b5d6d0d5 +F test/pragma.test 50b91bedea9324d3ab48e793f908ee7d2c7dcf84bfa2281e792838be59641ec8 F test/tester.tcl 6417cbb60c4169804e2e1b36ce1a840c9f33d0b0d97956e058f3cc49ed3904f0 F tool/showdb.c 49e810f5c414c792b5bf38cd5557ca9639713ebfef32aaff32faf7cb7ccce513 -P 4c5f3c6cacf84a36d0347790d98d82d1f584cd1537a13a2736348405c4d20367 d7dd4fc464c791915f646b1ad228697d1fa16f530fc7d0e9aa702c8df3068c65 -R 9f768fbf5ac24d5cfc1f04bde3af2863 -T +closed d7dd4fc464c791915f646b1ad228697d1fa16f530fc7d0e9aa702c8df3068c65 +P e4a92688fca31335bf15933dec10ecba04cf340ee2f726fd36d46d4c76660eee 65dd321432e8f80bc1cb11be8ca06656b41ac997a74a5eb271c797cf0fbb764e +R e4d9af4b2c8b3592cb28dca1e5ada87e U drh -Z 1b7e8560967702c3a371d0209708674f +Z 84df909394c97f14dff1300a1886c42c diff --git a/manifest.uuid b/manifest.uuid index dd8880ae41..e1472e3485 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e4a92688fca31335bf15933dec10ecba04cf340ee2f726fd36d46d4c76660eee \ No newline at end of file +b5443b47af95f4f0ef527bee90f186ee25faa5df197dbcc3e14d48eee095e4aa \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index 77eb7a7887..39bbf17a4d 100644 --- a/src/btree.c +++ b/src/btree.c @@ -10120,6 +10120,15 @@ end_of_check: ** allocation errors, an error message held in memory obtained from ** malloc is returned if *pnErr is non-zero. If *pnErr==0 then NULL is ** returned. If a memory allocation error occurs, NULL is returned. +** +** If the first entry in aRoot[] is 0, that indicates that the list of +** root pages is incomplete. This is a "partial integrity-check". This +** happens when performing an integrity check on a single table. The +** zero is skipped, of course. But in addition, the freelist checks +** and the checks to make sure every page is referenced are also skipped, +** since obviously it is not possible to know which pages are covered by +** the unverified btrees. Except, if aRoot[1] is 1, then the freelist +** checks are still performed. */ char *sqlite3BtreeIntegrityCheck( sqlite3 *db, /* Database connection that is running the check */ @@ -10135,6 +10144,16 @@ char *sqlite3BtreeIntegrityCheck( u64 savedDbFlags = pBt->db->flags; char zErr[100]; VVA_ONLY( int nRef ); + int bPartial = 0; /* True if not checking all btrees */ + int bCkFreelist = 1; /* True to scan the freelist */ + assert( nRoot>0 ); + + /* aRoot[0]==0 means this is a partial check */ + if( aRoot[0]==0 ){ + assert( nRoot>1 ); + bPartial = 1; + if( aRoot[1]!=1 ) bCkFreelist = 0; + } sqlite3BtreeEnter(p); assert( p->inTrans>TRANS_NONE && pBt->inTransaction>TRANS_NONE ); @@ -10174,29 +10193,33 @@ char *sqlite3BtreeIntegrityCheck( /* Check the integrity of the freelist */ - sCheck.zPfx = "Main freelist: "; - checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]), - get4byte(&pBt->pPage1->aData[36])); - sCheck.zPfx = 0; + if( bCkFreelist ){ + sCheck.zPfx = "Main freelist: "; + checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]), + get4byte(&pBt->pPage1->aData[36])); + sCheck.zPfx = 0; + } /* Check all the tables. */ #ifndef SQLITE_OMIT_AUTOVACUUM - if( pBt->autoVacuum ){ - Pgno mx = 0; - int mxInHdr; - for(i=0; (int)ipPage1->aData[52]); - if( mx!=mxInHdr ){ + if( !bPartial ){ + if( pBt->autoVacuum ){ + Pgno mx = 0; + Pgno mxInHdr; + for(i=0; (int)ipPage1->aData[52]); + if( mx!=mxInHdr ){ + checkAppendMsg(&sCheck, + "max rootpage (%d) disagrees with header (%d)", + mx, mxInHdr + ); + } + }else if( get4byte(&pBt->pPage1->aData[64])!=0 ){ checkAppendMsg(&sCheck, - "max rootpage (%d) disagrees with header (%d)", - mx, mxInHdr + "incremental_vacuum enabled with a max rootpage of zero" ); } - }else if( get4byte(&pBt->pPage1->aData[64])!=0 ){ - checkAppendMsg(&sCheck, - "incremental_vacuum enabled with a max rootpage of zero" - ); } #endif testcase( pBt->db->flags & SQLITE_CellSizeCk ); @@ -10205,7 +10228,7 @@ char *sqlite3BtreeIntegrityCheck( i64 notUsed; if( aRoot[i]==0 ) continue; #ifndef SQLITE_OMIT_AUTOVACUUM - if( pBt->autoVacuum && aRoot[i]>1 ){ + if( pBt->autoVacuum && aRoot[i]>1 && !bPartial ){ checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); } #endif @@ -10215,22 +10238,24 @@ char *sqlite3BtreeIntegrityCheck( /* Make sure every page in the file is referenced */ - for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){ + if( !bPartial ){ + for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){ #ifdef SQLITE_OMIT_AUTOVACUUM - if( getPageReferenced(&sCheck, i)==0 ){ - checkAppendMsg(&sCheck, "Page %d is never used", i); - } + if( getPageReferenced(&sCheck, i)==0 ){ + checkAppendMsg(&sCheck, "Page %d is never used", i); + } #else - /* If the database supports auto-vacuum, make sure no tables contain - ** references to pointer-map pages. - */ - if( getPageReferenced(&sCheck, i)==0 && - (PTRMAP_PAGENO(pBt, i)!=i || !pBt->autoVacuum) ){ - checkAppendMsg(&sCheck, "Page %d is never used", i); - } - if( getPageReferenced(&sCheck, i)!=0 && - (PTRMAP_PAGENO(pBt, i)==i && pBt->autoVacuum) ){ - checkAppendMsg(&sCheck, "Pointer map page %d is referenced", i); + /* If the database supports auto-vacuum, make sure no tables contain + ** references to pointer-map pages. + */ + if( getPageReferenced(&sCheck, i)==0 && + (PTRMAP_PAGENO(pBt, i)!=i || !pBt->autoVacuum) ){ + checkAppendMsg(&sCheck, "Page %d is never used", i); + } + if( getPageReferenced(&sCheck, i)!=0 && + (PTRMAP_PAGENO(pBt, i)==i && pBt->autoVacuum) ){ + checkAppendMsg(&sCheck, "Pointer map page %d is referenced", i); + } } #endif } diff --git a/src/pragma.c b/src/pragma.c index 393a3cc52a..97ad5ac90b 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1526,9 +1526,22 @@ void sqlite3Pragma( ** integrity_check designed to detect most database corruption ** without the overhead of cross-checking indexes. Quick_check ** is linear time wherease integrity_check is O(NlogN). + ** + ** The maximum nubmer of errors is 100 by default. A different default + ** can be specified using a numeric parameter N. + ** + ** Or, the parameter N can be the name of a table. In that case, only + ** the one table named is verified. The freelist is only verified if + ** the named table is "sqlite_schema" (or one of its aliases). + ** + ** All schemas are checked by default. To check just a single + ** schema, use the form: + ** + ** PRAGMA schema.integrity_check; */ case PragTyp_INTEGRITY_CHECK: { int i, j, addr, mxErr; + Table *pObjTab = 0; /* Check only this one table, if not NULL */ int isQuick = (sqlite3Tolower(zLeft[0])=='q'); @@ -1551,9 +1564,13 @@ void sqlite3Pragma( /* Set the maximum error count */ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; if( zRight ){ - sqlite3GetInt32(zRight, &mxErr); - if( mxErr<=0 ){ - mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; + if( sqlite3GetInt32(zRight, &mxErr) ){ + if( mxErr<=0 ){ + mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; + } + }else{ + pObjTab = sqlite3LocateTable(pParse, 0, zRight, + iDb>=0 ? db->aDb[iDb].zDbSName : 0); } } sqlite3VdbeAddOp2(v, OP_Integer, mxErr-1, 1); /* reg[1] holds errors left */ @@ -1582,15 +1599,21 @@ void sqlite3Pragma( Table *pTab = sqliteHashData(x); /* Current table */ Index *pIdx; /* An index on pTab */ int nIdx; /* Number of indexes on pTab */ + if( pObjTab && pObjTab!=pTab ) continue; if( HasRowid(pTab) ) cnt++; for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ cnt++; } if( nIdx>mxIdx ) mxIdx = nIdx; } + if( cnt==0 ) continue; + if( pObjTab ) cnt++; aRoot = sqlite3DbMallocRawNN(db, sizeof(int)*(cnt+1)); if( aRoot==0 ) break; - for(cnt=0, x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + cnt = 0; + if( pObjTab ) aRoot[++cnt] = 0; + for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); Index *pIdx; + if( pObjTab && pObjTab!=pTab ) continue; if( HasRowid(pTab) ) aRoot[++cnt] = pTab->tnum; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ aRoot[++cnt] = pIdx->tnum; @@ -1624,6 +1647,7 @@ void sqlite3Pragma( int r1 = -1; if( pTab->tnum<1 ) continue; /* Skip VIEWs or VIRTUAL TABLEs */ + if( pObjTab && pObjTab!=pTab ) continue; pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0, 1, 0, &iDataCur, &iIdxCur); diff --git a/test/pragma.test b/test/pragma.test index 1881a5c005..04f5bd0fbe 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -387,11 +387,15 @@ ifcapable attach { PRAGMA integrity_check=4 } } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2}} - do_test pragma-3.6 { - execsql { - PRAGMA integrity_check=xyz - } - } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} + do_catchsql_test pragma-3.6 { + PRAGMA integrity_check=xyz + } {1 {no such table: xyz}} + do_catchsql_test pragma-3.6b { + PRAGMA integrity_check=t2 + } {0 {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}}} + do_catchsql_test pragma-3.6c { + PRAGMA integrity_check=sqlite_schema + } {0 ok} do_test pragma-3.7 { execsql { PRAGMA integrity_check=0 @@ -423,7 +427,7 @@ ifcapable attach { do_test pragma-3.8.2 { execsql {PRAGMA QUICK_CHECK} } {ok} - do_test pragma-3.9 { + do_test pragma-3.9a { execsql { ATTACH 'testerr.db' AS t2; PRAGMA integrity_check @@ -432,6 +436,12 @@ ifcapable attach { Page 4 is never used Page 5 is never used Page 6 is never used} {row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} + do_execsql_test pragma-3.9b { + PRAGMA t2.integrity_check=t2; + } {{row 1 missing from index i2} {row 2 missing from index i2} {wrong # of entries in index i2}} + do_execsql_test pragma-3.9c { + PRAGMA t2.integrity_check=sqlite_schema; + } {ok} do_test pragma-3.10 { execsql { PRAGMA integrity_check=1