]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Modify integrity-check so that it is only tolerant of very small distortions of real...
authordan <Dan Kennedy>
Tue, 17 Mar 2026 21:17:42 +0000 (21:17 +0000)
committerdan <Dan Kennedy>
Tue, 17 Mar 2026 21:17:42 +0000 (21:17 +0000)
FossilOrigin-Name: 82e6de835bde306778425d18ab1a15ae80fdf01af7f577ead89c09f9d53b5b2f

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

index e57570c3c96e469958eff79f13115a25d8e01348..4092bacd9675c682b6daee834bdf78ccb578c8c5 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Improved\serror\shandling\sin\sOP_IFindKey\sand\sin\svdbeIsMatchingIndexKey().
-D 2026-03-17T18:55:24.081
+C Modify\sintegrity-check\sso\sthat\sit\sis\sonly\stolerant\sof\svery\ssmall\sdistortions\sof\sreal\svalues\sin\sindexed\sexpression\sfields.
+D 2026-03-17T21:17:42.003
 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 aee946eefb15d3c587528391781c8cffe7b790aacd08929dcacd5f4638b10274
+F src/pragma.c 8b7aefee546ac7c5e599aee1ced7f40b1dba55f05e06b95dec9144ea7e541ce1
 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 e0921e7046ef9f3c472b583ff8ee9a5db593d9be3570f22d44f69cb95a717dfa
+F src/vdbe.c 034ccc09f315f6d30ed95b0e6af0a4279ed1e78579343e48cad9f12f2ed9f67e
 F src/vdbe.h 70e862ac8a11b590f8c1eaac17a0078429d42bc4ea3f757a9af0f451dd966a71
-F src/vdbeInt.h 9909bdaaa2ef3d47b05d93b3e22a4211903305f1ba0afb902c7448258c6418e2
+F src/vdbeInt.h f7157f110f88f1d9d8338c292faf23a9129f6712563ade2b408537c95e17bdef
 F src/vdbeapi.c 6cdcbe5c7afa754c998e73d2d5d2805556268362914b952811bdfb9c78a37cf1
-F src/vdbeaux.c 5b586e4b08c0ff2df2aa3b011afcb2382478444306940077105a10f8f816aa79
+F src/vdbeaux.c d6acd54e3dea2373a1d902a6f54718996fd765aa3ca2a4295d4026935337340c
 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 fac8ac065d8d62af280dbbbf07c2bc278f6c251f4823fa341f49564a56cfdcae
+F test/eiib1.test 1b4e1f89c843e081b897219a1a11e7045e10170165b3e84d86ca9b8cae305357
 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 978cedcbd33165dadc7dffc118bde359eff6751ec9842230880cb827d9f7845f
-R 531962050de8b772498e3016aa53cb5c
-U drh
-Z 4452c0c9088d2f943b3f97617ed9fb76
+P f7389cdb129d3386b7dfb8acacf84816cf10864c6800a9bd9a61c6364b850a31
+R 82803ca4f78db5dcdf077da2e4845bd5
+U dan
+Z 1601c9d521a1531b56905fb2ee04daaf
 # Remove this line to create a well-formed Fossil manifest.
index 40aec4c7fa19fd1d2c722b61d139728739bf971a..58b065a702fc74d24bad895d5b1b5a7e9da0cec4 100644 (file)
@@ -1 +1 @@
-f7389cdb129d3386b7dfb8acacf84816cf10864c6800a9bd9a61c6364b850a31
+82e6de835bde306778425d18ab1a15ae80fdf01af7f577ead89c09f9d53b5b2f
index ce3834a3a401146e3ca99e8490e3fa192bc987ce..21799aaa145dd09039bbe15185ce83b0d59bb0c5 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 jmp3, jmp4, jmp5, label6;
+            int jmp2, jmp3, jmp4, jmp5, label6;
             int kk;
             int ckUniq = sqlite3VdbeMakeLabel(pParse);
             if( pPk==pIdx ) continue;
@@ -2069,9 +2069,20 @@ void sqlite3Pragma(
             /* Verify that an index entry exists for the current table row */
             sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1,
                                         pIdx->nColumn); VdbeCoverage(v);
-            sqlite3VdbeAddOp3(v, OP_IFindKey, iIdxCur+j, ckUniq, r1); 
+            jmp2 = sqlite3VdbeAddOp3(v, OP_IFindKey, iIdxCur+j, ckUniq, r1); 
             VdbeCoverage(v);
             sqlite3VdbeChangeP4(v, -1, (const char*)pIdx, P4_INDEX);
+
+            sqlite3VdbeLoadString(v, 3, "WARNING: expression index ");
+            sqlite3VdbeLoadString(v, 4, pIdx->zName);
+            sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3);
+            sqlite3VdbeLoadString(v, 4, " stores an imprecise value for row ");
+            sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3);
+            sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3);
+            integrityCheckResultRow(v);
+            sqlite3VdbeAddOp2(v, OP_Goto, 0, ckUniq);
+
+            sqlite3VdbeJumpHere(v, jmp2);
             sqlite3VdbeLoadString(v, 3, "row ");
             sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3);
             sqlite3VdbeLoadString(v, 4, " missing from index ");
index 9617ef8f5a57cc6cf75a8a8971fa2f47506da87b..32978479ad5eeb3ca7cb7579b98fd953e46f415e 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 = sqlite3VdbeFindIndexKey(pCrsr, pOp->p4.pIdx, 1, &r, &res);
+    rc = sqlite3VdbeFindIndexKey(pCrsr, pOp->p4.pIdx, &r, &res, 0);
     if( rc!=SQLITE_OK ) goto abort_due_to_error;
     if( res!=0 ){
       if( !sqlite3WritableSchema(db) ){
@@ -7307,11 +7307,12 @@ case OP_IntegrityCk: {
 ** 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.
+** If one is not found, jump to P2. Otherwise, fall through.
 */
 case OP_IFindKey: {     /* jump, in3 */
   VdbeCursor *pC;
   int res;
+  int ulp;
   UnpackedRecord r;
 
   pC = p->apCsr[pOp->p1];
@@ -7324,12 +7325,10 @@ case OP_IFindKey: {     /* jump, in3 */
   r.nField = pOp->p4.pIdx->nColumn;
   r.pKeyInfo = pC->pKeyInfo;
 
-  rc = sqlite3VdbeFindIndexKey(pC->uc.pCursor, pOp->p4.pIdx, 0, &r, &res);
+  rc = sqlite3VdbeFindIndexKey(pC->uc.pCursor, pOp->p4.pIdx, &r, &res, 1);
   if( rc ) goto abort_due_to_error;
-  if( res==0 ){
-    pC->nullRow = 0;
-    goto jump_to_p2;
-  }
+  if( res!=0 ) goto jump_to_p2;
+  pC->nullRow = 0;
   break;
 };
 #endif /* SQLITE_OMIT_INTEGRITY_CHECK */
index 1e23f0e8590878faaeee1a4a2c0c3268abce9f4d..ac31d5afa88a78ce2dcd784c6218ebf2841c7012 100644 (file)
@@ -687,7 +687,7 @@ void sqlite3VdbePreUpdateHook(
     Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int,int);
 #endif
 int sqlite3VdbeTransferError(Vdbe *p);
-int sqlite3VdbeFindIndexKey(BtCursor*, Index*, int, UnpackedRecord*, int*);
+int sqlite3VdbeFindIndexKey(BtCursor*, Index*, UnpackedRecord*, int*, int);
 
 int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *);
 void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *);
index db7f2c75d442af411bbf09ee6b1a70a238af942f..1549e55b1f3aaeb74b4a6053838302df41ac8b60 100644 (file)
@@ -5397,15 +5397,59 @@ void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){
   }
 }
 
+
+/*
+** Helper function for vdbeIsMatchingIndexKey(). Return true if column
+** iCol should be ignored when comparing a record with a record from 
+** an index on disk. The field should be ignored if:
+**
+**   * the corresponding bit in mask is set, and
+**   * either bIntegrity is false, or
+**   * the two Mem values are both real values that differ by 
+**     BTREE_ULPDISTORTION or fewer ULPs.
+*/
+static int vdbeSkipField(
+  Bitmask mask,                   /* Mask of indexed expression fields */
+  int iCol,                       /* Column of index being considered */
+  Mem *pMem1,                     /* Expected index value */
+  Mem *pMem2,                     /* Actual indexed value */
+  int bIntegrity
+){
+#define BTREE_MANTISSA64 ((u64)0x0FFF << 52)
+#define BTREE_ULPDISTORTION 2
+  if( iCol>=BMS || (mask & MASKBIT(iCol))==0 ) return 0;
+  if( bIntegrity==0 ) return 1;
+  if( (pMem1->flags & MEM_Real) && (pMem2->flags & MEM_Real) ){
+    u64 r1 = *(u64*)&pMem1->u.r;
+    u64 r2 = *(u64*)&pMem2->u.r;
+    if( (r1 & BTREE_MANTISSA64)==(r2 & BTREE_MANTISSA64) ){
+      u64 diff;
+      r1 = r1 & ~BTREE_MANTISSA64;
+      r2 = r2 & ~BTREE_MANTISSA64;
+      diff = MIN(r1-r2, r2-r1);
+      if( diff<=BTREE_ULPDISTORTION ){
+        return 1;
+      }
+    }
+  }
+  return 0;
+}
+
 /*
 ** This function compares the unpacked record with the current key that
-** cursor pCur points to, ignoring any fields for which the corresponding
-** bit in parameter "mask" is set.  Return the usual less than zero, zero, or
-** greater than zero if the remaining fields of the cursor cursor key are less
-** than, equal to or greater than those in (*p).
+** cursor pCur points to. If bInt is false, all fields for which the 
+** corresponding bit in parameter "mask" is set are ignored. Or, if
+** bInt is true, then a difference of BTREE_ULPDISTORTION or fewer ULPs
+** in real values is overlooked for fields with the corresponding bit
+** set in mask.  
+**
+** Return the usual less than zero, zero, or 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 vdbeIsMatchingIndexKey(
   BtCursor *pCur,                 /* Cursor open on index */
+  int bInt,
   Bitmask mask,
   UnpackedRecord *p,              /* Index key being deleted */
   int *piRes                      /* 0 for a match, non-zero for not a match */
@@ -5450,10 +5494,12 @@ static int vdbeIsMatchingIndexKey(
         nSerial = sqlite3VdbeSerialTypeLen(iSerial);
         if( (idxRec+nSerial)>nRec ){
           rc = SQLITE_CORRUPT_BKPT;
-        }else if( ii>=BMS || (mask & MASKBIT(ii))==0 ){
+        }else{
           sqlite3VdbeSerialGet(&aRec[idxRec], iSerial, &mem);
-          res = sqlite3MemCompare(&mem, &p->aMem[ii], p->pKeyInfo->aColl[ii]);
-          if( res!=0 ) break;
+          if( vdbeSkipField(mask, ii, &p->aMem[ii], &mem, bInt)==0 ){
+            res = sqlite3MemCompare(&mem, &p->aMem[ii], p->pKeyInfo->aColl[ii]);
+            if( res!=0 ) break;
+          }
         }
         idxRec += sqlite3VdbeSerialTypeLen(iSerial);
       }
@@ -5467,16 +5513,14 @@ static int vdbeIsMatchingIndexKey(
 }
 
 /*
-** This is called when the record in (*p) is to be deleted from the index
-** opened by cursor pCur, but an exact match for the record was not found
-** in the index. The result of searching for it was (*pRes) - if (*pRes)
-** is -1, then the cursor points at a record that is smaller than (*p),
-** if it is +1, then it points to a record greater than (*p).
+** This is called when the record in (*p) should be found in the index 
+** opened by cursor pCur, but was not. This may happen as part of a DELETE
+** operation or an integrity check.
 **
 ** One reason that an exact match was not found may be the EIIB bug - that
 ** a text-to-float conversion may have caused a real value in record (*p)
 ** to be slightly different from its counterpart on disk. This function
-** attempts to find the right record to delete. If it does find the right
+** attempts to find the right index record. If it does find the right
 ** record, it leaves *pCur pointing to it and sets (*pRes) to 0 before
 ** returning. Otherwise, (*pRes) is set to non-zero and an SQLite error
 ** code returned.
@@ -5484,15 +5528,21 @@ static int vdbeIsMatchingIndexKey(
 ** The algorithm used to find the correct record is:
 **
 **   * Scan up to BTREE_FDK_RANGE entries either side of the current entry.
+**     If parameter bIntegrity is false, then all fields that are indexed
+**     expressions or virtual table columns are omitted from the comparison.
+**     If bIntegrity is true, then small differences in real values in
+**     such fields are overlooked, but they are not omitted from the comparison
+**     altogether.
 **
-**   * If the above fails to find an entry to delete, search the entire index.
+**   * If the above fails to find an entry and bIntegrity is false, search 
+**     the entire index.
 */
 int sqlite3VdbeFindIndexKey(
   BtCursor *pCur, 
   Index *pIdx,
-  int bExhaustive,
   UnpackedRecord *p, 
-  int *pRes
+  int *pRes,
+  int bIntegrity
 ){
 #define BTREE_FDK_RANGE 10
   int nStep = 0;
@@ -5540,7 +5590,7 @@ int sqlite3VdbeFindIndexKey(
     ** 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 = vdbeIsMatchingIndexKey(pCur, mask, p, &res);
+        rc = vdbeIsMatchingIndexKey(pCur, bIntegrity, mask, p, &res);
         if( res==0 || rc!=SQLITE_OK ) break;
         rc = sqlite3BtreeNext(pCur, 0);
       }
@@ -5548,7 +5598,7 @@ int sqlite3VdbeFindIndexKey(
         rc = SQLITE_OK;
         assert( res!=0 );
       }
-      if( nStep<0 || rc!=SQLITE_OK || res==0 || bExhaustive==0 ) break;
+      if( nStep<0 || rc!=SQLITE_OK || res==0 || bIntegrity ) break;
   
       /* The first, non-exhaustive, search failed to find an entry with 
       ** matching PK fields. So restart for an exhaustive search of the 
index 48e1dfd6ab81a8f080f12b73b64d6253540a2ffd..d8089884b6973d5e139b92e740e358a0bad4abe1 100644 (file)
@@ -56,7 +56,7 @@ do_execsql_test 1.1.1 {
 }
 do_execsql_test 1.1.1b {
   PRAGMA integrity_check;
-} {ok}
+} {{row 3 missing from index i1}}
 do_execsql_test 1.1.1c $idxcheck {20}
 
 do_execsql_test 1.1.2 {
@@ -70,7 +70,9 @@ 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;
-} {ok}
+} {
+  {row 3 missing from index i1}
+}
 do_execsql_test 1.2.2 $idxcheck {25}
 
 do_execsql_test 1.2.3 {
@@ -93,7 +95,13 @@ do_execsql_test 1.3.1 {
 
   UPDATE x1 SET b=19.0 WHERE b=20.0;
   PRAGMA integrity_check;
-} {ok}
+} {
+  {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}
+}
 
 do_execsql_test 1.3.2 $idxcheck {10 15 20 25 30}
 
@@ -186,7 +194,47 @@ do_execsql_test 3.3 $idxcheck 2
 do_execsql_test 3.4 {
   DELETE FROM y1 WHERE a=4;
 }
-
 do_execsql_test 3.5 $idxcheck {}
 
+#-------------------------------------------------------------------------
+reset_db
+do_execsql_test 4.0 {
+  CREATE TABLE z1(a INTEGER PRIMARY KEY, b);
+  CREATE INDEX z1b ON z1(b+0.0);
+  INSERT INTO z1 VALUES(1, 1.0);
+  INSERT INTO z1 VALUES(2, 4.0);
+  INSERT INTO z1 VALUES(3, 4.0);
+  INSERT INTO z1 VALUES(4, 4.0);
+  INSERT INTO z1 VALUES(5, 4.0);
+  INSERT INTO z1 VALUES(6, 4.0);
+  INSERT INTO z1 VALUES(7, 4.0);
+  INSERT INTO z1 VALUES(8, 1.0);
+}
+
+set root [db one {SELECT rootpage FROM sqlite_schema WHERE name='z1b'}]
+do_test 4.1 { 
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 1 $root
+  db eval { CREATE TABLE x1(b, a, PRIMARY KEY(b, a)) WITHOUT ROWID; }
+  sqlite3_test_control SQLITE_TESTCTRL_IMPOSTER db main 0 0
+} {}
+
+do_execsql_test 4.2 {
+  UPDATE x1 SET b=4.000000000000001 WHERE a=2;
+  UPDATE x1 SET b=4.000000000000002 WHERE a=3;
+  UPDATE x1 SET b=4.000000000000003 WHERE a=4;
+}
+
+do_execsql_test 4.3 {
+  PRAGMA integrity_check
+} {
+  {WARNING: expression index z1b stores an imprecise value for row 2}
+  {WARNING: expression index z1b stores an imprecise value for row 3}
+  {row 4 missing from index z1b}
+}
+
+
+
+
+
+
 finish_test