diff --git a/go.mod b/go.mod index 193eda79e0..15834a728a 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/klauspost/pgzip v1.2.6 github.com/linuxkit/virtsock v0.0.0-20241009230534-cb6a20cc0422 github.com/mattn/go-shellwords v1.0.14 - github.com/mattn/go-sqlite3 v1.14.48 + github.com/mattn/go-sqlite3 v1.14.49 github.com/mdlayher/vsock v1.3.0 github.com/moby/docker-image-spec v1.3.1 github.com/moby/moby/api v1.55.0 diff --git a/go.sum b/go.sum index b070c5ede2..b89de29847 100644 --- a/go.sum +++ b/go.sum @@ -233,8 +233,8 @@ github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhV github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= github.com/mattn/go-shellwords v1.0.14 h1:yUKzIgsCnosndOASY6/enly1EAuaXeFSQ7cdyA3OuYg= github.com/mattn/go-shellwords v1.0.14/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= -github.com/mattn/go-sqlite3 v1.14.48 h1:7XHIgl0a8HwOaiK4E47ozLkST78rR9+OtNGx27D/TFs= -github.com/mattn/go-sqlite3 v1.14.48/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= +github.com/mattn/go-sqlite3 v1.14.49 h1:B8jBHC3xhxZgxztrgruTuLucebnULQnx4W7cF7SAE9w= +github.com/mattn/go-sqlite3 v1.14.49/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY= github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4= github.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU= diff --git a/vendor/github.com/mattn/go-sqlite3/callback.go b/vendor/github.com/mattn/go-sqlite3/callback.go index 8ea17db9d2..b7df2be7f6 100644 --- a/vendor/github.com/mattn/go-sqlite3/callback.go +++ b/vendor/github.com/mattn/go-sqlite3/callback.go @@ -131,6 +131,27 @@ func lookupHandle(handle unsafe.Pointer) any { return lookupHandleVal(handle).val } +// deleteHandle releases a single handle created by newHandle. It is a no-op +// if the handle is unknown (e.g. already released). +func deleteHandle(handle unsafe.Pointer) { + handleLock.Lock() + defer handleLock.Unlock() + + current := loadHandleVals() + if _, ok := current[handle]; !ok { + return + } + next := make(map[unsafe.Pointer]handleVal, len(current)-1) + for h, v := range current { + if h == handle { + continue + } + next[h] = v + } + handleVals.Store(next) + C.free(handle) +} + func deleteHandles(db *SQLiteConn) { handleLock.Lock() defer handleLock.Unlock() diff --git a/vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c b/vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c index 337af9e47c..4a3437f57d 100644 --- a/vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c +++ b/vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c @@ -1,7 +1,7 @@ #ifndef USE_LIBSQLITE3 /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.53.3. By combining all the individual C code files into this +** version 3.53.4. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -19,7 +19,7 @@ ** separate file. This file contains only code for the core SQLite library. ** ** The content in this amalgamation comes from Fossil check-in -** d4c0e51e4aeb96955b99185ab9cde75c339e with changes in files: +** bf7c7f30031888f4e796e429ab3978879485 with changes in files: ** ** */ @@ -468,12 +468,12 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.53.3" -#define SQLITE_VERSION_NUMBER 3053003 -#define SQLITE_SOURCE_ID "2026-06-26 20:14:12 d4c0e51e4aeb96955b99185ab9cde75c339e2c29c3f3f12428d364a10d782c62" +#define SQLITE_VERSION "3.53.4" +#define SQLITE_VERSION_NUMBER 3053004 +#define SQLITE_SOURCE_ID "2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc" #define SQLITE_SCM_BRANCH "branch-3.53" -#define SQLITE_SCM_TAGS "release version-3.53.3" -#define SQLITE_SCM_DATETIME "2026-06-26T20:14:12.354Z" +#define SQLITE_SCM_TAGS "release version-3.53.4" +#define SQLITE_SCM_DATETIME "2026-07-24T19:02:57.525Z" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -27592,7 +27592,7 @@ SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p }else{ double r; rc = pVfs->xCurrentTime(pVfs, &r); - *pTimeOut = (sqlite3_int64)(r*86400000.0); + *pTimeOut = sqlite3RealToI64(r*86400000.0); } return rc; } @@ -60970,7 +60970,7 @@ static int readSuperJournal(sqlite3_file *pJrnl, u64 nSuper, char **pzSuper){ cksum -= zOut[u]; } } - if( rc!=SQLITE_OK || cksum ){ + if( rc!=SQLITE_OK || cksum || zOut[0]==0 ){ /* If the checksum doesn't add up, then one or more of the disk sectors ** containing the super-journal filename is corrupted. This means ** definitely roll back, so just return SQLITE_OK and report a (nul) @@ -111438,11 +111438,20 @@ static SQLITE_NOINLINE void resolveSetExprSubtypeArg(ExprList *pList){ nn = pList ? pList->nExpr : 0; for(ii=0; iia[ii].pExpr; - ExprSetProperty(pExpr, EP_SubtArg); - if( pExpr->op==TK_SELECT ){ - assert( ExprUseXSelect(pExpr) ); - assert( pExpr->x.pSelect!=0 ); - resolveSetExprSubtypeArg(pExpr->x.pSelect->pEList); + while( 1 /*exit-by-break*/ ){ + ExprSetProperty(pExpr, EP_SubtArg); + if( pExpr->op==TK_SELECT ){ + assert( ExprUseXSelect(pExpr) ); + assert( pExpr->x.pSelect!=0 ); + resolveSetExprSubtypeArg(pExpr->x.pSelect->pEList); + break; + } + if( pExpr->op==TK_UPLUS ){ + pExpr = pExpr->pLeft; + assert( pExpr!=0 ); + }else{ + break; + } } } } @@ -176955,7 +176964,7 @@ static void nth_valueStepFunc( break; case SQLITE_FLOAT: { double fVal = sqlite3_value_double(apArg[1]); - if( ((i64)fVal)!=fVal ) goto error_out; + if( sqlite3RealToI64(fVal)!=fVal ) goto error_out; iVal = (i64)fVal; break; } @@ -210777,8 +210786,8 @@ static int fts3StringAppend( ** to grow the buffer until so that it is big enough to accommodate the ** appended data. */ - if( pStr->n+nAppend+1>=pStr->nAlloc ){ - sqlite3_int64 nAlloc = pStr->nAlloc+(sqlite3_int64)nAppend+100; + if( (i64)pStr->n+(i64)nAppend+1>=(i64)pStr->nAlloc ){ + i64 nAlloc = pStr->nAlloc+(i64)nAppend+100; char *zNew = sqlite3_realloc64(pStr->z, nAlloc); if( !zNew ){ return SQLITE_NOMEM; @@ -214982,7 +214991,8 @@ static u32 jsonTranslateBlobToText( if( sz==0 ) goto malformed_jsonb; if( zIn[0]=='-' ){ jsonAppendChar(pOut, '-'); - k++; + if( sz<=1 ) goto malformed_jsonb; + k = 1; } if( zIn[k]=='.' ){ jsonAppendChar(pOut, '0'); @@ -217946,7 +217956,9 @@ static int jsonSkipLabel(JsonEachCursor *p){ if( p->eType==JSONB_OBJECT ){ u32 sz = 0; u32 n = jsonbPayloadSize(&p->sParse, p->i, &sz); - return p->i + n + sz; + sz += p->i + n; + if( sz >= p->sParse.nBlob ) sz = p->i; + return sz; }else{ return p->i; } @@ -226907,8 +226919,8 @@ static int rbuDeltaApply( int lenDelta, /* Length of the delta */ char *zOut /* Write the output into this preallocated buffer */ ){ - unsigned int limit; - unsigned int total = 0; + sqlite3_uint64 limit; + sqlite3_uint64 total = 0; #if RBU_ENABLE_DELTA_CKSUM char *zOrigOut = zOut; #endif @@ -226918,8 +226930,8 @@ static int rbuDeltaApply( /* ERROR: size integer not terminated by "\n" */ return -1; } - zDelta++; lenDelta--; - while( *zDelta && lenDelta>0 ){ + zDelta++; lenDelta--; /* Skip the \n */ + while( lenDelta>0 && zDelta[0] ){ unsigned int cnt, ofst; cnt = rbuDeltaGetInt(&zDelta, &lenDelta); if( lenDelta<=0 ) return -1; @@ -226927,7 +226939,7 @@ static int rbuDeltaApply( case '@': { zDelta++; lenDelta--; ofst = rbuDeltaGetInt(&zDelta, &lenDelta); - if( lenDelta>0 || zDelta[0]!=',' ){ + if( lenDelta>0 && zDelta[0]!=',' ){ /* ERROR: copy command not terminated by ',' */ return -1; } @@ -226952,7 +226964,7 @@ static int rbuDeltaApply( /* ERROR: insert command gives an output larger than predicted */ return -1; } - if( (i64)cnt>(i64)lenDelta ){ + if( cnt>lenDelta ){ /* ERROR: insert count exceeds size of delta */ return -1; } @@ -228965,13 +228977,13 @@ static int rbuGetUpdateStmt( char *zUpdate = 0; pUp->zMask = (char*)&pUp[1]; - memcpy(pUp->zMask, zMask, pIter->nTblCol); pUp->pNext = pIter->pRbuUpdate; pIter->pRbuUpdate = pUp; if( zSet ){ const char *zPrefix = ""; - + assert( p->rc==SQLITE_OK ); + memcpy(pUp->zMask, zMask, pIter->nTblCol); if( pIter->eType!=RBU_PK_VTAB ) zPrefix = "rbu_imp_"; zUpdate = sqlite3_mprintf("UPDATE \"%s%w\" SET %s WHERE %s", zPrefix, pIter->zTbl, zSet, zWhere @@ -229061,6 +229073,9 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ case RBU_STATE_ROW: pRet->nRow = sqlite3_column_int(pStmt, 1); + if( pRet->nRow<0 ){ + rc = SQLITE_CORRUPT; + } break; case RBU_STATE_PROGRESS: @@ -240297,7 +240312,12 @@ static int sessionChangesetToHash( pIter->in.bNoDiscard = 1; while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){ - rc = sessionOneChangeIterToHash(pGrp, pIter, bRebase); + if( bRebase && pIter->bPatchset ){ + /* A patchset may not be used as a rebase */ + rc = SQLITE_ERROR; + }else{ + rc = sessionOneChangeIterToHash(pGrp, pIter, bRebase); + } if( rc!=SQLITE_OK ) break; } @@ -240674,13 +240694,14 @@ static void sessionAppendPartialUpdate( int i; u8 *a1 = aRec; u8 *a2 = aChange; + u8 *a2Eof = &a2[nChange]; *pOut++ = SQLITE_UPDATE; *pOut++ = pIter->bIndirect; for(i=0; inCol; i++){ int n1 = sessionSerialLen(a1); - int n2 = sessionSerialLen(a2); - if( pIter->abPK[i] || a2[0]==0 ){ + int n2 = (a2>=a2Eof) ? 0 : sessionSerialLen(a2); + if( n2<=0 || pIter->abPK[i] || a2[0]==0 ){ if( !pIter->abPK[i] && a1[0] ) bData = 1; memcpy(pOut, a1, n1); pOut += n1; @@ -240881,8 +240902,8 @@ SQLITE_API int sqlite3rebaser_configure( sqlite3_rebaser *p, int nRebase, const void *pRebase ){ - sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */ int rc; /* Return code */ + sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */ rc = sqlite3changeset_start(&pIter, nRebase, (void*)pRebase); if( rc==SQLITE_OK ){ rc = sessionChangesetToHash(pIter, &p->grp, 1); @@ -251702,6 +251723,7 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ pRet = (Fts5Data*)sqlite3_malloc64(nAlloc); if( pRet ){ pRet->nn = nByte; + pRet->szLeaf = 0; aOut = pRet->p = (u8*)pRet + szData; }else{ rc = SQLITE_NOMEM; @@ -251714,10 +251736,8 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ sqlite3_free(pRet); pRet = 0; }else{ - /* TODO1: Fix this */ pRet->p[nByte] = 0x00; pRet->p[nByte+1] = 0x00; - pRet->szLeaf = fts5GetU16(&pRet->p[2]); } } p->rc = rc; @@ -251738,9 +251758,17 @@ static void fts5DataRelease(Fts5Data *pData){ sqlite3_free(pData); } +/* +** Read a leaf-page record. This is similar to fts5DataRead(), except that +** it fills in the Fts5Data.szLeaf value before returning. +*/ static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){ Fts5Data *pRet = fts5DataRead(p, iRowid); if( pRet ){ + assert( pRet->szLeaf==0 ); + if( pRet->nn>=4 ){ + pRet->szLeaf = fts5GetU16(&pRet->p[2]); + } if( pRet->szLeaf<4 || pRet->szLeaf>pRet->nn ){ FTS5_CORRUPT_ROWID(p, iRowid); fts5DataRelease(pRet); @@ -253650,6 +253678,10 @@ static void fts5SegIterNextInit( pIter->iPgidxOff = pIter->pLeaf->szLeaf; pIter->iPgidxOff += fts5GetVarint32(&a[pIter->iPgidxOff], iTermOff); + if( iTermOff > pIter->pLeaf->szLeaf ){ + p->rc = FTS5_CORRUPT; + return; + } pIter->iLeafOffset = iTermOff; fts5SegIterLoadTerm(p, pIter, 0); fts5SegIterLoadNPos(p, pIter); @@ -255976,7 +256008,7 @@ static void fts5SecureDeleteOverflow( int iNext = 0; u8 *aPg = 0; - pLeaf = fts5DataRead(p, iRowid); + pLeaf = fts5LeafRead(p, iRowid); if( pLeaf==0 ) break; aPg = pLeaf->p; @@ -255984,7 +256016,7 @@ static void fts5SecureDeleteOverflow( if( iNext!=0 ){ *pbLastInDoclist = 0; } - if( iNext==0 && pLeaf->szLeaf!=pLeaf->nn ){ + if( iNext==0 && pLeaf->szLeafnn ){ fts5GetVarint32(&aPg[pLeaf->szLeaf], iNext); } @@ -256271,7 +256303,7 @@ static void fts5DoSecureDelete( /* The entry being removed may be the only position list in ** its doclist. */ for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){ - Fts5Data *pPg = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); + Fts5Data *pPg = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); int bEmpty = (pPg && pPg->nn==4); fts5DataRelease(pPg); if( bEmpty==0 ) break; @@ -256279,7 +256311,7 @@ static void fts5DoSecureDelete( if( iPgno==pSeg->iTermLeafPgno ){ i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno); - Fts5Data *pTerm = fts5DataRead(p, iId); + Fts5Data *pTerm = fts5LeafRead(p, iId); if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){ u8 *aTermIdx = &pTerm->p[pTerm->szLeaf]; int nTermIdx = pTerm->nn - pTerm->szLeaf; @@ -259231,7 +259263,7 @@ static void fts5IndexIntegrityCheckEmpty( /* Now check that the iter.nEmpty leaves following the current leaf ** (a) exist and (b) contain no terms. */ for(i=iFirst; p->rc==SQLITE_OK && i<=iLast; i++){ - Fts5Data *pLeaf = fts5DataRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i)); + Fts5Data *pLeaf = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i)); if( pLeaf ){ if( !fts5LeafIsTermless(pLeaf) || (i>=iNoRowid && 0!=fts5LeafFirstRowidOff(pLeaf)) @@ -259359,7 +259391,7 @@ static void fts5IndexIntegrityCheckSegment( FTS5_CORRUPT_ROWID(p, iRow); }else{ iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm); - if( iOff+nTerm>pLeaf->szLeaf ){ + if( (i64)iOff+(i64)nTerm>(i64)pLeaf->szLeaf ){ FTS5_CORRUPT_ROWID(p, iRow); }else{ res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm)); @@ -263087,19 +263119,23 @@ static int fts5ApiPhraseFirstColumn( if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ Fts5Sorter *pSorter = pCsr->pSorter; - int n; - if( pSorter ){ - int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); - n = pSorter->aIdx[iPhrase] - i1; - pIter->a = &pSorter->aPoslist[i1]; + if( iPhrase<0 || iPhrase>=sqlite3Fts5ExprPhraseCount(pCsr->pExpr) ){ + rc = SQLITE_RANGE; }else{ - rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n); - } - if( rc==SQLITE_OK ){ - assert( pIter->a || n==0 ); - pIter->b = (pIter->a ? &pIter->a[n] : 0); - *piCol = 0; - fts5ApiPhraseNextColumn(pCtx, pIter, piCol); + int n; + if( pSorter ){ + int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); + n = pSorter->aIdx[iPhrase] - i1; + pIter->a = &pSorter->aPoslist[i1]; + }else{ + rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n); + } + if( rc==SQLITE_OK ){ + assert( pIter->a || n==0 ); + pIter->b = (pIter->a ? &pIter->a[n] : 0); + *piCol = 0; + fts5ApiPhraseNextColumn(pCtx, pIter, piCol); + } } }else{ int n; @@ -264007,7 +264043,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2026-06-26 20:14:12 d4c0e51e4aeb96955b99185ab9cde75c339e2c29c3f3f12428d364a10d782c62", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc", -1, SQLITE_TRANSIENT); } /* diff --git a/vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h b/vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h index 9417bce8d5..aecd0f8385 100644 --- a/vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h +++ b/vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h @@ -147,12 +147,12 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.53.3" -#define SQLITE_VERSION_NUMBER 3053003 -#define SQLITE_SOURCE_ID "2026-06-26 20:14:12 d4c0e51e4aeb96955b99185ab9cde75c339e2c29c3f3f12428d364a10d782c62" +#define SQLITE_VERSION "3.53.4" +#define SQLITE_VERSION_NUMBER 3053004 +#define SQLITE_SOURCE_ID "2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc" #define SQLITE_SCM_BRANCH "branch-3.53" -#define SQLITE_SCM_TAGS "release version-3.53.3" -#define SQLITE_SCM_DATETIME "2026-06-26T20:14:12.354Z" +#define SQLITE_SCM_TAGS "release version-3.53.4" +#define SQLITE_SCM_DATETIME "2026-07-24T19:02:57.525Z" /* ** CAPI3REF: Run-Time Library Version Numbers diff --git a/vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vtable.go b/vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vtable.go index 9e916ea8cb..b2b2404bcb 100644 --- a/vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vtable.go +++ b/vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vtable.go @@ -423,6 +423,9 @@ func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char { } else { err = vt.vTab.Disconnect() } + // The vtab is gone as far as SQLite is concerned regardless of the + // callback result, so release the handle either way. + deleteHandle(pVTab) if err != nil { return mPrintf("%s", err.Error()) } @@ -451,6 +454,9 @@ func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char { if err != nil { return mPrintf("%s", err.Error()) } + if res == nil { + return mPrintf("%s", "BestIndex returned a nil IndexResult") + } if len(res.Used) != len(csts) { return mPrintf("Result.Used != expected value", "") } @@ -460,7 +466,11 @@ func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char { slice := unsafe.Slice(info.aConstraintUsage, int(info.nConstraint)) index := 1 for i := range slice { - if res.Used[i] { + // SQLite returns "xBestIndex malfunction" when an argvIndex is + // assigned to a constraint it marked as not usable, so ignore + // Used for those; they may become usable on a later xBestIndex + // invocation for a different plan. + if res.Used[i] && csts[i].Usable { slice[i].argvIndex = C.int(index) slice[i].omit = C.uchar(1) index++ @@ -482,8 +492,23 @@ func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char { if res.AlreadyOrdered { info.orderByConsumed = C.int(1) } - info.estimatedCost = C.double(res.EstimatedCost) - info.estimatedRows = C.sqlite3_int64(res.EstimatedRows) + // SQLite pre-initializes estimatedCost and estimatedRows with sensible + // defaults; overwriting them with the Go zero value would make every + // candidate plan look free and break query planning, so only pass + // values the implementation actually set. + if res.EstimatedCost > 0 { + info.estimatedCost = C.double(res.EstimatedCost) + } + if res.EstimatedRows > 0 { + var rows int64 + if res.EstimatedRows >= float64(math.MaxInt64) { + rows = math.MaxInt64 + } else if rows = int64(res.EstimatedRows); rows < 1 { + // A positive fractional estimate must not truncate to 0. + rows = 1 + } + info.estimatedRows = C.sqlite3_int64(rows) + } return nil } @@ -492,6 +517,9 @@ func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char { func goVClose(pCursor unsafe.Pointer) *C.char { vtc := lookupHandle(pCursor).(*sqliteVTabCursor) err := vtc.vTabCursor.Close() + // The cursor is gone as far as SQLite is concerned regardless of the + // callback result, so release the handle either way. + deleteHandle(pCursor) if err != nil { return mPrintf("%s", err.Error()) } @@ -502,6 +530,7 @@ func goVClose(pCursor unsafe.Pointer) *C.char { func goMDestroy(pClientData unsafe.Pointer) { m := lookupHandle(pClientData).(*sqliteModule) m.module.DestroyModule() + deleteHandle(pClientData) } //export goVFilter @@ -514,7 +543,14 @@ func goVFilter(pCursor unsafe.Pointer, idxNum C.int, idxName *C.char, argc C.int if err != nil { return mPrintf("%s", err.Error()) } - vals = append(vals, conv.Interface()) + + // work around for SQLITE_NULL + x := conv.Interface() + if z, ok := x.([]byte); ok && z == nil { + x = nil + } + + vals = append(vals, x) } err := vtc.vTabCursor.Filter(int(idxNum), C.GoString(idxName), vals) if err != nil { @@ -608,7 +644,15 @@ func goVUpdate(pVTab unsafe.Pointer, argc C.int, argv **C.sqlite3_value, pRowid } case argc > 1: - err = v.Update(vals[1], vals[2:]) + // Per the xUpdate contract argv[0] identifies the row being + // updated while argv[1] is its new rowid. VTabUpdater has no + // way to convey a rowid change, so reject it instead of + // silently updating values under the old rowid. + if vals[0] != vals[1] { + err = fmt.Errorf("virtual %s table %sdoes not support changing the rowid", vt.module.name, tname) + } else { + err = v.Update(vals[0], vals[2:]) + } } } @@ -708,5 +752,5 @@ func (c *SQLiteConn) CreateModule(moduleName string, module Module) error { } return nil } - return nil + return fmt.Errorf("sqlite3: CreateModule requires a non-nil module") } diff --git a/vendor/modules.txt b/vendor/modules.txt index 7df3bde6b2..e08b18ea69 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -380,7 +380,7 @@ github.com/mattn/go-runewidth # github.com/mattn/go-shellwords v1.0.14 ## explicit; go 1.13 github.com/mattn/go-shellwords -# github.com/mattn/go-sqlite3 v1.14.48 +# github.com/mattn/go-sqlite3 v1.14.49 ## explicit; go 1.21 github.com/mattn/go-sqlite3 # github.com/mdlayher/socket v0.6.0