]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
When integrity-check finds a missing index entry, search a small range (currently...
authordan <Dan Kennedy>
Tue, 17 Mar 2026 17:05:45 +0000 (17:05 +0000)
committerdan <Dan Kennedy>
Tue, 17 Mar 2026 17:05:45 +0000 (17:05 +0000)
FossilOrigin-Name: 978cedcbd33165dadc7dffc118bde359eff6751ec9842230880cb827d9f7845f

manifest
manifest.uuid
src/pragma.c
src/vdbe.c
src/vdbeInt.h
src/vdbeaux.c
test/eiib1.test

index 46c74f91c8cd454093948d87aad5e37c7898c135..0294481c906978ad590a5c6fe2c15a84f0591552 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Do\snot\srun\sindex\shealing\son\san\sinvalid\scursor.
-D 2026-03-17T13:13:02.624
+C When\sintegrity-check\sfinds\sa\smissing\sindex\sentry,\ssearch\sa\ssmall\srange\s(currently\s10)\sof\sentries\saround\sthe\smissing\skey\sfor\san\sentry\sthat\smatches\sall\snon-virtual\sand\snon-expression\sfields.
+D 2026-03-17T17:05:45.395
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -726,7 +726,7 @@ F src/parse.y 3b784d6083380a950e3b1b32ce5ddd303e8c7c209d8ab788df2c62aaf9ee8eb3
 F src/pcache.c 588cc3c5ccaaadde689ed35ce5c5c891a1f7b1f4d1f56f6cf0143b74d8ee6484
 F src/pcache.h 092b758d2c5e4dabb30eae46d8dfad77c0f70b16bf3ff1943f7a232b0fe0d4ba
 F src/pcache1.c 131ca0daf4e66b4608d2945ae76d6ed90de3f60539afbd5ef9ec65667a5f2fcd
-F src/pragma.c ecec75795c1821520266e4f93fa8840cce48979af532db06f085e36a7813860f
+F src/pragma.c aee946eefb15d3c587528391781c8cffe7b790aacd08929dcacd5f4638b10274
 F src/prepare.c f6a6e28a281bd1d1da12f47d370a81af46159b40f73bf7fa0b276b664f9c8b7d
 F src/printf.c 9cff219dba73b1aa9a8113e83e962f03f7bea8b6eb51cefb25bc468d5a69fb2d
 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
@@ -799,11 +799,11 @@ F src/upsert.c 215328c3f91623c520ec8672c44323553f12caeb4f01b1090ebdca99fdf7b4f1
 F src/utf.c 7267c3fb9e2467020507601af3354c2446c61f444387e094c779dccd5ca62165
 F src/util.c 0dbd633bdc509a1c967e4b49b1555820494d936131017634d7dec96c0b8343ce
 F src/vacuum.c d3d35d8ae893d419ade5fa196d761a83bddcbb62137a1a157ae751ef38b26e82
-F src/vdbe.c 8442ec7a77cca0b8c465bfcc57b1f548865aec44e8cf16aa21adb6772a2803b5
+F src/vdbe.c 3feabce53a3cdac0ba0915ecc4018907bb094bfc8623304f215ad76c1ce2dcdb
 F src/vdbe.h 70e862ac8a11b590f8c1eaac17a0078429d42bc4ea3f757a9af0f451dd966a71
-F src/vdbeInt.h e876d6c5fd5f2eee4d818d49dabd615c88147a131f773edf5bd329e5b66cd365
+F src/vdbeInt.h 9909bdaaa2ef3d47b05d93b3e22a4211903305f1ba0afb902c7448258c6418e2
 F src/vdbeapi.c 6cdcbe5c7afa754c998e73d2d5d2805556268362914b952811bdfb9c78a37cf1
-F src/vdbeaux.c 35016310d8a9694c41afd2a03aa2624a36cbb4de51ad1dee7a5f1aa74f6d87c4
+F src/vdbeaux.c bc86f76fb2c3888b10f1157c3a5fa1a1ca5427981c621d4494757697f04308fa
 F src/vdbeblob.c b3f0640db9642fbdc88bd6ebcc83d6009514cafc98f062f675f2c8d505d82692
 F src/vdbemem.c 317ec5e870ddb16951b606c9fe8be22baef22ecbe46f58fdefc259662238afb7
 F src/vdbesort.c b69220f4ea9ffea5fdef34d968c60305444eea909252a81933b54c296d9cca70
@@ -1081,7 +1081,7 @@ F test/e_wal.test db7c33642711cf3c7959714b5f012aca08cacfa78da0382f95e849eb3ba66a
 F test/e_walauto.test 248af31e73c98df23476a22bdb815524c9dc3ba8
 F test/e_walckpt.test 16e7d006e8687654ee59e7ad5a6d285ba23f0fe0eeb87f790afd6bc9cf1d1924
 F test/e_walhook.test 01b494287ba9e60b70f6ebf3c6c62e0ffe01788e344a4846b08e5de0b344cb66
-F test/eiib1.test 92df1db7db9ba2024e4151019caf0d2f00b7855b548e8df4e773ec963b6fbe0c
+F test/eiib1.test fac8ac065d8d62af280dbbbf07c2bc278f6c251f4823fa341f49564a56cfdcae
 F test/emptytable.test a38110becbdfa6325cd65cb588dca658cd885f62
 F test/enc.test b5503a87b31cea8a5084c6e447383f9ca08933bd2f29d97b6b6201081b2343eb
 F test/enc2.test 872afe58db772e7dfa1ad8e0759f8cc820e9efc8172d460fae83023101c2e435
@@ -2194,8 +2194,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
 F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
-P 9d84fdb7819a9850919e2c1977811bcf514b3baad29eb8cc9fc78451d500c8b6
-R 7b31f014b775adbe80ab3ab634f5173f
-U drh
-Z 25de1929fb81984a62a4f42e91b6530b
+P eb6403f02c113f58030c0b4143d91b6327ee21dcb324d3f235c2bf6bf3d6577c
+R 12804263c5e1f22ccb67d371c61b22bd
+U dan
+Z 2465f05d9225397370c730d2cd3ac6f7
 # Remove this line to create a well-formed Fossil manifest.
index c58559d3be52beb242d70219e3413a5193d65778..d3c15bbd38c6e41ede30424a780c5a7558b658e1 100644 (file)
@@ -1 +1 @@
-eb6403f02c113f58030c0b4143d91b6327ee21dcb324d3f235c2bf6bf3d6577c
+978cedcbd33165dadc7dffc118bde359eff6751ec9842230880cb827d9f7845f
index 791508ea4aafe165c7454646d9e3da74a2217b44..ce3834a3a401146e3ca99e8490e3fa192bc987ce 100644 (file)
@@ -2058,7 +2058,7 @@ void sqlite3Pragma(
         if( !isQuick ){ /* Omit the remaining tests for quick_check */
           /* Validate index entries for the current row */
           for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){
-            int jmp2, jmp3, jmp4, jmp5, label6;
+            int jmp3, jmp4, jmp5, label6;
             int kk;
             int ckUniq = sqlite3VdbeMakeLabel(pParse);
             if( pPk==pIdx ) continue;
@@ -2067,8 +2067,11 @@ void sqlite3Pragma(
             pPrior = pIdx;
             sqlite3VdbeAddOp2(v, OP_AddImm, 8+j, 1);/* increment entry count */
             /* Verify that an index entry exists for the current table row */
-            jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1,
+            sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1,
                                         pIdx->nColumn); VdbeCoverage(v);
+            sqlite3VdbeAddOp3(v, OP_IFindKey, iIdxCur+j, ckUniq, r1); 
+            VdbeCoverage(v);
+            sqlite3VdbeChangeP4(v, -1, (const char*)pIdx, P4_INDEX);
             sqlite3VdbeLoadString(v, 3, "row ");
             sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3);
             sqlite3VdbeLoadString(v, 4, " missing from index ");
@@ -2076,7 +2079,7 @@ void sqlite3Pragma(
             jmp5 = sqlite3VdbeLoadString(v, 4, pIdx->zName);
             sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3);
             jmp4 = integrityCheckResultRow(v);
-            sqlite3VdbeJumpHere(v, jmp2);
+            sqlite3VdbeResolveLabel(v, ckUniq);
 
             /* The OP_IdxRowid opcode is an optimized version of OP_Column
             ** that extracts the rowid off the end of the index record.
index b643f1e1e949342f90930ce5af628e6072401c52..3815426889e2b0ae74f13bc0d5b5e4692f38dcea 100644 (file)
@@ -6656,7 +6656,7 @@ case OP_IdxDelete: {
   rc = sqlite3BtreeIndexMoveto(pCrsr, &r, &res);
   if( rc ) goto abort_due_to_error;
   if( res!=0 ){
-    rc = sqlite3VdbeFindDeleteKey(pCrsr, pOp->p4.pIdx, &r, &res);
+    rc = sqlite3VdbeFindIndexKey(pCrsr, pOp->p4.pIdx, 1, &r, &res);
     if( rc!=SQLITE_OK ) goto abort_due_to_error;
     if( res!=0 ){
       if( !sqlite3WritableSchema(db) ){
@@ -7297,6 +7297,40 @@ case OP_IntegrityCk: {
   sqlite3VdbeChangeEncoding(pIn1, encoding);
   goto check_for_interrupt;
 }
+
+/* Opcode: IFindKey P1 P2 P3 P4 *
+**
+** This instruction always follows an OP_Found with the same P1, P2 and P3
+** values as this instruction, and a non-zero P4 value. The P4 value to
+** this opcode is always of type P4_INDEX.
+**
+** This opcode uses sqlite3VdbeFindIndexKey() to search around the current
+** location for an index key for which all fields that are not indexed
+** expressions or virtual columns match the expected values from the table.
+** If one is found, jump to P2. Otherwise, fall through.
+*/
+case OP_IFindKey: {     /* jump, in3 */
+  VdbeCursor *pC;
+  int res;
+  UnpackedRecord r;
+
+  pC = p->apCsr[pOp->p1];
+  assert( pC->eCurType==CURTYPE_BTREE );
+  assert( pC->uc.pCursor!=0 );
+  assert( pC->isTable==0 );
+
+  memset(&r, 0, sizeof(r));
+  r.aMem = &aMem[pOp->p3];
+  r.nField = pOp->p4.pIdx->nColumn;
+  r.pKeyInfo = pC->pKeyInfo;
+
+  rc = sqlite3VdbeFindIndexKey(pC->uc.pCursor, pOp->p4.pIdx, 0, &r, &res);
+  if( res==0 ){
+    pC->nullRow = 0;
+    goto jump_to_p2;
+  }
+  break;
+};
 #endif /* SQLITE_OMIT_INTEGRITY_CHECK */
 
 /* Opcode: RowSetAdd P1 P2 * * *
index 57f4714155c5de7ff6728f7f2cbdef3d09a66b17..1e23f0e8590878faaeee1a4a2c0c3268abce9f4d 100644 (file)
@@ -687,7 +687,7 @@ void sqlite3VdbePreUpdateHook(
     Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int,int);
 #endif
 int sqlite3VdbeTransferError(Vdbe *p);
-int sqlite3VdbeFindDeleteKey(BtCursor*, Index*, UnpackedRecord*, int*);
+int sqlite3VdbeFindIndexKey(BtCursor*, Index*, int, UnpackedRecord*, int*);
 
 int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *);
 void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *);
index 2fbf4f21a95ffe8dfbf39b0c775c235fe326088b..4d0b3f29c8f354930a49541ccd2b222ff64491ed 100644 (file)
@@ -5404,7 +5404,7 @@ void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){
 ** greater than zero if the remaining fields of the cursor cursor key are less
 ** than, equal to or greater than those in (*p).
 */
-static int vdbeIsDeleteKey(
+static int vdbeIsMatchingIndexKey(
   BtCursor *pCur,                 /* Cursor open on index */
   Bitmask mask,
   UnpackedRecord *p,              /* Index key being deleted */
@@ -5484,9 +5484,10 @@ static int vdbeIsDeleteKey(
 **
 **   * If the above fails to find an entry to delete, search the entire index.
 */
-int sqlite3VdbeFindDeleteKey(
+int sqlite3VdbeFindIndexKey(
   BtCursor *pCur, 
   Index *pIdx,
+  int bExhaustive,
   UnpackedRecord *p, 
   int *pRes
 ){
@@ -5536,7 +5537,7 @@ int sqlite3VdbeFindDeleteKey(
     ** current cursor entry if (nStep>=0), or the entire index if (nStep<0).  */
     while( sqlite3BtreeCursorIsValidNN(pCur) ){
       for(ii=0; rc==SQLITE_OK && (ii<nStep || nStep<0); ii++){
-        rc = vdbeIsDeleteKey(pCur, mask, p, &res);
+        rc = vdbeIsMatchingIndexKey(pCur, mask, p, &res);
         if( res==0 || rc!=SQLITE_OK ) break;
         rc = sqlite3BtreeNext(pCur, 0);
       }
@@ -5544,7 +5545,7 @@ int sqlite3VdbeFindDeleteKey(
         rc = SQLITE_OK;
         assert( res!=0 );
       }
-      if( nStep<0 || rc!=SQLITE_OK || res==0 ) break;
+      if( nStep<0 || rc!=SQLITE_OK || res==0 || bExhaustive==0 ) break;
   
       /* The first, non-exhaustive, search failed to find an entry with 
       ** matching PK fields. So restart for an exhaustive search of the 
index c1a0d2a2fbef09844b0d8fa341cad870383494d1..48e1dfd6ab81a8f080f12b73b64d6253540a2ffd 100644 (file)
@@ -31,6 +31,13 @@ do_execsql_test 1.0.1 {
   CREATE INDEX i1 ON t1((b+0.0));
 }
 
+# Return a list of rowids from table t1 for which there is no exact
+# match in the index.
+set idxcheck {
+  SELECT rowid FROM t1 AS o NOT INDEXED 
+  WHERE NOT EXISTS (SELECT 1 FROM t1 WHERE +a=o.a AND b+0.0=o.b+0.0)
+}
+
 set root [db one {SELECT rootpage FROM sqlite_schema WHERE name='i1'}]
 
 do_test 1.0.2 {
@@ -46,29 +53,33 @@ do_test 1.0.2 {
 
 do_execsql_test 1.1.1 {
   UPDATE x1 SET b=21.0 WHERE rowid=20;
-  PRAGMA integrity_check;
-} {
-  {row 3 missing from index i1}
 }
+do_execsql_test 1.1.1b {
+  PRAGMA integrity_check;
+} {ok}
+do_execsql_test 1.1.1c $idxcheck {20}
+
 do_execsql_test 1.1.2 {
   DELETE FROM t1 WHERE a=20;
 } {}
 do_execsql_test 1.1.3 {
   PRAGMA integrity_check;
 } {ok}
+do_execsql_test 1.1.4 $idxcheck {}
 
 do_execsql_test 1.2.1 {
   UPDATE x1 SET b=26.0 WHERE rowid=25;
   PRAGMA integrity_check;
-} {
-  {row 3 missing from index i1}
-}
-do_execsql_test 1.2.2 {
+} {ok}
+do_execsql_test 1.2.2 $idxcheck {25}
+
+do_execsql_test 1.2.3 {
   DELETE FROM t1 WHERE a=25;
 } {}
-do_execsql_test 1.2.3 {
+do_execsql_test 1.2.4 {
   PRAGMA integrity_check;
 } {ok}
+do_execsql_test 1.2.5 $idxcheck {}
 
 do_execsql_test 1.3.1 {
   DELETE FROM t1;
@@ -82,24 +93,21 @@ do_execsql_test 1.3.1 {
 
   UPDATE x1 SET b=19.0 WHERE b=20.0;
   PRAGMA integrity_check;
-} {
-  {row 2 missing from index i1} 
-  {row 3 missing from index i1} 
-  {row 4 missing from index i1} 
-  {row 5 missing from index i1}
-  {row 6 missing from index i1}
-}
+} {ok}
+
+do_execsql_test 1.3.2 $idxcheck {10 15 20 25 30}
 
 foreach {tn a} {
   1 15   2 30   3 20   4 10   5 25
 } {
-  do_execsql_test 1.3.2.$tn {
+  do_execsql_test 1.3.3.$tn {
     DELETE FROM t1 WHERE a=$a
   }
 }
-do_execsql_test 1.3.3 {
+do_execsql_test 1.3.4 {
   PRAGMA integrity_check
 } {ok}
+do_execsql_test 1.3.5 $idxcheck {}
 
 #-------------------------------------------------------------------------
 reset_db
@@ -113,6 +121,11 @@ do_execsql_test 2.0 {
   CREATE INDEX t1c ON t1(+c);
 }
 
+set idxcheck {
+  SELECT a, b FROM t1 AS o NOT INDEXED 
+  WHERE NOT EXISTS (SELECT 1 FROM t1 WHERE +a=o.a AND +b=o.b AND +c=o.c)
+}
+
 set root [db one {SELECT rootpage FROM sqlite_schema WHERE name='t1c'}]
 
 do_test 2.1 { 
@@ -127,13 +140,21 @@ do_execsql_test 2.2 {
   UPDATE x1 SET c=hex(randomblob(50)) WHERE (a%2)!=0
 }
 
-do_test 2.3 {
+do_execsql_test 2.3 "
+  SELECT count(*) FROM ( $idxcheck )
+" [expr $nRow/2]
+
+do_test 2.4 {
   for {set ii 1} {$ii<$nRow} {incr ii 2} {
     execsql { DELETE FROM t1 WHERE a=$ii }
   }
   execsql {PRAGMA integrity_check}
 } {ok}
 
+do_execsql_test 2.5 "
+  SELECT count(*) FROM ( $idxcheck )
+" 0
+
 #-------------------------------------------------------------------------
 reset_db
 do_execsql_test 3.0 {
@@ -143,6 +164,11 @@ do_execsql_test 3.0 {
   INSERT INTO y1 VALUES(4, 5);
 }
 
+set idxcheck {
+  SELECT rowid FROM y1 AS o NOT INDEXED 
+  WHERE NOT EXISTS (SELECT 1 FROM y1 WHERE +rowid=o.rowid AND c=o.c)
+}
+
 set root [db one {SELECT rootpage FROM sqlite_schema WHERE name='i1'}]
 
 do_test 3.1 { 
@@ -155,9 +181,12 @@ do_execsql_test 3.2 {
   UPDATE x1 SET c=19 WHERE rowid=2;
 }
 
-breakpoint
-do_execsql_test 3.3 {
+do_execsql_test 3.3 $idxcheck 2 
+
+do_execsql_test 3.4 {
   DELETE FROM y1 WHERE a=4;
 }
 
+do_execsql_test 3.5 $idxcheck {}
+
 finish_test