]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Make OP_IdxDelete tolerant of small variations in index key values.
authordan <Dan Kennedy>
Mon, 16 Mar 2026 15:16:13 +0000 (15:16 +0000)
committerdan <Dan Kennedy>
Mon, 16 Mar 2026 15:16:13 +0000 (15:16 +0000)
FossilOrigin-Name: c4b0081f08bd0458dbcf269b43a4051941eec8067393aa8e6810d4b8422ce44a

manifest
manifest.tags
manifest.uuid
src/vdbe.c
src/vdbeInt.h
src/vdbeaux.c

index 86a1bf4c10719e61e58ea52fe0ea7966bc699cf0..25bb5c0d83015975e26830c79b2a4aec5d2eaf02 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Properly\sfix\stemp\striggers\screated\sas\spart\sof\sFK\sprocessing\sto\stheir\sschemas.\sOtherwise\sthey\smay\sbecome\sconfused\sby\ssimilarly\snamed\schild\stables\sin\sother\sattached\sdatabases.\sFix\sfor\sforum\spost\s[forum:636bd0180a\s|\s636bd0180a].
-D 2026-03-16T11:14:26.814
+C Make\sOP_IdxDelete\stolerant\sof\ssmall\svariations\sin\sindex\skey\svalues.
+D 2026-03-16T15:16:13.147
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -799,11 +799,11 @@ F src/upsert.c 215328c3f91623c520ec8672c44323553f12caeb4f01b1090ebdca99fdf7b4f1
 F src/utf.c 7267c3fb9e2467020507601af3354c2446c61f444387e094c779dccd5ca62165
 F src/util.c 0cb2e590e9dcac6807352017fcbf5a52e0f836d74a338cb8c02ee3162bcf6508
 F src/vacuum.c d3d35d8ae893d419ade5fa196d761a83bddcbb62137a1a157ae751ef38b26e82
-F src/vdbe.c 5328c99dd256ee8132383565a86e253543a85daccfd7477c52f20bac6b385a7f
+F src/vdbe.c 303e8070a9d77509c754c2948cf1b61944930f1136001733e2bd5daaadefb8d1
 F src/vdbe.h 966d0677a540b7ea6549b7c4e1312fc0d830fce3a235a58c801f2cc31cf5ecf9
-F src/vdbeInt.h 42488247a80cd9d300627833c6c85ace067ae5011a99e7614e2358130d62feea
+F src/vdbeInt.h e2ac1de191ba71b98daee4757950548f00b8bb169a794cf8cde51587cb147fe9
 F src/vdbeapi.c 6cdcbe5c7afa754c998e73d2d5d2805556268362914b952811bdfb9c78a37cf1
-F src/vdbeaux.c 396d38a62a357b807eabae0cae441fc89d2767a57ab08026b7072bf7aa2dd00c
+F src/vdbeaux.c 4885cfc6a58b8bf7a8a367673649057dc4a076c0c8797edb6bf1470bb082a31d
 F src/vdbeblob.c b3f0640db9642fbdc88bd6ebcc83d6009514cafc98f062f675f2c8d505d82692
 F src/vdbemem.c 317ec5e870ddb16951b606c9fe8be22baef22ecbe46f58fdefc259662238afb7
 F src/vdbesort.c b69220f4ea9ffea5fdef34d968c60305444eea909252a81933b54c296d9cca70
@@ -2192,8 +2192,11 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
 F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
-P dc78d258745a1350685b37da56f0a2ac2e437b422415d634522dc61d340d06eb
-R f4cdffcb37cefef0e7ac6ef7ca48fea4
+P 80bc5bc07e221f837c28066f0a438f11c8ab6be4c8ba93615439eb1667967003
+R 5a3b7bcea17f97d45cd400c7992de61b
+T *branch * idxdelete-tolerance
+T *sym-idxdelete-tolerance *
+T -sym-trunk *
 U dan
-Z d215803567bcaa7157c0e213d8c093a7
+Z 7bca9a15e7fe96dadf1d72ae49f0c8d2
 # Remove this line to create a well-formed Fossil manifest.
index bec971799ff1b8ee641c166c7aeb22d12c785393..27e2c51b4291aff9d33364ee26124d8a782f75b8 100644 (file)
@@ -1,2 +1,2 @@
-branch trunk
-tag trunk
+branch idxdelete-tolerance
+tag idxdelete-tolerance
index 35c327cc26abd16f9954782bce99332f0a461f05..0bcbe30bb72ca83bc0cdb615248bfe6958d243c4 100644 (file)
@@ -1 +1 @@
-80bc5bc07e221f837c28066f0a438f11c8ab6be4c8ba93615439eb1667967003
+c4b0081f08bd0458dbcf269b43a4051941eec8067393aa8e6810d4b8422ce44a
index e2e98eb5fff325be327d28ba47f34960577e069a..c385822d8d3cd09b6c12d009bfdf3aef1c7bb467 100644 (file)
@@ -6653,13 +6653,22 @@ case OP_IdxDelete: {
   r.aMem = &aMem[pOp->p2];
   rc = sqlite3BtreeIndexMoveto(pCrsr, &r, &res);
   if( rc ) goto abort_due_to_error;
-  if( res==0 ){
-    rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE);
-    if( rc ) goto abort_due_to_error;
-  }else if( !sqlite3WritableSchema(db) ){
-    rc = sqlite3ReportError(SQLITE_CORRUPT_INDEX, __LINE__, "index corruption");
-    goto abort_due_to_error;
+  if( res!=0 ){
+    rc = sqlite3VdbeFindDeleteKey(pCrsr, &r, &res);
+    if( rc!=SQLITE_OK ) goto abort_due_to_error;
+    if( res!=0 ){
+      if( !sqlite3WritableSchema(db) ){
+        rc = sqlite3ReportError(
+            SQLITE_CORRUPT_INDEX, __LINE__, "index corruption");
+        goto abort_due_to_error;
+      }
+      pC->cacheStatus = CACHE_STALE;
+      pC->seekResult = 0;
+      break;
+    }
   }
+  rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE);
+  if( rc ) goto abort_due_to_error;
   assert( pC->deferredMoveto==0 );
   pC->cacheStatus = CACHE_STALE;
   pC->seekResult = 0;
index 320721d065cdcf72ffd833775edddd907a8b5c6a..a2e00afac921de68268116fdb27678ba5a13ed41 100644 (file)
@@ -687,6 +687,7 @@ void sqlite3VdbePreUpdateHook(
     Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int,int);
 #endif
 int sqlite3VdbeTransferError(Vdbe *p);
+int sqlite3VdbeFindDeleteKey(BtCursor*, UnpackedRecord*, int*);
 
 int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *);
 void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *);
index 603e85ddfdb6515067acf446773ab7188823e387..e09dd3357416c8efa796217558ddd630fdd73610 100644 (file)
@@ -5393,6 +5393,151 @@ void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){
   }
 }
 
+/*
+** This function compares the unpacked record with the current key that
+** cursor pCur points to. It returns the usual less than zero, zero, or
+** greater than zero if the cursor key is less than, equal to or greater
+** than p. i.e.
+**
+**          (pCur->pKey) - (p)
+**
+** Except that:
+**
+**   * if the PK fields of the keys match, zero is always returned, even
+**     if the preceding fields do not match.
+**
+**   * otherwise, if the preceding fields are not identical, the result
+**     of comparing them is returned.
+**
+**   * finally, if the preceding fields all match but the PK fields do 
+**     not, the result of comparing the PK fields is returned.
+**
+** This function is not optimized. It is not expected to be called often.
+*/
+static int vdbeIsDeleteKey(
+  BtCursor *pCur,                 /* Cursor open on index */
+  UnpackedRecord *p,              /* Index key being deleted */
+  int *piRes                      /* 0 for a match, non-zero for not a match */
+){
+  u8 *aRec = 0;
+  u32 nRec = 0;
+  Mem mem;
+  int rc = SQLITE_OK;
+
+  memset(&mem, 0, sizeof(mem));
+  mem.enc = p->pKeyInfo->enc;
+  mem.db = p->pKeyInfo->db;
+  nRec = sqlite3BtreePayloadSize(pCur);
+  aRec = sqlite3MallocZero(nRec);
+  if( aRec==0 ){
+    rc = SQLITE_NOMEM_BKPT;
+  }else{
+    rc = sqlite3BtreePayload(pCur, 0, nRec, aRec);
+  }
+
+  if( rc==SQLITE_OK ){
+    int szHdr = 0;                /* Size of record header in bytes */
+    int idxHdr = 0;               /* Current index in header */
+    int idxRec = 0;               /* Current index in record */
+    int ii = 0;
+    int nCol = 0;
+    int res = 0;
+
+    idxHdr = getVarint32(aRec, szHdr);
+    if( szHdr>98307 ){
+      rc = SQLITE_CORRUPT;
+    }else{
+      int recres = 0;             /* Result of comparing record fields */
+      int res = 0;                /* Result of this function call */
+
+      idxRec = szHdr; 
+      nCol = p->pKeyInfo->nAllField;
+      for(ii=0; ii<nCol && rc==SQLITE_OK; ii++){
+        u32 iSerial = 0;
+        int nSerial = 0;
+        int r2 = 0;
+
+        idxHdr += getVarint32(&aRec[idxHdr], iSerial);
+        nSerial = sqlite3VdbeSerialTypeLen(iSerial);
+        if( (idxRec+nSerial)>nRec ){
+          rc = SQLITE_CORRUPT_BKPT;
+        }else{
+          sqlite3VdbeSerialGet(&aRec[idxRec], iSerial, &mem);
+          idxRec += sqlite3VdbeSerialTypeLen(iSerial);
+          r2 = sqlite3MemCompare(&mem, &p->aMem[ii], p->pKeyInfo->aColl[ii]);
+          if( r2!=0 ){
+            if( ii<p->pKeyInfo->nKeyField ){
+              if( recres==0 ) recres = r2;
+            }else{
+              res = recres;
+              if( res==0 ) res = r2;
+              break;
+            }
+          }
+        }
+      }
+
+      *piRes = res;
+    }
+  }
+
+  sqlite3_free(aRec);
+  return rc;
+}
+
+/*
+** 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).
+**
+** 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
+** 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.
+**
+** The algorithm used to find the correct record is:
+**
+**   * Test the PK columns of the current record to see if they match (*p).
+**     If so, delete the current record.
+**
+**   * If the caller's (*pRes) value was -ve, advance the cursor forward one
+**     entry. Then test the PK fields again. Repeat until the cursor points
+**     to an entry larger than (*p).
+**
+**   * Or, if the caller's (*pRes) value was +ve, move the cursor backwards 
+**     one entry. Then test the PK fields again. Repeat until the cursor 
+**     points to an entry larger than (*p).
+*/
+int sqlite3VdbeFindDeleteKey(BtCursor *pCur, UnpackedRecord *p, int *pRes){
+  int resCaller = *pRes;
+  int res = resCaller;
+  int rc = SQLITE_OK;
+
+  assert( resCaller==-1 || resCaller==0 || resCaller==+1 );
+  while( sqlite3BtreeEof(pCur)==0 && rc==SQLITE_OK ){
+    rc = vdbeIsDeleteKey(pCur, p, &res);
+    assert( res==-1 || res==0 || res==+1 );
+    if( res!=resCaller ) break;
+
+    if( rc==SQLITE_OK ){
+      if( resCaller<0 ){
+        rc = sqlite3BtreeNext(pCur, 0);
+      }else{
+        rc = sqlite3BtreePrevious(pCur, 0);
+      }
+    }
+  }
+
+  if( rc==SQLITE_DONE ) rc = SQLITE_OK;
+  *pRes = res;
+  return rc;
+}
+
 #ifndef SQLITE_OMIT_DATETIME_FUNCS
 /*
 ** Cause a function to throw an error if it was call from OP_PureFunc