From: dan Date: Mon, 16 Mar 2026 15:16:13 +0000 (+0000) Subject: Make OP_IdxDelete tolerant of small variations in index key values. X-Git-Tag: major-release~74^2~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d21413adcb1726f53a422d89e18298f048e44ffc;p=thirdparty%2Fsqlite.git Make OP_IdxDelete tolerant of small variations in index key values. FossilOrigin-Name: c4b0081f08bd0458dbcf269b43a4051941eec8067393aa8e6810d4b8422ce44a --- diff --git a/manifest b/manifest index 86a1bf4c10..25bb5c0d83 100644 --- 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. diff --git a/manifest.tags b/manifest.tags index bec971799f..27e2c51b42 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,2 +1,2 @@ -branch trunk -tag trunk +branch idxdelete-tolerance +tag idxdelete-tolerance diff --git a/manifest.uuid b/manifest.uuid index 35c327cc26..0bcbe30bb7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -80bc5bc07e221f837c28066f0a438f11c8ab6be4c8ba93615439eb1667967003 +c4b0081f08bd0458dbcf269b43a4051941eec8067393aa8e6810d4b8422ce44a diff --git a/src/vdbe.c b/src/vdbe.c index e2e98eb5ff..c385822d8d 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -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; diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 320721d065..a2e00afac9 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -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 *); diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 603e85ddfd..e09dd33574 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -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; iinRec ){ + 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( iipKeyInfo->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