diff options
60 files changed, 2566 insertions, 798 deletions
@@ -10430,6 +10430,8 @@ fi if test "${use_wasi_sdk}" = "no" ; then HAVE_WASI_SDK="" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } else HAVE_WASI_SDK=1 # Changing --host and --target have no effect here except to possibly @@ -10451,6 +10453,8 @@ else # libtool is apparently hard-coded to use gcc for linking DLLs, so # we disable the DLL build... enable_shared=no + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } fi diff --git a/configure.ac b/configure.ac index 5391f0802..53be0a686 100644 --- a/configure.ac +++ b/configure.ac @@ -181,6 +181,7 @@ AC_CACHE_VAL(ac_cv_c_wasi_sdk,[ ]) if test "${use_wasi_sdk}" = "no" ; then HAVE_WASI_SDK="" + AC_MSG_RESULT([no]) else HAVE_WASI_SDK=1 # Changing --host and --target have no effect here except to possibly @@ -202,6 +203,7 @@ else # libtool is apparently hard-coded to use gcc for linking DLLs, so # we disable the DLL build... enable_shared=no + AC_MSG_RESULT([yes]) fi AC_SUBST(HAVE_WASI_SDK) diff --git a/ext/fts5/fts5_main.c b/ext/fts5/fts5_main.c index d4b9fa85c..ccdcf7b57 100644 --- a/ext/fts5/fts5_main.c +++ b/ext/fts5/fts5_main.c @@ -1625,7 +1625,7 @@ static int fts5UpdateMethod( int rc = SQLITE_OK; /* Return code */ /* A transaction must be open when this is called. */ - assert( pTab->ts.eState==1 ); + assert( pTab->ts.eState==1 || pTab->ts.eState==2 ); assert( pVtab->zErrMsg==0 ); assert( nArg==1 || nArg==(2+pConfig->nCol+2) ); diff --git a/ext/fts5/test/fts5misc.test b/ext/fts5/test/fts5misc.test index 4b9ca2dbb..416b4c808 100644 --- a/ext/fts5/test/fts5misc.test +++ b/ext/fts5/test/fts5misc.test @@ -402,5 +402,46 @@ do_test 15.4 { list [catch { db2 eval COMMIT } msg] $msg } {0 {}} +#------------------------------------------------------------------------- +reset_db +forcedelete test.db2 +sqlite3 db2 test.db +do_execsql_test 16.0 { + + ATTACH 'test.db2' AS aux; + CREATE TABLE aux.t2(x,y); + INSERT INTO t2 VALUES(1, 2); + CREATE VIRTUAL TABLE x1 USING fts5(a); + BEGIN; + INSERT INTO x1 VALUES('abc'); + INSERT INTO t2 VALUES(3, 4); +} + +do_execsql_test -db db2 16.1 { + ATTACH 'test.db2' AS aux; + BEGIN; + SELECT * FROM t2 +} {1 2} + +do_catchsql_test 16.2 { + COMMIT; +} {1 {database is locked}} + +do_execsql_test 16.3 { + INSERT INTO x1 VALUES('def'); +} + +do_execsql_test -db db2 16.4 { + END +} + +do_execsql_test 16.5 { + COMMIT +} + +do_execsql_test -db db2 16.6 { + SELECT * FROM x1 +} {abc def} + finish_test diff --git a/ext/lsm1/lsmInt.h b/ext/lsm1/lsmInt.h index 0f822e479..c8e917ea1 100644 --- a/ext/lsm1/lsmInt.h +++ b/ext/lsm1/lsmInt.h @@ -405,7 +405,7 @@ struct Segment { LsmPgno iFirst; /* First page of this run */ LsmPgno iLastPg; /* Last page of this run */ LsmPgno iRoot; /* Root page number (if any) */ - int nSize; /* Size of this run in pages */ + LsmPgno nSize; /* Size of this run in pages */ Redirect *pRedirect; /* Block redirects (or NULL) */ }; @@ -853,6 +853,8 @@ int lsmVarintGet32(u8 *, int *); int lsmVarintPut64(u8 *aData, i64 iVal); int lsmVarintGet64(const u8 *aData, i64 *piVal); +int lsmVarintLen64(i64); + int lsmVarintLen32(int); int lsmVarintSize(u8 c); diff --git a/ext/lsm1/lsm_ckpt.c b/ext/lsm1/lsm_ckpt.c index ba92a823c..1c4f788ad 100644 --- a/ext/lsm1/lsm_ckpt.c +++ b/ext/lsm1/lsm_ckpt.c @@ -511,7 +511,7 @@ static void ckptNewSegment( pSegment->iFirst = ckptGobble64(aIn, piIn); pSegment->iLastPg = ckptGobble64(aIn, piIn); pSegment->iRoot = ckptGobble64(aIn, piIn); - pSegment->nSize = (int)ckptGobble64(aIn, piIn); + pSegment->nSize = ckptGobble64(aIn, piIn); assert( pSegment->iFirst ); } diff --git a/ext/lsm1/lsm_file.c b/ext/lsm1/lsm_file.c index 1dcdd05d9..fd78835bb 100644 --- a/ext/lsm1/lsm_file.c +++ b/ext/lsm1/lsm_file.c @@ -215,8 +215,8 @@ struct FileSystem { char *zLog; /* Database file name */ int nMetasize; /* Size of meta pages in bytes */ int nMetaRwSize; /* Read/written size of meta pages in bytes */ - int nPagesize; /* Database page-size in bytes */ - int nBlocksize; /* Database block-size in bytes */ + i64 nPagesize; /* Database page-size in bytes */ + i64 nBlocksize; /* Database block-size in bytes */ /* r/w file descriptors for both files. */ LsmFile *pLsmFile; /* Used after lsm_close() to link into list */ @@ -889,7 +889,7 @@ static LsmPgno fsFirstPageOnBlock(FileSystem *pFS, int iBlock){ iPg = pFS->nBlocksize * (LsmPgno)(iBlock-1) + 4; } }else{ - const int nPagePerBlock = (pFS->nBlocksize / pFS->nPagesize); + const i64 nPagePerBlock = (pFS->nBlocksize / pFS->nPagesize); if( iBlock==1 ){ iPg = 1 + ((pFS->nMetasize*2 + pFS->nPagesize - 1) / pFS->nPagesize); }else{ @@ -1131,7 +1131,6 @@ static void fsGrowMapping( i64 iSz, /* Minimum size to extend mapping to */ int *pRc /* IN/OUT: Error code */ ){ - assert( pFS->pCompress==0 ); assert( PAGE_HASPREV==4 ); if( *pRc==LSM_OK && iSz>pFS->nMap ){ @@ -1872,7 +1871,7 @@ void lsmFsGobble( assert( nPgno>0 && 0==fsPageRedirects(pFS, pRun, aPgno[0]) ); iBlk = fsPageToBlock(pFS, pRun->iFirst); - pRun->nSize += (int)(pRun->iFirst - fsFirstPageOnBlock(pFS, iBlk)); + pRun->nSize += (pRun->iFirst - fsFirstPageOnBlock(pFS, iBlk)); while( rc==LSM_OK ){ int iNext = 0; @@ -1883,13 +1882,13 @@ void lsmFsGobble( } rc = fsBlockNext(pFS, pRun, iBlk, &iNext); if( rc==LSM_OK ) rc = fsFreeBlock(pFS, pSnapshot, pRun, iBlk); - pRun->nSize -= (int)( + pRun->nSize -= ( 1 + fsLastPageOnBlock(pFS, iBlk) - fsFirstPageOnBlock(pFS, iBlk) ); iBlk = iNext; } - pRun->nSize -= (int)(pRun->iFirst - fsFirstPageOnBlock(pFS, iBlk)); + pRun->nSize -= (pRun->iFirst - fsFirstPageOnBlock(pFS, iBlk)); assert( pRun->nSize>0 ); } diff --git a/ext/lsm1/lsm_main.c b/ext/lsm1/lsm_main.c index a9c48e004..f2b353105 100644 --- a/ext/lsm1/lsm_main.c +++ b/ext/lsm1/lsm_main.c @@ -432,7 +432,7 @@ int lsm_config(lsm_db *pDb, int eParam, ...){ } void lsmAppendSegmentList(LsmString *pStr, char *zPre, Segment *pSeg){ - lsmStringAppendf(pStr, "%s{%d %d %d %d}", zPre, + lsmStringAppendf(pStr, "%s{%lld %lld %lld %lld}", zPre, pSeg->iFirst, pSeg->iLastPg, pSeg->iRoot, pSeg->nSize ); } diff --git a/ext/lsm1/lsm_shared.c b/ext/lsm1/lsm_shared.c index 2fdacf1ec..09f933848 100644 --- a/ext/lsm1/lsm_shared.c +++ b/ext/lsm1/lsm_shared.c @@ -1306,6 +1306,24 @@ int lsmBeginRoTrans(lsm_db *db){ } } + /* In 'lsm_open()' we don't update the page and block sizes in the + ** Filesystem for 'readonly' connection. Because member 'db->pShmhdr' is a + ** nullpointer, this prevents loading a checkpoint. Now that the system is + ** live this member should be set. So we can update both values in + ** the Filesystem. + ** + ** Configure the file-system connection with the page-size and block-size + ** of this database. Even if the database file is zero bytes in size + ** on disk, these values have been set in shared-memory by now, and so + ** are guaranteed not to change during the lifetime of this connection. */ + if( LSM_OK==rc + && 0==lsmCheckpointClientCacheOk(db) + && LSM_OK==(rc=lsmCheckpointLoad(db, 0)) + ){ + lsmFsSetPageSize(db->pFS, lsmCheckpointPgsz(db->aSnapshot)); + lsmFsSetBlockSize(db->pFS, lsmCheckpointBlksz(db->aSnapshot)); + } + if( rc==LSM_OK ){ rc = lsmBeginReadTrans(db); } diff --git a/ext/lsm1/lsm_sorted.c b/ext/lsm1/lsm_sorted.c index 4a24e4b82..82a2a666b 100644 --- a/ext/lsm1/lsm_sorted.c +++ b/ext/lsm1/lsm_sorted.c @@ -657,14 +657,14 @@ static int btreeCursorLoadKey(BtreeCursor *pCsr){ return rc; } -static int btreeCursorPtr(u8 *aData, int nData, int iCell){ +static LsmPgno btreeCursorPtr(u8 *aData, int nData, int iCell){ int nCell; nCell = pageGetNRec(aData, nData); if( iCell>=nCell ){ - return (int)pageGetPtr(aData, nData); + return pageGetPtr(aData, nData); } - return (int)pageGetRecordPtr(aData, nData, iCell); + return pageGetRecordPtr(aData, nData, iCell); } static int btreeCursorNext(BtreeCursor *pCsr){ @@ -751,7 +751,7 @@ static int btreeCursorFirst(BtreeCursor *pCsr){ Page *pPg = 0; FileSystem *pFS = pCsr->pFS; - int iPg = (int)pCsr->pSeg->iRoot; + LsmPgno iPg = pCsr->pSeg->iRoot; do { rc = lsmFsDbPageGet(pFS, pCsr->pSeg, iPg, &pPg); @@ -779,7 +779,7 @@ static int btreeCursorFirst(BtreeCursor *pCsr){ assert( pCsr->aPg[pCsr->nDepth].iCell==0 ); pCsr->aPg[pCsr->nDepth].pPage = pPg; pCsr->nDepth++; - iPg = (int)pageGetRecordPtr(aData, nData, 0); + iPg = pageGetRecordPtr(aData, nData, 0); } } }while( rc==LSM_OK ); @@ -871,7 +871,7 @@ static int btreeCursorRestore( int nSeek; int iTopicSeek; int iPg = 0; - int iLoad = (int)pSeg->iRoot; + LsmPgno iLoad = pSeg->iRoot; Page *pPg = pCsr->aPg[nDepth-1].pPage; if( pageObjGetNRec(pPg)==0 ){ @@ -903,7 +903,7 @@ static int btreeCursorRestore( aData = fsPageData(pPg2, &nData); assert( (pageGetFlags(aData, nData) & SEGMENT_BTREE_FLAG) ); - iLoad = (int)pageGetPtr(aData, nData); + iLoad = pageGetPtr(aData, nData); iCell2 = pageGetNRec(aData, nData); iMax = iCell2-1; iMin = 0; @@ -926,7 +926,7 @@ static int btreeCursorRestore( assert( res!=0 ); if( res<0 ){ - iLoad = (int)iPtr; + iLoad = iPtr; iCell2 = iTry; iMax = iTry-1; }else{ @@ -1013,7 +1013,7 @@ static void segmentPtrSetPage(SegmentPtr *pPtr, Page *pNext){ static int segmentPtrLoadPage( FileSystem *pFS, SegmentPtr *pPtr, /* Load page into this SegmentPtr object */ - int iNew /* Page number of new page */ + LsmPgno iNew /* Page number of new page */ ){ Page *pPg = 0; /* The new page */ int rc; /* Return Code */ @@ -1633,7 +1633,7 @@ static int segmentPtrSeek( int iTopic, /* Key topic to seek to */ void *pKey, int nKey, /* Key to seek to */ int eSeek, /* Search bias - see above */ - int *piPtr, /* OUT: FC pointer */ + LsmPgno *piPtr, /* OUT: FC pointer */ int *pbStop ){ int (*xCmp)(void *, int, void *, int) = pCsr->pDb->xCmp; @@ -1759,7 +1759,7 @@ static int segmentPtrSeek( } assert( rc!=LSM_OK || assertSeekResult(pCsr,pPtr,iTopic,pKey,nKey,eSeek) ); - *piPtr = (int)iPtrOut; + *piPtr = iPtrOut; return rc; } @@ -1773,11 +1773,11 @@ static int seekInBtree( ){ int i = 0; int rc; - int iPg; + LsmPgno iPg; Page *pPg = 0; LsmBlob blob = {0, 0, 0}; - iPg = (int)pSeg->iRoot; + iPg = pSeg->iRoot; do { LsmPgno *piFirst = 0; if( aPg ){ @@ -1799,7 +1799,7 @@ static int seekInBtree( flags = pageGetFlags(aData, nData); if( (flags & SEGMENT_BTREE_FLAG)==0 ) break; - iPg = (int)pageGetPtr(aData, nData); + iPg = pageGetPtr(aData, nData); nRec = pageGetNRec(aData, nData); iMin = 0; @@ -1825,7 +1825,7 @@ static int seekInBtree( pCsr->pDb->xCmp, iTopic, pKey, nKey, iTopicT, pKeyT, nKeyT ); if( res<0 ){ - iPg = (int)iPtr; + iPg = iPtr; iMax = iTry-1; }else{ iMin = iTry+1; @@ -1851,12 +1851,12 @@ static int seekInSegment( SegmentPtr *pPtr, int iTopic, void *pKey, int nKey, - int iPg, /* Page to search */ + LsmPgno iPg, /* Page to search */ int eSeek, /* Search bias - see above */ - int *piPtr, /* OUT: FC pointer */ + LsmPgno *piPtr, /* OUT: FC pointer */ int *pbStop /* OUT: Stop search flag */ ){ - int iPtr = iPg; + LsmPgno iPtr = iPg; int rc = LSM_OK; if( pPtr->pSeg->iRoot ){ @@ -1866,7 +1866,7 @@ static int seekInSegment( if( rc==LSM_OK ) segmentPtrSetPage(pPtr, pPg); }else{ if( iPtr==0 ){ - iPtr = (int)pPtr->pSeg->iFirst; + iPtr = pPtr->pSeg->iFirst; } if( rc==LSM_OK ){ rc = segmentPtrLoadPage(pCsr->pDb->pFS, pPtr, iPtr); @@ -1904,7 +1904,7 @@ static int seekInLevel( ){ Level *pLvl = aPtr[0].pLevel; /* Level to seek within */ int rc = LSM_OK; /* Return code */ - int iOut = 0; /* Pointer to return to caller */ + LsmPgno iOut = 0; /* Pointer to return to caller */ int res = -1; /* Result of xCmp(pKey, split) */ int nRhs = pLvl->nRight; /* Number of right-hand-side segments */ int bStop = 0; @@ -1923,8 +1923,8 @@ static int seekInLevel( ** left-hand-side of the level in this case. */ if( res<0 ){ int i; - int iPtr = 0; - if( nRhs==0 ) iPtr = (int)*piPgno; + LsmPgno iPtr = 0; + if( nRhs==0 ) iPtr = *piPgno; rc = seekInSegment( pCsr, &aPtr[0], iTopic, pKey, nKey, iPtr, eSeek, &iOut, &bStop @@ -1939,7 +1939,7 @@ static int seekInLevel( if( res>=0 ){ int bHit = 0; /* True if at least one rhs is not EOF */ - int iPtr = (int)*piPgno; + LsmPgno iPtr = *piPgno; int i; segmentPtrReset(&aPtr[0], LSM_SEGMENTPTR_FREE_THRESHOLD); for(i=1; rc==LSM_OK && i<=nRhs && bStop==0; i++){ @@ -3480,7 +3480,7 @@ static int mergeWorkerLoadHierarchy(MergeWorker *pMW){ lsm_env *pEnv = pMW->pDb->pEnv; Page **apHier = 0; int nHier = 0; - int iPg = (int)pSeg->iRoot; + LsmPgno iPg = pSeg->iRoot; do { Page *pPg = 0; @@ -3506,7 +3506,7 @@ static int mergeWorkerLoadHierarchy(MergeWorker *pMW){ nHier++; apHier[0] = pPg; - iPg = (int)pageGetPtr(aData, nData); + iPg = pageGetPtr(aData, nData); }else{ lsmFsPageRelease(pPg); break; @@ -3625,10 +3625,11 @@ static int mergeWorkerBtreeWrite( assert( lsmFsPageWritable(pOld) ); aData = fsPageData(pOld, &nData); if( eType==0 ){ - nByte = 2 + 1 + lsmVarintLen32((int)iPtr) + lsmVarintLen32((int)iKeyPg); + nByte = 2 + 1 + lsmVarintLen64(iPtr) + lsmVarintLen64(iKeyPg); }else{ - nByte = 2 + 1 + lsmVarintLen32((int)iPtr) + lsmVarintLen32(nKey) + nKey; + nByte = 2 + 1 + lsmVarintLen64(iPtr) + lsmVarintLen32(nKey) + nKey; } + nRec = pageGetNRec(aData, nData); nFree = SEGMENT_EOF(nData, nRec) - mergeWorkerPageOffset(aData, nData); if( nByte<=nFree ) break; @@ -3672,11 +3673,11 @@ static int mergeWorkerBtreeWrite( lsmPutU16(&aData[SEGMENT_NRECORD_OFFSET(nData)], (u16)(nRec+1)); if( eType==0 ){ aData[iOff++] = 0x00; - iOff += lsmVarintPut32(&aData[iOff], (int)iPtr); - iOff += lsmVarintPut32(&aData[iOff], (int)iKeyPg); + iOff += lsmVarintPut64(&aData[iOff], iPtr); + iOff += lsmVarintPut64(&aData[iOff], iKeyPg); }else{ aData[iOff++] = eType; - iOff += lsmVarintPut32(&aData[iOff], (int)iPtr); + iOff += lsmVarintPut64(&aData[iOff], iPtr); iOff += lsmVarintPut32(&aData[iOff], nKey); memcpy(&aData[iOff], pKey, nKey); } @@ -3872,7 +3873,7 @@ static int mergeWorkerNextPage( static int mergeWorkerData( MergeWorker *pMW, /* Merge worker object */ int bSep, /* True to write to separators run */ - int iFPtr, /* Footer ptr for new pages */ + LsmPgno iFPtr, /* Footer ptr for new pages */ u8 *aWrite, /* Write data from this buffer */ int nWrite /* Size of aWrite[] in bytes */ ){ @@ -3916,14 +3917,14 @@ static int mergeWorkerData( static int mergeWorkerFirstPage(MergeWorker *pMW){ int rc = LSM_OK; /* Return code */ Page *pPg = 0; /* First page of run pSeg */ - int iFPtr = 0; /* Pointer value read from footer of pPg */ + LsmPgno iFPtr = 0; /* Pointer value read from footer of pPg */ MultiCursor *pCsr = pMW->pCsr; assert( pMW->pPage==0 ); if( pCsr->pBtCsr ){ rc = LSM_OK; - iFPtr = (int)pMW->pLevel->pNext->lhs.iFirst; + iFPtr = pMW->pLevel->pNext->lhs.iFirst; }else if( pCsr->nPtr>0 ){ Segment *pSeg; pSeg = pCsr->aPtr[pCsr->nPtr-1].pSeg; @@ -3932,7 +3933,7 @@ static int mergeWorkerFirstPage(MergeWorker *pMW){ u8 *aData; /* Buffer for page pPg */ int nData; /* Size of aData[] in bytes */ aData = fsPageData(pPg, &nData); - iFPtr = (int)pageGetPtr(aData, nData); + iFPtr = pageGetPtr(aData, nData); lsmFsPageRelease(pPg); } } @@ -3951,7 +3952,7 @@ static int mergeWorkerWrite( int eType, /* One of SORTED_SEPARATOR, WRITE or DELETE */ void *pKey, int nKey, /* Key value */ void *pVal, int nVal, /* Value value */ - int iPtr /* Absolute value of page pointer, or 0 */ + LsmPgno iPtr /* Absolute value of page pointer, or 0 */ ){ int rc = LSM_OK; /* Return code */ Merge *pMerge; /* Persistent part of level merge state */ @@ -3960,8 +3961,8 @@ static int mergeWorkerWrite( u8 *aData; /* Data buffer for page pWriter->pPage */ int nData = 0; /* Size of buffer aData[] in bytes */ int nRec = 0; /* Number of records on page pPg */ - int iFPtr = 0; /* Value of pointer in footer of pPg */ - int iRPtr = 0; /* Value of pointer written into record */ + LsmPgno iFPtr = 0; /* Value of pointer in footer of pPg */ + LsmPgno iRPtr = 0; /* Value of pointer written into record */ int iOff = 0; /* Current write offset within page pPg */ Segment *pSeg; /* Segment being written */ int flags = 0; /* If != 0, flags value for page footer */ @@ -3978,7 +3979,7 @@ static int mergeWorkerWrite( if( pPg ){ aData = fsPageData(pPg, &nData); nRec = pageGetNRec(aData, nData); - iFPtr = (int)pageGetPtr(aData, nData); + iFPtr = pageGetPtr(aData, nData); iRPtr = iPtr - iFPtr; } @@ -4009,7 +4010,7 @@ static int mergeWorkerWrite( assert( aData ); memset(&aData[iOff], 0, SEGMENT_EOF(nData, nRec)-iOff); } - iFPtr = (int)*pMW->pCsr->pPrevMergePtr; + iFPtr = *pMW->pCsr->pPrevMergePtr; iRPtr = iPtr - iFPtr; iOff = 0; nRec = 0; @@ -4283,7 +4284,7 @@ static int mergeWorkerStep(MergeWorker *pMW){ pVal = pCsr->val.pData; } if( rc==LSM_OK ){ - rc = mergeWorkerWrite(pMW, eType, pKey, nKey, pVal, nVal, (int)iPtr); + rc = mergeWorkerWrite(pMW, eType, pKey, nKey, pVal, nVal, iPtr); } } } @@ -4604,7 +4605,7 @@ static int mergeWorkerInit( SegmentPtr *pPtr; assert( pCsr->aPtr[i].pPg==0 ); pPtr = &pCsr->aPtr[i]; - rc = segmentPtrLoadPage(pDb->pFS, pPtr, (int)pInput->iPg); + rc = segmentPtrLoadPage(pDb->pFS, pPtr, pInput->iPg); if( rc==LSM_OK && pPtr->nCell>0 ){ rc = segmentPtrLoadCell(pPtr, pInput->iCell); } @@ -5469,7 +5470,7 @@ int lsmFlushTreeToDisk(lsm_db *pDb){ ** be freed by the caller using lsmFree(). */ static char *segToString(lsm_env *pEnv, Segment *pSeg, int nMin){ - int nSize = pSeg->nSize; + LsmPgno nSize = pSeg->nSize; LsmPgno iRoot = pSeg->iRoot; LsmPgno iFirst = pSeg->iFirst; LsmPgno iLast = pSeg->iLastPg; @@ -5481,9 +5482,9 @@ static char *segToString(lsm_env *pEnv, Segment *pSeg, int nMin){ z1 = lsmMallocPrintf(pEnv, "%d.%d", iFirst, iLast); if( iRoot ){ - z2 = lsmMallocPrintf(pEnv, "root=%d", iRoot); + z2 = lsmMallocPrintf(pEnv, "root=%lld", iRoot); }else{ - z2 = lsmMallocPrintf(pEnv, "size=%d", nSize); + z2 = lsmMallocPrintf(pEnv, "size=%lld", nSize); } nPad = nMin - 2 - strlen(z1) - 1 - strlen(z2); @@ -5536,7 +5537,7 @@ void sortedDumpPage(lsm_db *pDb, Segment *pRun, Page *pPg, int bVals){ int i; int nRec; - int iPtr; + LsmPgno iPtr; int flags; u8 *aData; int nData; @@ -5544,11 +5545,11 @@ void sortedDumpPage(lsm_db *pDb, Segment *pRun, Page *pPg, int bVals){ aData = fsPageData(pPg, &nData); nRec = pageGetNRec(aData, nData); - iPtr = (int)pageGetPtr(aData, nData); + iPtr = pageGetPtr(aData, nData); flags = pageGetFlags(aData, nData); lsmStringInit(&s, pDb->pEnv); - lsmStringAppendf(&s,"nCell=%d iPtr=%d flags=%d {", nRec, iPtr, flags); + lsmStringAppendf(&s,"nCell=%d iPtr=%lld flags=%d {", nRec, iPtr, flags); if( flags&SEGMENT_BTREE_FLAG ) iPtr = 0; for(i=0; i<nRec; i++){ @@ -5558,13 +5559,13 @@ void sortedDumpPage(lsm_db *pDb, Segment *pRun, Page *pPg, int bVals){ u8 *aVal = 0; int nVal = 0; /* Value */ int iTopic; u8 *aCell; - int iPgPtr; + i64 iPgPtr; int eType; aCell = pageGetCell(aData, nData, i); eType = *aCell++; assert( (flags & SEGMENT_BTREE_FLAG) || eType!=0 ); - aCell += lsmVarintGet32(aCell, &iPgPtr); + aCell += lsmVarintGet64(aCell, &iPgPtr); if( eType==0 ){ LsmPgno iRef; /* Page number of referenced page */ @@ -5590,7 +5591,7 @@ void sortedDumpPage(lsm_db *pDb, Segment *pRun, Page *pPg, int bVals){ } } - lsmStringAppendf(&s, " %d", iPgPtr+iPtr); + lsmStringAppendf(&s, " %lld", iPgPtr+iPtr); lsmFsPageRelease(pRef); } lsmStringAppend(&s, "}", 1); @@ -5720,20 +5721,20 @@ static int infoPageDump( int nKeyWidth = 0; LsmString str; int nRec; - int iPtr; + LsmPgno iPtr; int flags2; int iCell; u8 *aData; int nData; /* Page data and size thereof */ aData = fsPageData(pPg, &nData); nRec = pageGetNRec(aData, nData); - iPtr = (int)pageGetPtr(aData, nData); + iPtr = pageGetPtr(aData, nData); flags2 = pageGetFlags(aData, nData); lsmStringInit(&str, pDb->pEnv); lsmStringAppendf(&str, "Page : %lld (%d bytes)\n", iPg, nData); lsmStringAppendf(&str, "nRec : %d\n", nRec); - lsmStringAppendf(&str, "iPtr : %d\n", iPtr); + lsmStringAppendf(&str, "iPtr : %lld\n", iPtr); lsmStringAppendf(&str, "flags: %04x\n", flags2); lsmStringAppendf(&str, "\n"); diff --git a/ext/lsm1/lsm_varint.c b/ext/lsm1/lsm_varint.c index c550a6405..f690e3b06 100644 --- a/ext/lsm1/lsm_varint.c +++ b/ext/lsm1/lsm_varint.c @@ -185,6 +185,11 @@ int lsmVarintLen32(int n){ return lsmVarintPut32(aData, n); } +int lsmVarintLen64(i64 n){ + u8 aData[9]; + return lsmVarintPut64(aData, n); +} + /* ** The argument is the first byte of a varint. This function returns the ** total number of bytes in the entire varint (including the first byte). diff --git a/ext/misc/base64.c b/ext/misc/base64.c index 20297367e..69eff6181 100755 --- a/ext/misc/base64.c +++ b/ext/misc/base64.c @@ -55,6 +55,15 @@ #include "sqlite3ext.h" +#ifndef deliberate_fall_through +/* Quiet some compilers about some of our intentional code. */ +# if GCC_VERSION>=7000000 +# define deliberate_fall_through __attribute__((fallthrough)); +# else +# define deliberate_fall_through +# endif +#endif + SQLITE_EXTENSION_INIT1; #define PC 0x80 /* pad character */ @@ -165,15 +174,15 @@ static u8* fromBase64( char *pIn, int ncIn, u8 *pOut ){ case ND: /* Treat dark non-digits as pad, but they terminate decode too. */ ncIn = 0; - /* fall thru */ + deliberate_fall_through; case WS: /* Treat whitespace as pad and terminate this group.*/ nti = nac; - /* fall thru */ + deliberate_fall_through; case PC: bdp = 0; --nbo; - /* fall thru */ + deliberate_fall_through; default: /* bdp is the digit value. */ qv = qv<<6 | bdp; break; diff --git a/ext/misc/zipfile.c b/ext/misc/zipfile.c index ba55b09cf..480fbe399 100644 --- a/ext/misc/zipfile.c +++ b/ext/misc/zipfile.c @@ -1285,7 +1285,7 @@ static int zipfileFilter( } if( 0==pTab->pWriteFd && 0==bInMemory ){ - pCsr->pFile = fopen(zFile, "rb"); + pCsr->pFile = zFile ? fopen(zFile, "rb") : 0; if( pCsr->pFile==0 ){ zipfileCursorErr(pCsr, "cannot open file: %s", zFile); rc = SQLITE_ERROR; diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api index 75e5ea3da..ad2872d83 100644 --- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api +++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api @@ -1,4 +1,8 @@ +_malloc +_free +_realloc _sqlite3_aggregate_context +_sqlite3_auto_extension _sqlite3_bind_blob _sqlite3_bind_double _sqlite3_bind_int @@ -10,6 +14,7 @@ _sqlite3_bind_pointer _sqlite3_bind_text _sqlite3_busy_handler _sqlite3_busy_timeout +_sqlite3_cancel_auto_extension _sqlite3_changes _sqlite3_changes64 _sqlite3_clear_bindings @@ -26,9 +31,11 @@ _sqlite3_column_name _sqlite3_column_text _sqlite3_column_type _sqlite3_column_value +_sqlite3_commit_hook _sqlite3_compileoption_get _sqlite3_compileoption_used _sqlite3_complete +_sqlite3_context_db_handle _sqlite3_create_collation _sqlite3_create_collation_v2 _sqlite3_create_function @@ -72,11 +79,18 @@ _sqlite3_open_v2 _sqlite3_overload_function _sqlite3_prepare_v2 _sqlite3_prepare_v3 +_sqlite3_preupdate_blobwrite +_sqlite3_preupdate_count +_sqlite3_preupdate_depth +_sqlite3_preupdate_hook +_sqlite3_preupdate_new +_sqlite3_preupdate_old _sqlite3_progress_handler _sqlite3_randomness _sqlite3_realloc _sqlite3_realloc64 _sqlite3_reset +_sqlite3_reset_auto_extension _sqlite3_result_blob _sqlite3_result_double _sqlite3_result_error @@ -91,6 +105,7 @@ _sqlite3_result_subtype _sqlite3_result_text _sqlite3_result_zeroblob _sqlite3_result_zeroblob64 +_sqlite3_rollback_hook _sqlite3_serialize _sqlite3_set_authorizer _sqlite3_set_auxdata @@ -113,6 +128,7 @@ _sqlite3_total_changes _sqlite3_total_changes64 _sqlite3_trace_v2 _sqlite3_txn_state +_sqlite3_update_hook _sqlite3_uri_boolean _sqlite3_uri_int64 _sqlite3_uri_key @@ -143,6 +159,45 @@ _sqlite3_vtab_in_next _sqlite3_vtab_nochange _sqlite3_vtab_on_conflict _sqlite3_vtab_rhs_value -_malloc -_free -_realloc +_sqlite3changegroup_add +_sqlite3changegroup_add_strm +_sqlite3changegroup_delete +_sqlite3changegroup_new +_sqlite3changegroup_output +_sqlite3changegroup_output_strm +_sqlite3changeset_apply +_sqlite3changeset_apply_strm +_sqlite3changeset_apply_v2 +_sqlite3changeset_apply_v2_strm +_sqlite3changeset_concat +_sqlite3changeset_concat_strm +_sqlite3changeset_conflict +_sqlite3changeset_finalize +_sqlite3changeset_fk_conflicts +_sqlite3changeset_invert +_sqlite3changeset_invert_strm +_sqlite3changeset_new +_sqlite3changeset_next +_sqlite3changeset_old +_sqlite3changeset_op +_sqlite3changeset_pk +_sqlite3changeset_start +_sqlite3changeset_start_strm +_sqlite3changeset_start_v2 +_sqlite3changeset_start_v2_strm +_sqlite3session_attach +_sqlite3session_changeset +_sqlite3session_changeset_size +_sqlite3session_changeset_strm +_sqlite3session_config +_sqlite3session_create +_sqlite3session_delete +_sqlite3session_diff +_sqlite3session_enable +_sqlite3session_indirect +_sqlite3session_isempty +_sqlite3session_memory_used +_sqlite3session_object_config +_sqlite3session_patchset +_sqlite3session_patchset_strm +_sqlite3session_table_filter diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js index 7b5fa1181..7db23bacc 100644 --- a/ext/wasm/api/sqlite3-api-glue.js +++ b/ext/wasm/api/sqlite3-api-glue.js @@ -24,7 +24,24 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ self.WhWasmUtilInstaller(wasm); delete self.WhWasmUtilInstaller; - { + if(0){ + /** + Please keep this block around as a maintenance reminder + that we cannot rely on this type of check. + + This block fails on Safari, per a report at + https://sqlite.org/forum/forumpost/e5b20e1feb. + + It turns out that what Safari serves from the indirect function + table (e.g. wasm.functionEntry(X)) is anonymous functions which + wrap the WASM functions, rather than returning the WASM + functions themselves. That means comparison of such functions + is useless for determining whether or not we have a specific + function from wasm.exports. i.e. if function X is indirection + function table entry N then wasm.exports.X is not equal to + wasm.functionEntry(N) in Safari, despite being so in the other + browsers. + */ /** Find a mapping for SQLITE_WASM_DEALLOC, which the API guarantees is a WASM pointer to the same underlying function as @@ -69,6 +86,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ wasm.bindingSignatures = [ // Please keep these sorted by function name! ["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"], + /* sqlite3_auto_extension() has a hand-written binding. */ /* sqlite3_bind_blob() and sqlite3_bind_text() have hand-written bindings to permit more flexible inputs. */ ["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"], @@ -82,12 +100,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "sqlite3*", new wasm.xWrap.FuncPtrAdapter({ signature: 'i(pi)', - contextKey: (argIndex,argv)=>'sqlite3@'+argv[0] + contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] }), "*" ]], ["sqlite3_busy_timeout","int", "sqlite3*", "int"], - ["sqlite3_close_v2", "int", "sqlite3*"], + /* sqlite3_cancel_auto_extension() has a hand-written binding. */ + /* sqlite3_close_v2() is implemented by hand to perform some + extra work. */ ["sqlite3_changes", "int", "sqlite3*"], ["sqlite3_clear_bindings","int", "sqlite3_stmt*"], ["sqlite3_collation_needed", "int", "sqlite3*", "*", "*"/*=>v(ppis)*/], @@ -100,9 +120,20 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_column_text","string", "sqlite3_stmt*", "int"], ["sqlite3_column_type","int", "sqlite3_stmt*", "int"], ["sqlite3_column_value","sqlite3_value*", "sqlite3_stmt*", "int"], + ["sqlite3_commit_hook", "void*", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_commit_hook', + signature: 'i(p)', + contextKey: (argv)=>argv[0/* sqlite3* */] + }), + '*' + ]], ["sqlite3_compileoption_get", "string", "int"], ["sqlite3_compileoption_used", "int", "string"], ["sqlite3_complete", "int", "string:flexible"], + ["sqlite3_context_db_handle", "sqlite3*", "sqlite3_context*"], + /* sqlite3_create_function(), sqlite3_create_function_v2(), and sqlite3_create_window_function() use hand-written bindings to simplify handling of their function-type arguments. */ @@ -125,9 +156,32 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_errmsg", "string", "sqlite3*"], ["sqlite3_error_offset", "int", "sqlite3*"], ["sqlite3_errstr", "string", "int"], - /*["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**" - Handled seperately to perform translation of the callback - into a WASM-usable one. ],*/ + ["sqlite3_exec", "int", [ + "sqlite3*", "string:flexible", + new wasm.xWrap.FuncPtrAdapter({ + signature: 'i(pipp)', + bindScope: 'transient', + callProxy: (callback)=>{ + let aNames; + return (pVoid, nCols, pColVals, pColNames)=>{ + try { + const aVals = wasm.cArgvToJs(nCols, pColVals); + if(!aNames) aNames = wasm.cArgvToJs(nCols, pColNames); + return callback(aVals, aNames) | 0; + }catch(e){ + /* If we set the db error state here, the higher-level + exec() call replaces it with its own, so we have no way + of reporting the exception message except the console. We + must not propagate exceptions through the C API. Though + we make an effort to report OOM here, sqlite3_exec() + translates that into SQLITE_ABORT as well. */ + return e.resultCode || capi.SQLITE_ERROR; + } + } + } + }), + "*", "**" + ]], ["sqlite3_expanded_sql", "string", "sqlite3_stmt*"], ["sqlite3_extended_errcode", "int", "sqlite3*"], ["sqlite3_extended_result_codes", "int", "sqlite3*", "int"], @@ -154,18 +208,17 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ for those, depending on how their SQL argument is provided. */ /* sqlite3_randomness() uses a hand-written wrapper to extend the range of supported argument types. */ - [ - "sqlite3_progress_handler", undefined, [ - "sqlite3*", "int", new wasm.xWrap.FuncPtrAdapter({ - name: 'xProgressHandler', - signature: 'i(p)', - bindScope: 'context', - contextKey: (argIndex,argv)=>'sqlite3@'+argv[0] - }), "*" - ] - ], + ["sqlite3_progress_handler", undefined, [ + "sqlite3*", "int", new wasm.xWrap.FuncPtrAdapter({ + name: 'xProgressHandler', + signature: 'i(p)', + bindScope: 'context', + contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] + }), "*" + ]], ["sqlite3_realloc", "*","*","int"], ["sqlite3_reset", "int", "sqlite3_stmt*"], + /* sqlite3_reset_auto_extension() has a hand-written binding. */ ["sqlite3_result_blob", undefined, "sqlite3_context*", "*", "int", "*"], ["sqlite3_result_double", undefined, "sqlite3_context*", "f64"], ["sqlite3_result_error", undefined, "sqlite3_context*", "string", "int"], @@ -179,7 +232,43 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_result_subtype", undefined, "sqlite3_value*", "int"], ["sqlite3_result_text", undefined, "sqlite3_context*", "string", "int", "*"], ["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"], - ["sqlite3_set_auxdata", undefined, "sqlite3_context*", "int", "*", "*"/* => v(*) */], + ["sqlite3_rollback_hook", "void*", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_rollback_hook', + signature: 'v(p)', + contextKey: (argv)=>argv[0/* sqlite3* */] + }), + '*' + ]], + ["sqlite3_set_authorizer", "int", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + name: "sqlite3_set_authorizer::xAuth", + signature: "i(pi"+"ssss)", + contextKey: (argv, argIndex)=>argv[0/*(sqlite3*)*/], + callProxy: (callback)=>{ + return (pV, iCode, s0, s1, s2, s3)=>{ + try{ + s0 = s0 && wasm.cstrToJs(s0); s1 = s1 && wasm.cstrToJs(s1); + s2 = s2 && wasm.cstrToJs(s2); s3 = s3 && wasm.cstrToJs(s3); + return callback(pV, iCode, s0, s1, s2, s3) || 0; + }catch(e){ + return e.resultCode || capi.SQLITE_ERROR; + } + } + } + }), + "*"/*pUserData*/ + ]], + ["sqlite3_set_auxdata", undefined, [ + "sqlite3_context*", "int", "*", + new wasm.xWrap.FuncPtrAdapter({ + name: 'xDestroyAuxData', + signature: 'v(*)', + contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */] + }) + ]], ["sqlite3_shutdown", undefined], ["sqlite3_sourceid", "string"], ["sqlite3_sql", "string", "sqlite3_stmt*"], @@ -196,12 +285,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "sqlite3*", "string", "string", "string", "**", "**", "*", "*", "*"], ["sqlite3_total_changes", "int", "sqlite3*"], - ["sqlite3_trace_v2", "int", "sqlite3*", "int", - new wasm.xWrap.FuncPtrAdapter({ - name: 'sqlite3_trace_v2::callback', - signature: 'i(ippp)', - contextKey: (argIndex, argv)=>'sqlite3@'+argv[0] - }), "*"], + ["sqlite3_trace_v2", "int", [ + "sqlite3*", "int", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_trace_v2::callback', + signature: 'i(ippp)', + contextKey: (argv,argIndex)=>argv[0/* sqlite3* */] + }), + "*" + ]], ["sqlite3_txn_state", "int", ["sqlite3*","string"]], /* Note that sqlite3_uri_...() have very specific requirements for their first C-string arguments, so we cannot perform any value @@ -234,7 +326,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ optional features into account. */ wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]); } - + /** Functions which require BigInt (int64) support are separated from the others because we need to conditionally bind them or apply @@ -263,15 +355,47 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_malloc64", "*","i64"], ["sqlite3_msize", "i64", "*"], ["sqlite3_overload_function", "int", ["sqlite3*","string","int"]], + ["sqlite3_preupdate_blobwrite", "int", "sqlite3*"], + ["sqlite3_preupdate_count", "int", "sqlite3*"], + ["sqlite3_preupdate_depth", "int", "sqlite3*"], + ["sqlite3_preupdate_hook", "*", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_preupdate_hook', + signature: "v(ppippjj)", + contextKey: (argv)=>argv[0/* sqlite3* */], + callProxy: (callback)=>{ + return (p,db,op,zDb,zTbl,iKey1,iKey2)=>{ + callback(p, db, op, wasm.cstrToJs(zDb), wasm.cstrToJs(zTbl), + iKey1, iKey2); + }; + } + }), + "*" + ]], + ["sqlite3_preupdate_new", "int", ["sqlite3*", "int", "**"]], + ["sqlite3_preupdate_old", "int", ["sqlite3*", "int", "**"]], ["sqlite3_realloc64", "*","*", "i64"], ["sqlite3_result_int64", undefined, "*", "i64"], ["sqlite3_result_zeroblob64", "int", "*", "i64"], ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"], - /* sqlite3_set_authorizer() requires a hand-written binding for - string conversions, so is defined elsewhere. */ ["sqlite3_set_last_insert_rowid", undefined, ["sqlite3*", "i64"]], ["sqlite3_status64", "int", "int", "*", "*", "int"], ["sqlite3_total_changes64", "i64", ["sqlite3*"]], + ["sqlite3_update_hook", "*", [ + "sqlite3*", + new wasm.xWrap.FuncPtrAdapter({ + name: 'sqlite3_update_hook', + signature: "v(iippj)", + contextKey: (argv)=>argv[0/* sqlite3* */], + callProxy: (callback)=>{ + return (p,op,z0,z1,rowid)=>{ + callback(p, op, wasm.cstrToJs(z0), wasm.cstrToJs(z1), rowid); + }; + } + }), + "*" + ]], ["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]], ["sqlite3_value_int64","i64", "sqlite3_value*"], ["sqlite3_vtab_collation","string","sqlite3_index_info*","int"], @@ -286,6 +410,184 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"] ]; + // Add session/changeset APIs... + if(wasm.bigIntEnabled && !!wasm.exports.sqlite3changegroup_add){ + /* ACHTUNG: 2022-12-23: the session/changeset API bindings are + COMPLETELY UNTESTED. */ + /** + FuncPtrAdapter options for session-related callbacks with the + native signature "i(ps)". This proxy converts the 2nd argument + from a C string to a JS string before passing the arguments on + to the client-provided JS callback. + */ + const __ipsProxy = { + signature: 'i(ps)', + callProxy:(callback)=>{ + return (p,s)=>{ + try{return callback(p, wasm.cstrToJs(s)) | 0} + catch(e){return e.resultCode || capi.SQLITE_ERROR} + } + } + }; + + wasm.bindingSignatures.int64.push(...[ + ['sqlite3changegroup_add', 'int', ['sqlite3_changegroup*', 'int', 'void*']], + ['sqlite3changegroup_add_strm', 'int', [ + 'sqlite3_changegroup*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changegroup_delete', undefined, ['sqlite3_changegroup*']], + ['sqlite3changegroup_new', 'int', ['**']], + ['sqlite3changegroup_output', 'int', ['sqlite3_changegroup*', 'int*', '**']], + ['sqlite3changegroup_output_strm', 'int', [ + 'sqlite3_changegroup*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_apply', 'int', [ + 'sqlite3*', 'int', 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xFilter', bindScope: 'transient', ...__ipsProxy + }), + new wasm.xWrap.FuncPtrAdapter({ + name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_apply_strm', 'int', [ + 'sqlite3*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xFilter', bindScope: 'transient', ...__ipsProxy + }), + new wasm.xWrap.FuncPtrAdapter({ + name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_apply_v2', 'int', [ + 'sqlite3*', 'int', 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xFilter', bindScope: 'transient', ...__ipsProxy + }), + new wasm.xWrap.FuncPtrAdapter({ + name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' + }), + 'void*', '**', 'int*', 'int' + + ]], + ['sqlite3changeset_apply_v2_strm', 'int', [ + 'sqlite3*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xFilter', bindScope: 'transient', ...__ipsProxy + }), + new wasm.xWrap.FuncPtrAdapter({ + name: 'xConflict', signature: 'i(pip)', bindScope: 'transient' + }), + 'void*', '**', 'int*', 'int' + ]], + ['sqlite3changeset_concat', 'int', ['int','void*', 'int', 'void*', 'int*', '**']], + ['sqlite3changeset_concat_strm', 'int', [ + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInputA', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInputB', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_conflict', 'int', ['sqlite3_changeset_iter*', 'int', '**']], + ['sqlite3changeset_finalize', 'int', ['sqlite3_changeset_iter*']], + ['sqlite3changeset_fk_conflicts', 'int', ['sqlite3_changeset_iter*', 'int*']], + ['sqlite3changeset_invert', 'int', ['int', 'void*', 'int*', '**']], + ['sqlite3changeset_invert_strm', 'int', [ + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xOutput', signature: 'i(ppi)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_new', 'int', ['sqlite3_changeset_iter*', 'int', '**']], + ['sqlite3changeset_next', 'int', ['sqlite3_changeset_iter*']], + ['sqlite3changeset_old', 'int', ['sqlite3_changeset_iter*', 'int', '**']], + ['sqlite3changeset_op', 'int', [ + 'sqlite3_changeset_iter*', '**', 'int*', 'int*','int*' + ]], + ['sqlite3changeset_pk', 'int', ['sqlite3_changeset_iter*', '**', 'int*']], + ['sqlite3changeset_start', 'int', ['**', 'int', '*']], + ['sqlite3changeset_start_strm', 'int', [ + '**', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3changeset_start_v2', 'int', ['**', 'int', '*', 'int']], + ['sqlite3changeset_start_v2_strm', 'int', [ + '**', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xInput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*', 'int' + ]], + ['sqlite3session_attach', 'int', ['sqlite3_session*', 'string']], + ['sqlite3session_changeset', 'int', ['sqlite3_session*', 'int*', '**']], + ['sqlite3session_changeset_size', 'i64', ['sqlite3_session*']], + ['sqlite3session_changeset_strm', 'int', [ + 'sqlite3_session*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3session_config', 'int', ['int', 'void*']], + ['sqlite3session_create', 'int', ['sqlite3*', 'string', '**']], + //sqlite3session_delete() is bound manually + ['sqlite3session_diff', 'int', ['sqlite3_session*', 'string', 'string', '**']], + ['sqlite3session_enable', 'int', ['sqlite3_session*', 'int']], + ['sqlite3session_indirect', 'int', ['sqlite3_session*', 'int']], + ['sqlite3session_isempty', 'int', ['sqlite3_session*']], + ['sqlite3session_memory_used', 'i64', ['sqlite3_session*']], + ['sqlite3session_object_config', 'int', ['sqlite3_session*', 'int', 'void*']], + ['sqlite3session_patchset', 'int', ['sqlite3_session*', '*', '**']], + ['sqlite3session_patchset_strm', 'int', [ + 'sqlite3_session*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xOutput', signature: 'i(ppp)', bindScope: 'transient' + }), + 'void*' + ]], + ['sqlite3session_table_filter', undefined, [ + 'sqlite3_session*', + new wasm.xWrap.FuncPtrAdapter({ + name: 'xFilter', ...__ipsProxy, + contextKey: (argv,argIndex)=>argv[0/* (sqlite3_session*) */] + }), + '*' + ]] + ]); + }/*session/changeset APIs*/ + /** Functions which are intended solely for API-internal use by the WASM components, not client code. These get installed into @@ -313,8 +615,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }); delete self.Jaccwabyt; - {/* Convert Arrays and certain TypedArrays to strings for - 'string:flexible'-type arguments */ + {// wasm.xWrap() bindings... + + /* Convert Arrays and certain TypedArrays to strings for + 'string:flexible'-type arguments */ const __xString = wasm.xWrap.argAdapter('string'); wasm.xWrap.argAdapter( 'string:flexible', (v)=>__xString(util.flexibleString(v)) @@ -350,33 +654,35 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return rc || (this[v] = wasm.allocCString(v)); }.bind(Object.create(null)) ); - }/* special-case string-type argument conversions */ - if(1){// wasm.xWrap() bindings... /** Add some descriptive xWrap() aliases for '*' intended to (A) - initially improve readability/correctness of capi.signatures - and (B) provide automatic conversion from higher-level - representations, e.g. capi.sqlite3_vfs to `sqlite3_vfs*` via - capi.sqlite3_vfs.pointer. + initially improve readability/correctness of + wasm.bindingSignatures and (B) provide automatic conversion + from higher-level representations, e.g. capi.sqlite3_vfs to + `sqlite3_vfs*` via capi.sqlite3_vfs.pointer. */ - const aPtr = wasm.xWrap.argAdapter('*'); - const nilType = function(){}; - wasm.xWrap.argAdapter('sqlite3_filename', aPtr) - ('sqlite3_context*', aPtr) - ('sqlite3_value*', aPtr) - ('void*', aPtr) + const __xArgPtr = wasm.xWrap.argAdapter('*'); + const nilType = function(){}/*a class no value can ever be an instance of*/; + wasm.xWrap.argAdapter('sqlite3_filename', __xArgPtr) + ('sqlite3_context*', __xArgPtr) + ('sqlite3_value*', __xArgPtr) + ('void*', __xArgPtr) + ('sqlite3_changegroup*', __xArgPtr) + ('sqlite3_changeset_iter*', __xArgPtr) + //('sqlite3_rebaser*', __xArgPtr) + ('sqlite3_session*', __xArgPtr) ('sqlite3_stmt*', (v)=> - aPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType)) + __xArgPtr((v instanceof (sqlite3?.oo1?.Stmt || nilType)) ? v.pointer : v)) ('sqlite3*', (v)=> - aPtr((v instanceof (sqlite3?.oo1?.DB || nilType)) + __xArgPtr((v instanceof (sqlite3?.oo1?.DB || nilType)) ? v.pointer : v)) ('sqlite3_index_info*', (v)=> - aPtr((v instanceof (capi.sqlite3_index_info || nilType)) + __xArgPtr((v instanceof (capi.sqlite3_index_info || nilType)) ? v.pointer : v)) ('sqlite3_module*', (v)=> - aPtr((v instanceof (capi.sqlite3_module || nilType)) + __xArgPtr((v instanceof (capi.sqlite3_module || nilType)) ? v.pointer : v)) /** `sqlite3_vfs*`: @@ -397,8 +703,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ "Unknown sqlite3_vfs name:", v ); } - return aPtr((v instanceof (capi.sqlite3_vfs || nilType)) - ? v.pointer : v); + return __xArgPtr((v instanceof (capi.sqlite3_vfs || nilType)) + ? v.pointer : v); }); const __xRcPtr = wasm.xWrap.resultAdapter('*'); @@ -467,7 +773,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ message = message || ''+resultCode; resultCode = (resultCode.resultCode || capi.SQLITE_ERROR); } - return __db_err(pDb, resultCode, message); + return pDb ? __db_err(pDb, resultCode, message) : resultCode; }; }else{ util.sqlite3_wasm_db_error = function(pDb,errCode,msg){ @@ -475,7 +781,87 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return errCode; }; } - }/*xWrap() bindings*/; + }/*xWrap() bindings*/ + + {/* Import C-level constants and structs... */ + const cJson = wasm.xCall('sqlite3_wasm_enum_json'); + if(!cJson){ + toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", + "static buffer size!"); + } + //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); + wasm.ctype = JSON.parse(wasm.cstrToJs(cJson)); + // Groups of SQLITE_xyz macros... + const defineGroups = ['access', 'authorizer', + 'blobFinalizers', 'changeset', + 'config', 'dataTypes', + 'dbConfig', 'dbStatus', + 'encodings', 'fcntl', 'flock', 'ioCap', + 'limits', 'openFlags', + 'prepareFlags', 'resultCodes', + 'sqlite3Status', + 'stmtStatus', 'syncFlags', + 'trace', 'txnState', 'udfFlags', + 'version' ]; + if(wasm.bigIntEnabled){ + defineGroups.push('serialize', 'session', 'vtab'); + } + for(const t of defineGroups){ + for(const e of Object.entries(wasm.ctype[t])){ + // ^^^ [k,v] there triggers a buggy code transformation via + // one of the Emscripten-driven optimizers. + capi[e[0]] = e[1]; + } + } + if(!wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){ + toss("Internal error: cannot resolve exported function", + "entry SQLITE_WASM_DEALLOC (=="+capi.SQLITE_WASM_DEALLOC+")."); + } + const __rcMap = Object.create(null); + for(const t of ['resultCodes']){ + for(const e of Object.entries(wasm.ctype[t])){ + __rcMap[e[1]] = e[0]; + } + } + /** + For the given integer, returns the SQLITE_xxx result code as a + string, or undefined if no such mapping is found. + */ + capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; + /* Bind all registered C-side structs... */ + const notThese = Object.assign(Object.create(null),{ + // For each struct to NOT register, map its name to true: + WasmTestStruct: true, + /* We unregister the kvvfs VFS from Worker threads below. */ + sqlite3_kvvfs_methods: !util.isUIThread(), + /* sqlite3_index_info and friends require int64: */ + sqlite3_index_info: !wasm.bigIntEnabled, + sqlite3_index_constraint: !wasm.bigIntEnabled, + sqlite3_index_orderby: !wasm.bigIntEnabled, + sqlite3_index_constraint_usage: !wasm.bigIntEnabled + }); + for(const s of wasm.ctype.structs){ + if(!notThese[s.name]){ + capi[s.name] = sqlite3.StructBinder(s); + } + } + if(capi.sqlite3_index_info){ + /* Move these inner structs into sqlite3_index_info. Binding + ** them to WASM requires that we create global-scope structs to + ** model them with, but those are no longer needed after we've + ** passed them to StructBinder. */ + for(const k of ['sqlite3_index_constraint', + 'sqlite3_index_orderby', + 'sqlite3_index_constraint_usage']){ + capi.sqlite3_index_info[k] = capi[k]; + delete capi[k]; + } + capi.sqlite3_vtab_config = wasm.xWrap( + 'sqlite3_wasm_vtab_config','int',[ + 'sqlite3*', 'int', 'int'] + ); + }/* end vtab-related setup */ + }/*end C constant and struct imports*/ /** Internal helper to assist in validating call argument counts in @@ -497,28 +883,191 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ ); }; - if(1){/* Bindings for sqlite3_create_collation[_v2]() */ - const __collationContextKey = (argIndex,argv)=>{ - return 'argv['+argIndex+']:sqlite3@'+argv[0]+ - ':'+wasm.cstrToJs(argv[1]).toLowerCase() + /** + __dbCleanupMap is infrastructure for recording registration of + UDFs and collations so that sqlite3_close_v2() can clean up any + automated JS-to-WASM function conversions installed by those. + */ + const __argPDb = (pDb)=>wasm.xWrap.argAdapter('sqlite3*')(pDb); + const __argStr = (str)=>wasm.isPtr(str) ? wasm.cstrToJs(str) : str; + const __dbCleanupMap = function( + pDb, mode/*0=remove, >0=create if needed, <0=do not create if missing*/ + ){ + pDb = __argPDb(pDb); + let m = this.dbMap.get(pDb); + if(!mode){ + this.dbMap.delete(pDb); + return m; + }else if(!m && mode>0){ + this.dbMap.set(pDb, (m = Object.create(null))); + } + return m; + }.bind(Object.assign(Object.create(null),{ + dbMap: new Map + })); + + __dbCleanupMap.addCollation = function(pDb, name){ + const m = __dbCleanupMap(pDb, 1); + if(!m.collation) m.collation = new Set; + m.collation.add(__argStr(name).toLowerCase()); + }; + + __dbCleanupMap._addUDF = function(pDb, name, arity, map){ + /* Map UDF name to a Set of arity values */ + name = __argStr(name).toLowerCase(); + let u = map.get(name); + if(!u) map.set(name, (u = new Set)); + u.add((arity<0) ? -1 : arity); + }; + + __dbCleanupMap.addFunction = function(pDb, name, arity){ + const m = __dbCleanupMap(pDb, 1); + if(!m.udf) m.udf = new Map; + this._addUDF(pDb, name, arity, m.udf); + }; + + __dbCleanupMap.addWindowFunc = function(pDb, name, arity){ + const m = __dbCleanupMap(pDb, 1); + if(!m.wudf) m.wudf = new Map; + this._addUDF(pDb, name, arity, m.wudf); + }; + + /** + Intended to be called _only_ from sqlite3_close_v2(), + passed its non-0 db argument. + + This function frees up certain automatically-installed WASM + function bindings which were installed on behalf of the given db, + as those may otherwise leak. + + Notable caveat: this is only ever run via + sqlite3.capi.sqlite3_close_v2(). If a client, for whatever + reason, uses sqlite3.wasm.exports.sqlite3_close_v2() (the + function directly exported from WASM), this cleanup will not + happen. + + This is not a silver bullet for avoiding automation-related + leaks but represents "an honest effort." + + The issue being addressed here is covered at: + + https://sqlite.org/wasm/doc/trunk/api-c-style.md#convert-func-ptr + */ + __dbCleanupMap.cleanup = function(pDb){ + pDb = __argPDb(pDb); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; + /** + Installing NULL functions in the C API will remove those + bindings. The FuncPtrAdapter which sits between us and the C + API will also treat that as an opportunity to + wasm.uninstallFunction() any WASM function bindings it has + installed for pDb. + */ + const closeArgs = [pDb]; + for(const name of [ + 'sqlite3_busy_handler', + 'sqlite3_commit_hook', + 'sqlite3_preupdate_hook', + 'sqlite3_progress_handler', + 'sqlite3_rollback_hook', + 'sqlite3_set_authorizer', + 'sqlite3_trace_v2', + 'sqlite3_update_hook' + ]) { + const x = wasm.exports[name]; + closeArgs.length = x.length/*==argument count*/ + /* recall that undefined entries translate to 0 when passed to + WASM. */; + try{ capi[name](...closeArgs) } + catch(e){ + console.warn("close-time call of",name+"(",closeArgs,") threw:",e); + } + } + const m = __dbCleanupMap(pDb, 0); + if(!m) return; + if(m.collation){ + for(const name of m.collation){ + try{ + capi.sqlite3_create_collation_v2( + pDb, name, capi.SQLITE_UTF8, 0, 0, 0 + ); + }catch(e){ + /*ignored*/ + } + } + delete m.collation; + } + let i; + for(i = 0; i < 2; ++i){ /* Clean up UDFs... */ + const fmap = i ? m.wudf : m.udf; + if(!fmap) continue; + const func = i + ? capi.sqlite3_create_window_function + : capi.sqlite3_create_function_v2; + for(const e of fmap){ + const name = e[0], arities = e[1]; + const fargs = [pDb, name, 0/*arity*/, capi.SQLITE_UTF8, 0, 0, 0, 0, 0]; + if(i) fargs.push(0); + for(const arity of arities){ + try{ fargs[2] = arity; func.apply(null, fargs); } + catch(e){/*ignored*/} + } + arities.clear(); + } + fmap.clear(); + } + delete m.udf; + delete m.wudf; + }/*__dbCleanupMap.cleanup()*/; + + {/* Binding of sqlite3_close_v2() */ + const __sqlite3CloseV2 = wasm.xWrap("sqlite3_close_v2", "int", "sqlite3*"); + capi.sqlite3_close_v2 = function(pDb){ + if(1!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_close_v2', 1); + if(pDb){ + try{__dbCleanupMap.cleanup(pDb)} catch(e){/*ignored*/} + } + return __sqlite3CloseV2(pDb); }; - const __ccv2 = wasm.xWrap( - 'sqlite3_create_collation_v2', 'int', - 'sqlite3*','string','int','*', - new wasm.xWrap.FuncPtrAdapter({ - /* int(*xCompare)(void*,int,const void*,int,const void*) */ - name: 'sqlite3_create_collation_v2::xCompare', - signature: 'i(pipip)', - bindScope: 'context', - contextKey: __collationContextKey - }), - new wasm.xWrap.FuncPtrAdapter({ - /* void(*xDestroy(void*) */ - name: 'sqlite3_create_collation_v2::xDestroy', - signature: 'v(p)', - bindScope: 'context', - contextKey: __collationContextKey - }) + }/*sqlite3_close_v2()*/ + + if(capi.sqlite3session_table_filter){ + const __sqlite3SessionDelete = wasm.xWrap( + 'sqlite3session_delete', undefined, ['sqlite3_session*'] + ); + capi.sqlite3session_delete = function(pSession){ + if(1!==arguments.length){ + return __dbArgcMismatch(pDb, 'sqlite3session_delete', 1); + /* Yes, we're returning a value from a void function. That seems + like the lesser evil compared to not maintaining arg-count + consistency as we do with other similar bindings. */ + } + else if(pSession){ + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; + capi.sqlite3session_table_filter(pSession, 0, 0); + } + __sqlite3SessionDelete(pSession); + }; + } + + {/* Bindings for sqlite3_create_collation[_v2]() */ + // contextKey() impl for wasm.xWrap.FuncPtrAdapter + const contextKey = (argv,argIndex)=>{ + return 'argv['+argIndex+']:'+argv[0/* sqlite3* */]+ + ':'+wasm.cstrToJs(argv[1/* collation name */]).toLowerCase() + }; + const __sqlite3CreateCollationV2 = wasm.xWrap( + 'sqlite3_create_collation_v2', 'int', [ + 'sqlite3*', 'string', 'int', '*', + new wasm.xWrap.FuncPtrAdapter({ + /* int(*xCompare)(void*,int,const void*,int,const void*) */ + name: 'xCompare', signature: 'i(pipip)', contextKey + }), + new wasm.xWrap.FuncPtrAdapter({ + /* void(*xDestroy(void*) */ + name: 'xDestroy', signature: 'v(p)', contextKey + }) + ] ); /** @@ -552,13 +1101,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ return __errEncoding(pDb); } - let rc, pfCompare, pfDestroy; - try{ - rc = __ccv2(pDb, zName, eTextRep, pArg, xCompare, xDestroy); + try{ + const rc = __sqlite3CreateCollationV2(pDb, zName, eTextRep, pArg, xCompare, xDestroy); + if(0===rc && xCompare instanceof Function){ + __dbCleanupMap.addCollation(pDb, zName); + } + return rc; }catch(e){ - rc = util.sqlite3_wasm_db_error(pDb, e); + return util.sqlite3_wasm_db_error(pDb, e); } - return rc; }; capi.sqlite3_create_collation = (pDb,zName,eTextRep,pArg,xCompare)=>{ @@ -569,130 +1120,92 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/*sqlite3_create_collation() and friends*/ - if(1){/* Special-case handling of sqlite3_exec() */ - const __exec = wasm.xWrap("sqlite3_exec", "int", - ["sqlite3*", "string:flexible", - new wasm.xWrap.FuncPtrAdapter({ - signature: 'i(pipp)', - bindScope: 'transient' - }), "*", "**"]); - /* Documented in the api object's initializer. */ - capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){ - if(f.length!==arguments.length){ - return __dbArgcMismatch(pDb,"sqlite3_exec",f.length); - }else if(!(callback instanceof Function)){ - return __exec(pDb, sql, callback, pVoid, pErrMsg); - } - /* Wrap the callback in a WASM-bound function and convert the callback's - `(char**)` arguments to arrays of strings... */ - const cbwrap = function(pVoid, nCols, pColVals, pColNames){ - let rc = capi.SQLITE_ERROR; - try { - let aVals = [], aNames = [], i = 0, offset = 0; - for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){ - aVals.push( wasm.cstrToJs(wasm.peekPtr(pColVals + offset)) ); - aNames.push( wasm.cstrToJs(wasm.peekPtr(pColNames + offset)) ); - } - rc = callback(pVoid, nCols, aVals, aNames) | 0; - /* The first 2 args of the callback are useless for JS but - we want the JS mapping of the C API to be as close to the - C API as possible. */ - }catch(e){ - /* If we set the db error state here, the higher-level exec() call - replaces it with its own, so we have no way of reporting the - exception message except the console. We must not propagate - exceptions through the C API. */ - } - return rc; - }; - let rc; - try{ - rc = __exec(pDb, sql, cbwrap, pVoid, pErrMsg); - }catch(e){ - rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, - "Error running exec(): "+e); - } - return rc; + {/* Special-case handling of sqlite3_create_function_v2() + and sqlite3_create_window_function(). */ + /** FuncPtrAdapter for contextKey() for sqlite3_create_function() + and friends. */ + const contextKey = function(argv,argIndex){ + return ( + argv[0/* sqlite3* */] + +':'+(argv[2/*number of UDF args*/] < 0 ? -1 : argv[2]) + +':'+argIndex/*distinct for each xAbc callback type*/ + +':'+wasm.cstrToJs(argv[1]).toLowerCase() + ) }; - }/*sqlite3_exec() proxy*/; - - if(1){/* Special-case handling of sqlite3_create_function_v2() - and sqlite3_create_window_function() */ - /* Maintenance reminder: FuncPtrAdapter is not expressive enough - to be able to perform these mappings. */ - const sqlite3CreateFunction = wasm.xWrap( - "sqlite3_create_function_v2", "int", - ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, - "int"/*eTextRep*/, "*"/*pApp*/, - "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, "*"/*xDestroy*/] - ); - - const sqlite3CreateWindowFunction = wasm.xWrap( - "sqlite3_create_window_function", "int", - ["sqlite3*", "string"/*funcName*/, "int"/*nArg*/, - "int"/*eTextRep*/, "*"/*pApp*/, - "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, - "*"/*xInverse*/, "*"/*xDestroy*/] - ); - const __xFunc = function(callback){ - return function(pCtx, argc, pArgv){ - try{ - capi.sqlite3_result_js( - pCtx, - callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) - ); - }catch(e){ - //console.error('xFunc() caught:',e); - capi.sqlite3_result_error_js(pCtx, e); + /** + JS proxies for the various sqlite3_create[_window]_function() + callbacks, structured in a form usable by wasm.xWrap.FuncPtrAdapter. + */ + const __cfProxy = Object.assign(Object.create(null), { + xInverseAndStep: { + signature:'v(pip)', contextKey, + callProxy: (callback)=>{ + return (pCtx, argc, pArgv)=>{ + try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) } + catch(e){ capi.sqlite3_result_error_js(pCtx, e) } + }; } - }; - }; - - const __xInverseAndStep = function(callback){ - return function(pCtx, argc, pArgv){ - try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) } - catch(e){ capi.sqlite3_result_error_js(pCtx, e) } - }; - }; - - const __xFinalAndValue = function(callback){ - return function(pCtx){ - try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) } - catch(e){ capi.sqlite3_result_error_js(pCtx, e) } - }; - }; - - const __xDestroy = function(callback){ - return function(pVoid){ - try{ callback(pVoid) } - catch(e){ console.error("UDF xDestroy method threw:",e) } - }; - }; - - const __xMap = Object.assign(Object.create(null), { - xFunc: {sig:'v(pip)', f:__xFunc}, - xStep: {sig:'v(pip)', f:__xInverseAndStep}, - xInverse: {sig:'v(pip)', f:__xInverseAndStep}, - xFinal: {sig:'v(p)', f:__xFinalAndValue}, - xValue: {sig:'v(p)', f:__xFinalAndValue}, - xDestroy: {sig:'v(p)', f:__xDestroy} - }); - - /* Internal helper for sqlite3_create_function() and friends. */ - const __xWrapFuncs = function(theKeys, theFuncs, tgtUninst){ - const rc = [] - for(const k of theKeys){ - let fArg = theFuncs[k]; - if('function'===typeof fArg){ - const w = __xMap[k] || toss3("Internal error in __xWrapFuncs: invalid key:",k); - fArg = wasm.installFunction(w.sig, w.f(fArg)); - tgtUninst.push(fArg); + }, + xFinalAndValue: { + signature:'v(p)', contextKey, + callProxy: (callback)=>{ + return (pCtx)=>{ + try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) } + catch(e){ capi.sqlite3_result_error_js(pCtx, e) } + }; + } + }, + xFunc: { + signature:'v(pip)', contextKey, + callProxy: (callback)=>{ + return (pCtx, argc, pArgv)=>{ + try{ + capi.sqlite3_result_js( + pCtx, + callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) + ); + }catch(e){ + //console.error('xFunc() caught:',e); + capi.sqlite3_result_error_js(pCtx, e); + } + }; + } + }, + xDestroy: { + signature:'v(p)', contextKey, + //Arguable: a well-behaved destructor doesn't require a proxy. + callProxy: (callback)=>{ + return (pVoid)=>{ + try{ callback(pVoid) } + catch(e){ console.error("UDF xDestroy method threw:",e) } + }; } - rc.push(fArg); } - return rc; - }; + })/*__cfProxy*/; + + const __sqlite3CreateFunction = wasm.xWrap( + "sqlite3_create_function_v2", "int", [ + "sqlite3*", "string"/*funcName*/, "int"/*nArg*/, + "int"/*eTextRep*/, "*"/*pApp*/, + new wasm.xWrap.FuncPtrAdapter({name: 'xFunc', ...__cfProxy.xFunc}), + new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}), + new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}), + new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy}) + ] + ); + + const __sqlite3CreateWindowFunction = wasm.xWrap( + "sqlite3_create_window_function", "int", [ + "sqlite3*", "string"/*funcName*/, "int"/*nArg*/, + "int"/*eTextRep*/, "*"/*pApp*/, + new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}), + new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}), + new wasm.xWrap.FuncPtrAdapter({name: 'xValue', ...__cfProxy.xFinalAndValue}), + new wasm.xWrap.FuncPtrAdapter({name: 'xInverse', ...__cfProxy.xInverseAndStep}), + new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy}) + ] + ); /* Documented in the api object's initializer. */ capi.sqlite3_create_function_v2 = function f( @@ -709,26 +1222,23 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ return __errEncoding(pDb); } - /* Wrap the callbacks in a WASM-bound functions... */ - const uninstall = [/*funcs to uninstall on error*/]; - let rc; try{ - const funcArgs = __xWrapFuncs(['xFunc','xStep','xFinal','xDestroy'], - {xFunc, xStep, xFinal, xDestroy}, - uninstall); - rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, - pApp, ...funcArgs); + const rc = __sqlite3CreateFunction(pDb, funcName, nArg, eTextRep, + pApp, xFunc, xStep, xFinal, xDestroy); + if(0===rc && (xFunc instanceof Function + || xStep instanceof Function + || xFinal instanceof Function + || xDestroy instanceof Function)){ + __dbCleanupMap.addFunction(pDb, funcName, nArg); + } + return rc; }catch(e){ console.error("sqlite3_create_function_v2() setup threw:",e); - for(let v of uninstall){ - wasm.uninstallFunction(v); - } - rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, - "Creation of UDF threw: "+e.message); + return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } - return rc; }; + /* Documented in the api object's initializer. */ capi.sqlite3_create_function = function f( pDb, funcName, nArg, eTextRep, pApp, xFunc, xStep, xFinal @@ -744,8 +1254,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ pDb, funcName, nArg, eTextRep, pApp, xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**) xFinal, //void (*xFinal)(sqlite3_context*) - xValue, //void (*xFinal)(sqlite3_context*) - xInverse,//void (*xStep)(sqlite3_context*,int,sqlite3_value**) + xValue, //void (*xValue)(sqlite3_context*) + xInverse,//void (*xInverse)(sqlite3_context*,int,sqlite3_value**) xDestroy //void (*xDestroy)(void*) ){ if( f.length!==arguments.length ){ @@ -755,24 +1265,22 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){ return __errEncoding(pDb); } - /* Wrap the callbacks in a WASM-bound functions... */ - const uninstall = [/*funcs to uninstall on error*/]; - let rc; try{ - const funcArgs = __xWrapFuncs(['xStep','xFinal','xValue','xInverse','xDestroy'], - {xStep, xFinal, xValue, xInverse, xDestroy}, - uninstall); - rc = sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep, - pApp, ...funcArgs); + const rc = __sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep, + pApp, xStep, xFinal, xValue, + xInverse, xDestroy); + if(0===rc && (xStep instanceof Function + || xFinal instanceof Function + || xValue instanceof Function + || xInverse instanceof Function + || xDestroy instanceof Function)){ + __dbCleanupMap.addWindowFunc(pDb, funcName, nArg); + } + return rc; }catch(e){ console.error("sqlite3_create_window_function() setup threw:",e); - for(let v of uninstall){ - wasm.uninstallFunction(v); - } - rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR, - "Creation of UDF threw: "+e.message); + return util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e); } - return rc; }; /** A _deprecated_ alias for capi.sqlite3_result_js() which @@ -800,22 +1308,24 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/; - if(1){/* Special-case handling of sqlite3_prepare_v2() and - sqlite3_prepare_v3() */ + {/* Special-case handling of sqlite3_prepare_v2() and + sqlite3_prepare_v3() */ /** Helper for string:flexible conversions which require a byte-length counterpart argument. Passed a value and its - ostensible length, this function returns [V,N], where V - is either v or a transformed copy of v and N is either n, - -1, or the byte length of v (if it's a byte array). + ostensible length, this function returns [V,N], where V is + either v or a transformed copy of v and N is either n, -1, or + the byte length of v (if it's a byte array or ArrayBuffer). */ const __flexiString = (v,n)=>{ if('string'===typeof v){ n = -1; }else if(util.isSQLableTypedArray(v)){ n = v.byteLength; - v = util.typedArrayToString(v); + v = util.typedArrayToString( + (v instanceof ArrayBuffer) ? new Uint8Array(v) : v + ); }else if(Array.isArray(v)){ v = v.join(""); n = -1; @@ -958,37 +1468,6 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/*sqlite3_bind_text/blob()*/ - {/* sqlite3_set_authorizer() */ - const __ssa = wasm.xWrap("sqlite3_set_authorizer", 'int', [ - "sqlite3*", - new wasm.xWrap.FuncPtrAdapter({ - name: "sqlite3_set_authorizer::xAuth", - signature: "i(pi"+"ssss)", - contextKey: (argIndex, argv)=>argv[0/*(sqlite3*)*/] - }), - "*" - ]); - capi.sqlite3_set_authorizer = function(pDb, xAuth, pUserData){ - if(3!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_set_authorizer', 3); - if(xAuth instanceof Function){ - const xProxy = xAuth; - /* Create a proxy which will receive the C-strings from WASM - and convert them to JS strings for the client-supplied - function. */ - xAuth = function(pV, iCode, s0, s1, s2, s3){ - try{ - s0 = s0 && wasm.cstrToJs(s0); s1 = s1 && wasm.cstrToJs(s1); - s2 = s2 && wasm.cstrToJs(s2); s3 = s3 && wasm.cstrToJs(s3); - return xProxy(pV, iCode, s0, s1, s2, s3) || 0; - }catch(e){ - return util.sqlite3_wasm_db_error(pDb, e); - } - }; - } - return __ssa(pDb, xAuth, pUserData); - }; - }/* sqlite3_set_authorizer() */ - {/* sqlite3_config() */ /** Wraps a small subset of the C API's sqlite3_config() options. @@ -1035,79 +1514,41 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; }/* sqlite3_config() */ - {/* Import C-level constants and structs... */ - const cJson = wasm.xCall('sqlite3_wasm_enum_json'); - if(!cJson){ - toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", - "static buffer size!"); - } - wasm.ctype = JSON.parse(wasm.cstrToJs(cJson)); - //console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); - const defineGroups = ['access', 'authorizer', - 'blobFinalizers', 'config', 'dataTypes', - 'dbConfig', 'dbStatus', - 'encodings', 'fcntl', 'flock', 'ioCap', - 'limits', 'openFlags', - 'prepareFlags', 'resultCodes', - 'serialize', 'sqlite3Status', - 'stmtStatus', 'syncFlags', - 'trace', 'txnState', 'udfFlags', - 'version' ]; - if(wasm.bigIntEnabled){ - defineGroups.push('vtab'); - } - for(const t of defineGroups){ - for(const e of Object.entries(wasm.ctype[t])){ - // ^^^ [k,v] there triggers a buggy code transformation via - // one of the Emscripten-driven optimizers. - capi[e[0]] = e[1]; - } - } - const __rcMap = Object.create(null); - for(const t of ['resultCodes']){ - for(const e of Object.entries(wasm.ctype[t])){ - __rcMap[e[1]] = e[0]; - } - } - /** - For the given integer, returns the SQLITE_xxx result code as a - string, or undefined if no such mapping is found. - */ - capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; - /* Bind all registered C-side structs... */ - const notThese = Object.assign(Object.create(null),{ - // For each struct to NOT register, map its name to true: - WasmTestStruct: true, - /* We unregister the kvvfs VFS from Worker threads below. */ - sqlite3_kvvfs_methods: !util.isUIThread(), - /* sqlite3_index_info and friends require int64: */ - sqlite3_index_info: !wasm.bigIntEnabled, - sqlite3_index_constraint: !wasm.bigIntEnabled, - sqlite3_index_orderby: !wasm.bigIntEnabled, - sqlite3_index_constraint_usage: !wasm.bigIntEnabled - }); - for(const s of wasm.ctype.structs){ - if(!notThese[s.name]){ - capi[s.name] = sqlite3.StructBinder(s); + {/*auto-extension bindings.*/ + const __autoExtFptr = new Set; + + capi.sqlite3_auto_extension = function(fPtr){ + if( fPtr instanceof Function ){ + fPtr = wasm.installFunction('i(ppp)', fPtr); + }else if( 1!==arguments.length || !wasm.isPtr(fPtr) ){ + return capi.SQLITE_MISUSE; } - } - if(capi.sqlite3_index_info){ - /* Move these inner structs into sqlite3_index_info. Binding - ** them to WASM requires that we create global-scope structs to - ** model them with, but those are no longer needed after we've - ** passed them to StructBinder. */ - for(const k of ['sqlite3_index_constraint', - 'sqlite3_index_orderby', - 'sqlite3_index_constraint_usage']){ - capi.sqlite3_index_info[k] = capi[k]; - delete capi[k]; + const rc = wasm.exports.sqlite3_auto_extension(fPtr); + if( fPtr!==arguments[0] ){ + if(0===rc) __autoExtFptr.add(fPtr); + else wasm.uninstallFunction(fPtr); } - capi.sqlite3_vtab_config = wasm.xWrap( - 'sqlite3_wasm_vtab_config','int',[ - 'sqlite3*', 'int', 'int'] - ); - }/* end vtab-related setup */ - }/*end C constant and struct imports*/ + return rc; + }; + + capi.sqlite3_cancel_auto_extension = function(fPtr){ + /* We do not do an automatic JS-to-WASM function conversion here + because it would be senseless: the converted pointer would + never possibly match an already-installed one. */; + if(!fPtr || 1!==arguments.length || !wasm.isPtr(fPtr)) return 0; + return wasm.exports.sqlite3_cancel_auto_extension(fPtr); + /* Note that it "cannot happen" that a client passes a pointer which + is in __autoExtFptr because __autoExtFptr only contains automatic + conversions created inside sqlite3_auto_extension() and + never exposed to the client. */ + }; + + capi.sqlite3_reset_auto_extension = function(){ + wasm.exports.sqlite3_reset_auto_extension(); + for(const fp of __autoExtFptr) wasm.uninstallFunction(fp); + __autoExtFptr.clear(); + }; + }/* auto-extension */ const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); if( pKvvfs ){/* kvvfs-specific glue */ @@ -1206,4 +1647,5 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } }/*pKvvfs*/ + wasm.xWrap.FuncPtrAdapter.warnOnUse = true; }); diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index 16f5f00b1..ba210e7f9 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -72,7 +72,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ wasm.installFunction('i(ippp)', function(t,c,p,x){ if(capi.SQLITE_TRACE_STMT===t){ // x == SQL, p == sqlite3_stmt* - console.log("SQL TRACE #"+(++this.counter), + console.log("SQL TRACE #"+(++this.counter)+' via sqlite3@'+c+':', wasm.cstrToJs(x)); } }.bind({counter: 0})); @@ -161,7 +161,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ capi.sqlite3_extended_result_codes(pDb, 1); if(flagsStr.indexOf('t')>=0){ capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT, - __dbTraceToConsole, 0); + __dbTraceToConsole, pDb); } }catch( e ){ if( pDb ) capi.sqlite3_close_v2(pDb); @@ -463,23 +463,21 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; /** - Internal impl of the DB.selectArray() and + Internal impl of the DB.selectValue(), selectArray(), and selectObject() methods. */ - const __selectFirstRow = (db, sql, bind, getArg)=>{ - let stmt, rc; + const __selectFirstRow = (db, sql, bind, ...getArgs)=>{ + const stmt = db.prepare(sql); try { - stmt = db.prepare(sql).bind(bind); - if(stmt.step()) rc = stmt.get(getArg); + return stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined; }finally{ - if(stmt) stmt.finalize(); + stmt.finalize(); } - return rc; }; /** - Internal impl of the DB.selectArrays() and - selectObjects() methods. + Internal impl of the DB.selectArrays() and selectObjects() + methods. */ const __selectAll = (db, sql, bind, rowMode)=>db.exec({ @@ -1083,15 +1081,31 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Throws on error (e.g. malformed SQL). */ selectValue: function(sql,bind,asType){ - let stmt, rc; + return __selectFirstRow(this, sql, bind, 0, asType); + }, + + /** + Runs the given query and returns an array of the values from + the first result column of each row of the result set. The 2nd + argument is an optional value for use in a single-argument call + to Stmt.bind(). The 3rd argument may be any value suitable for + use as the 2nd argument to Stmt.get(). If a 3rd argument is + desired but no bind data are needed, pass `undefined` for the 2nd + argument. + + If there are no result rows, an empty array is returned. + */ + selectValues: function(sql,bind,asType){ + const stmt = this.prepare(sql), rc = []; try { - stmt = this.prepare(sql).bind(bind); - if(stmt.step()) rc = stmt.get(0,asType); + stmt.bind(bind); + while(stmt.step()) rc.push(stmt.get(0,asType)); }finally{ - if(stmt) stmt.finalize(); + stmt.finalize(); } return rc; }, + /** Prepares the given SQL, step()s it one time, and returns an array containing the values of the first result row. If it has @@ -1147,7 +1161,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ /** Returns the number of currently-opened Stmt handles for this db - handle, or 0 if this DB instance is closed. + handle, or 0 if this DB instance is closed. Note that only + handles prepared via this.prepare() are counted, and not + handles prepared using capi.sqlite3_prepare_v3() (or + equivalent). */ openStatementCount: function(){ return this.pointer ? Object.keys(__stmtMap.get(this)).length : 0; @@ -1163,9 +1180,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Note that transactions may not be nested, so this will throw if it is called recursively. For nested transactions, use the savepoint() method or manually manage SAVEPOINTs using exec(). + + If called with 2 arguments, the first must be a keyword which + is legal immediately after a BEGIN statement, e.g. one of + "DEFERRED", "IMMEDIATE", or "EXCLUSIVE". Though the exact list + of supported keywords is not hard-coded here, in order to be + future-compatible, if the argument does not look like a single + keyword then an exception is triggered with a description of + the problem. */ - transaction: function(callback){ - affirmDbOpen(this).exec("BEGIN"); + transaction: function(/* [beginQualifier,] */callback){ + let opener = 'BEGIN'; + if(arguments.length>1){ + if(/[^a-zA-Z]/.test(arguments[0])){ + toss3(capi.SQLITE_MISUSE, "Invalid argument for BEGIN qualifier."); + } + opener += ' '+arguments[0]; + callback = arguments[1]; + } + affirmDbOpen(this).exec(opener); try { const rc = callback(this); this.exec("COMMIT"); @@ -1229,9 +1262,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if(wasm.bigIntEnabled) return t; /* else fall through */ default: - //console.log("isSupportedBindType",t,v); - return (util.isBindableTypedArray(v) || (v instanceof ArrayBuffer)) - ? BindTypes.blob : undefined; + return util.isBindableTypedArray(v) ? BindTypes.blob : undefined; } }; @@ -1449,7 +1480,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ blob binding). - Uint8Array, Int8Array, and ArrayBuffer instances are bound as - blobs. (TODO? binding the other TypedArray types.) + blobs. If passed an array, each element of the array is bound at the parameter index equal to the array index plus 1 @@ -1611,7 +1642,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }, /** Fetches the value from the given 0-based column index of - the current data row, throwing if index is out of range. + the current data row, throwing if index is out of range. Requires that step() has just returned a truthy value, else an exception is thrown. diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js index faa2dbbbf..5dd37d303 100644 --- a/ext/wasm/api/sqlite3-api-prologue.js +++ b/ext/wasm/api/sqlite3-api-prologue.js @@ -139,7 +139,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( main thread (aborts via a failed assert() if it's attempted), which eliminates any(?) benefit to supporting it. */ false; - /** + /** The main sqlite3 binding API gets installed into this object, mimicking the C API as closely as we can. The numerous members names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as @@ -321,7 +321,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }; /** - Returns v if v appears to be one of our bind()-able TypedArray + Returns true if v appears to be one of our bind()-able TypedArray types: Uint8Array or Int8Array or ArrayBuffer. Support for TypedArrays with element sizes >1 is a potential TODO just waiting on a use case to justify them. Until then, their `buffer` @@ -377,7 +377,11 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( returned. Else v is returned as-is. */ const flexibleString = function(v){ - if(isSQLableTypedArray(v)) return typedArrayToString(v); + if(isSQLableTypedArray(v)){ + return typedArrayToString( + (v instanceof ArrayBuffer) ? new Uint8Array(v) : v + ); + } else if(Array.isArray(v)) return v.join(""); else if(wasm.isPtr(v)) v = wasm.cstrToJs(v); return v; @@ -405,6 +409,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( }else{ super("Allocation failed."); } + this.resultCode = capi.SQLITE_NOMEM; this.name = 'WasmAllocError'; } }; @@ -657,8 +662,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( terminated with a 0 byte. In usage (1), the 2nd argument must be of type string, - Uint8Array, or Int8Array (either of which is assumed to - hold SQL). If it is, this function assumes case (1) and + Uint8Array, Int8Array, or ArrayBuffer (all of which are assumed + to hold SQL). If it is, this function assumes case (1) and calls the underyling C function with the equivalent of: (pDb, sqlAsString, -1, prepFlags, ppStmt, null) @@ -750,7 +755,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( affirmBindableTypedArray, flexibleString, bigIntFits32, bigIntFits64, bigIntFitsDouble, isBindableTypedArray, - isInt32, isSQLableTypedArray, isTypedArray, + isInt32, isSQLableTypedArray, isTypedArray, typedArrayToString, isUIThread: ()=>(self.window===self && !!self.document), // is this true for ESM?: 'undefined'===typeof WorkerGlobalScope @@ -948,7 +953,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( const m = f._rx.exec(opt); rv[0] = (m ? m[1] : opt); rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true; - }; + }; } const rc = {}, ov = [0,0]; let i = 0, k; @@ -1019,12 +1024,20 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( to the memory. On error, returns throws a WasmAllocError. The memory must eventually be released using restore(). + If n is a string, it must be a WASM "IR" value in the set + accepted by wasm.sizeofIR(), which is mapped to the size of + that data type. If passed a string not in that set, it throws a + WasmAllocError. + This method always adjusts the given value to be a multiple of 8 bytes because failing to do so can lead to incorrect results when reading and writing 64-bit values from/to the WASM heap. Similarly, the returned address is always 8-byte aligned. */ - alloc: (n)=>{ + alloc: function(n){ + if('string'===typeof n && !(n = wasm.sizeofIR(n))){ + WasmAllocError.toss("Invalid value for pstack.alloc(",arguments[0],")"); + } return wasm.exports.sqlite3_wasm_pstack_alloc(n) || WasmAllocError.toss("Could not allocate",n, "bytes from the pstack."); @@ -1034,6 +1047,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( returns the addresses as an array of n element, each holding the address of one chunk. + sz may optionally be an IR string accepted by wasm.sizeofIR(). + Throws a WasmAllocError if allocation fails. Example: @@ -1042,7 +1057,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( const [p1, p2, p3] = wasm.pstack.allocChunks(3,4); ``` */ - allocChunks: (n,sz)=>{ + allocChunks: function(n,sz){ + if('string'===typeof sz && !(sz = wasm.sizeofIR(sz))){ + WasmAllocError.toss("Invalid size value for allocChunks(",arguments[1],")"); + } const mem = wasm.pstack.alloc(n * sz); const rc = []; let i = 0, offset = 0; @@ -1418,7 +1436,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( wasm.dealloc(pData); } }; - + if( util.isUIThread() ){ /* Features specific to the main window thread... */ @@ -1749,8 +1767,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( argument of sqlite3_value_to_js(). If the sqlite3_column_value() returns NULL (e.g. because the column index is out of range), this function returns `undefined`, regardless of the 3rd - argument. 3rd argument is falsy and conversion fails, `undefined` - will be returned. + argument. If the 3rd argument is falsy and conversion fails, + `undefined` will be returned. Note that sqlite3_column_value() returns an "unprotected" value object, but in a single-threaded environment (like this one) @@ -1761,6 +1779,56 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap( return (0===v) ? undefined : capi.sqlite3_value_to_js(v, throwIfCannotConvert); }; + /** + Internal impl of sqlite3_preupdate_new/old_js() and + sqlite3changeset_new/old_js(). + */ + const __newOldValue = function(pObj, iCol, impl){ + impl = capi[impl]; + if(!this.ptr) this.ptr = wasm.allocPtr(); + else wasm.pokePtr(this.ptr, 0); + const rc = impl(pObj, iCol, this.ptr); + if(rc) return SQLite3Error.toss(rc,arguments[2]+"() failed with code "+rc); + const pv = wasm.peekPtr(this.ptr); + return pv ? capi.sqlite3_value_to_js( pv, true ) : undefined; + }.bind(Object.create(null)); + + /** + A wrapper around sqlite3_preupdate_new() which fetches the + sqlite3_value at the given index and returns the result of + passing it to sqlite3_value_to_js(). Throws on error. + */ + capi.sqlite3_preupdate_new_js = + (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_new'); + + /** + The sqlite3_preupdate_old() counterpart of + sqlite3_preupdate_new_js(), with an identical interface. + */ + capi.sqlite3_preupdate_old_js = + (pDb, iCol)=>__newOldValue(pDb, iCol, 'sqlite3_preupdate_old'); + + /** + A wrapper around sqlite3changeset_new() which fetches the + sqlite3_value at the given index and returns the result of + passing it to sqlite3_value_to_js(). Throws on error. + + If sqlite3changeset_new() succeeds but has no value to report, + this function returns the undefined value, noting that undefined + is a valid conversion from an `sqlite3_value`, so is unambiguous. + */ + capi.sqlite3changeset_new_js = + (pChangesetIter, iCol) => __newOldValue(pChangesetIter, iCol, + 'sqlite3changeset_new'); + + /** + The sqlite3changeset_old() counterpart of + sqlite3changeset_new_js(), with an identical interface. + */ + capi.sqlite3changeset_old_js = + (pChangesetIter, iCol)=>__newOldValue(pChangesetIter, iCol, + 'sqlite3changeset_old'); + /* The remainder of the API will be set up in later steps. */ const sqlite3 = { WasmAllocError: WasmAllocError, diff --git a/ext/wasm/api/sqlite3-api-worker1.js b/ext/wasm/api/sqlite3-api-worker1.js index 62e2bb9bd..250b6fcdf 100644 --- a/ext/wasm/api/sqlite3-api-worker1.js +++ b/ext/wasm/api/sqlite3-api-worker1.js @@ -158,15 +158,6 @@ bigIntEnabled: bool. True if BigInt support is enabled. - wasmfsOpfsDir: path prefix, if any, _intended_ for use with - WASMFS OPFS persistent storage. - - wasmfsOpfsEnabled: true if persistent storage is enabled in the - current environment. Only files stored under wasmfsOpfsDir - will persist using that mechanism, however. It is legal to use - the non-WASMFS OPFS VFS to open a database via a URI-style - db filename. - vfsList: result of sqlite3.capi.sqlite3_js_vfs_list() } } @@ -449,7 +440,6 @@ sqlite3.initWorker1API = function(){ toss("Throwing because of simulateError flag."); } const rc = Object.create(null); - const pDir = sqlite3.capi.sqlite3_wasmfs_opfs_dir(); let byteArray, pVfs; oargs.vfs = args.vfs; if(isSpecialDbFilename(args.filename)){ @@ -475,15 +465,14 @@ sqlite3.initWorker1API = function(){ e.name+' creating '+args.filename+": "+e.message, { cause: e } - ); + ); }finally{ if(pMem) sqlite3.wasm.dealloc(pMem); } } const db = wState.open(oargs); rc.filename = db.filename; - rc.persistent = (!!pDir && db.filename.startsWith(pDir+'/')) - || !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs"); + rc.persistent = !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs"); rc.dbId = getDbId(db); rc.vfs = db.dbVfsName(); return rc; @@ -558,11 +547,10 @@ sqlite3.initWorker1API = function(){ 'config-get': function(){ const rc = Object.create(null), src = sqlite3.config; [ - 'wasmfsOpfsDir', 'bigIntEnabled' + 'bigIntEnabled' ].forEach(function(k){ if(Object.getOwnPropertyDescriptor(src, k)) rc[k] = src[k]; }); - rc.wasmfsOpfsEnabled = !!sqlite3.capi.sqlite3_wasmfs_opfs_dir(); rc.version = sqlite3.version; rc.vfsList = sqlite3.capi.sqlite3_js_vfs_list(); rc.opfsEnabled = !!sqlite3.opfs; diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c index f2bf5fef1..e7513509c 100644 --- a/ext/wasm/api/sqlite3-wasm.c +++ b/ext/wasm/api/sqlite3-wasm.c @@ -99,15 +99,21 @@ #ifndef SQLITE_ENABLE_FTS4 # define SQLITE_ENABLE_FTS4 1 #endif +#ifndef SQLITE_ENABLE_MATH_FUNCTIONS +# define SQLITE_ENABLE_MATH_FUNCTIONS 1 +#endif #ifndef SQLITE_ENABLE_OFFSET_SQL_FUNC # define SQLITE_ENABLE_OFFSET_SQL_FUNC 1 #endif -#ifndef SQLITE_ENABLE_MATH_FUNCTIONS -# define SQLITE_ENABLE_MATH_FUNCTIONS 1 +#ifndef SQLITE_ENABLE_PREUPDATE_HOOK +# define SQLITE_ENABLE_PREUPDATE_HOOK 1 /*required by session extension*/ #endif #ifndef SQLITE_ENABLE_RTREE # define SQLITE_ENABLE_RTREE 1 #endif +#ifndef SQLITE_ENABLE_SESSION +# define SQLITE_ENABLE_SESSION 1 +#endif #ifndef SQLITE_ENABLE_STMTVTAB # define SQLITE_ENABLE_STMTVTAB 1 #endif @@ -323,11 +329,13 @@ SQLITE_WASM_KEEP int sqlite3_wasm_pstack_quota(void){ */ SQLITE_WASM_KEEP int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){ - if( 0!=zMsg ){ - const int nMsg = sqlite3Strlen30(zMsg); - sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); - }else{ - sqlite3ErrorWithMsg(db, err_code, NULL); + if( db!=0 ){ + if( 0!=zMsg ){ + const int nMsg = sqlite3Strlen30(zMsg); + sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg); + }else{ + sqlite3ErrorWithMsg(db, err_code, NULL); + } } return err_code; } @@ -456,6 +464,24 @@ const char * sqlite3_wasm_enum_json(void){ /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as ** integers to avoid casting-related warnings. */ out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1"); + outf(",\"SQLITE_WASM_DEALLOC\": %lld", + (sqlite3_int64)(sqlite3_free)); + } _DefGroup; + + DefGroup(changeset){ + DefInt(SQLITE_CHANGESETSTART_INVERT); + DefInt(SQLITE_CHANGESETAPPLY_NOSAVEPOINT); + DefInt(SQLITE_CHANGESETAPPLY_INVERT); + + DefInt(SQLITE_CHANGESET_DATA); + DefInt(SQLITE_CHANGESET_NOTFOUND); + DefInt(SQLITE_CHANGESET_CONFLICT); + DefInt(SQLITE_CHANGESET_CONSTRAINT); + DefInt(SQLITE_CHANGESET_FOREIGN_KEY); + + DefInt(SQLITE_CHANGESET_OMIT); + DefInt(SQLITE_CHANGESET_REPLACE); + DefInt(SQLITE_CHANGESET_ABORT); } _DefGroup; DefGroup(config){ @@ -794,6 +820,11 @@ const char * sqlite3_wasm_enum_json(void){ DefInt(SQLITE_DESERIALIZE_RESIZEABLE); } _DefGroup; + DefGroup(session){ + DefInt(SQLITE_SESSION_CONFIG_STRMSIZE); + DefInt(SQLITE_SESSION_OBJCONFIG_SIZE); + } _DefGroup; + DefGroup(sqlite3Status){ DefInt(SQLITE_STATUS_MEMORY_USED); DefInt(SQLITE_STATUS_PAGECACHE_USED); @@ -1562,6 +1593,36 @@ int sqlite3_wasm_config_j(int op, sqlite3_int64 arg){ return sqlite3_config(op, arg); } +#if 0 +// Pending removal after verification of a workaround discussed in the +// forum post linked to below. +/* +** This function is NOT part of the sqlite3 public API. It is strictly +** for use by the sqlite project's own JS/WASM bindings. +** +** Returns a pointer to sqlite3_free(). In compliant browsers the +** return value, when passed to sqlite3.wasm.exports.functionEntry(), +** must resolve to the same function as +** sqlite3.wasm.exports.sqlite3_free. i.e. from a dev console where +** sqlite3 is exported globally, the following must be true: +** +** ``` +** sqlite3.wasm.functionEntry( +** sqlite3.wasm.exports.sqlite3_wasm_ptr_to_sqlite3_free() +** ) === sqlite3.wasm.exports.sqlite3_free +** ``` +** +** Using a function to return this pointer, as opposed to exporting it +** via sqlite3_wasm_enum_json(), is an attempt to work around a +** Safari-specific quirk covered at +** https://sqlite.org/forum/info/e5b20e1feb37a19a. +**/ +SQLITE_WASM_KEEP +void * sqlite3_wasm_ptr_to_sqlite3_free(void){ + return (void*)sqlite3_free; +} +#endif + #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) #include <emscripten/wasmfs.h> diff --git a/ext/wasm/api/sqlite3-worker1.js b/ext/wasm/api/sqlite3-worker1.js index 942437908..4ff19b888 100644 --- a/ext/wasm/api/sqlite3-worker1.js +++ b/ext/wasm/api/sqlite3-worker1.js @@ -41,9 +41,6 @@ //console.warn("worker1 theJs =",theJs); importScripts(theJs); sqlite3InitModule().then((sqlite3)=>{ - if(sqlite3.capi.sqlite3_wasmfs_opfs_dir){ - sqlite3.capi.sqlite3_wasmfs_opfs_dir(); - } sqlite3.initWorker1API(); }); })(); diff --git a/ext/wasm/common/whwasmutil.js b/ext/wasm/common/whwasmutil.js index de7500a02..e50210206 100644 --- a/ext/wasm/common/whwasmutil.js +++ b/ext/wasm/common/whwasmutil.js @@ -247,6 +247,25 @@ self.WhWasmUtilInstaller = function(target){ cache.utf8Encoder = new TextEncoder('utf-8'); /** + For the given IR-like string in the set ('i8', 'i16', 'i32', + 'f32', 'float', 'i64', 'f64', 'double', '*'), or any string value + ending in '*', returns the sizeof for that value + (target.ptrSizeof in the latter case). For any other value, it + returns the undefined value. + */ + target.sizeofIR = (n)=>{ + switch(n){ + case 'i8': return 1; + case 'i16': return 2; + case 'i32': case 'f32': case 'float': return 4; + case 'i64': case 'f64': case 'double': return 8; + case '*': return ptrSizeof; + default: + return (''+n).endsWith('*') ? ptrSizeof : undefined; + } + }; + + /** If (cache.heapSize !== cache.memory.buffer.byteLength), i.e. if the heap has grown since the last call, updates cache.HEAPxyz. Returns the cache object. @@ -447,7 +466,7 @@ self.WhWasmUtilInstaller = function(target){ type(s) of the given function signature, or throws if the signature is invalid. */ /******** // only valid for use with the WebAssembly.Function ctor, which - // is not yet documented on MDN. + // is not yet documented on MDN. sigToWasm: function(sig){ const rc = {parameters:[], results: []}; if('v'!==sig[0]) rc.results.push(f.sigTypes(sig[0])); @@ -1480,7 +1499,7 @@ self.WhWasmUtilInstaller = function(target){ */ const AbstractArgAdapter = class { constructor(opt){ - this.name = opt.name; + this.name = opt.name || 'unnamed adapter'; } /** Gets called via xWrap() to "convert" v to whatever type @@ -1496,7 +1515,7 @@ self.WhWasmUtilInstaller = function(target){ types are indeterminate, whereas the LHS values will be WASM-compatible values by the time this is called. */ - convertArg(v,argIndex,argv){ + convertArg(v,argv,argIndex){ toss("AbstractArgAdapter must be subclassed."); } }; @@ -1541,24 +1560,42 @@ self.WhWasmUtilInstaller = function(target){ context. This mode is the default if bindScope is _not_ set but a property named contextKey (described below) is. + - 'permanent': the function is installed and left there + forever. There is no way to recover its pointer address + later on. + + - callProxy (function): if set, this must be a function which + will act as a proxy for any "converted" JS function. It is + passed the being-converted function value and must return + either that function or a function which acts on its + behalf. The returned function will be the one which gets + installed into the WASM function table. The proxy must perform + any required argument conversion (noting that it will be called + from C code, so will receive C-format arguments) before passing + them on to the being-converted function. Whether or not the + proxy itself must return a value depends on the context. If it + does, it must be a WASM-friendly value, as it will be returning + from a call made from native code. + - contextKey (function): is only used if bindScope is 'context' or if bindScope is not set and this function is, in which case - 'context' is assumed. This function gets passed - (argIndex,argv), where argIndex is the index of _this_ function - pointer in its _wrapping_ function's arguments and argv is the - _current_ still-being-xWrap()-processed args array. All - arguments to the left of argIndex will have been processed by - xWrap() by the time this is called. argv[argIndex] will be the - value the user passed in to the xWrap()'d function for the - argument this FuncPtrAdapter is mapped to. Arguments to the - right of argv[argIndex] will not yet have been converted before - this is called. The function must return a key which uniquely + 'context' is assumed. This function gets bound to this object, + so its "this" is this object. It gets passed (argv,argIndex), + where argIndex is the index of _this_ function pointer in its + _wrapping_ function's arguments and argv is the _current_ + still-being-xWrap()-processed args array. All arguments to the + left of argIndex will have been processed by xWrap() by the + time this is called. argv[argIndex] will be the value the user + passed in to the xWrap()'d function for the argument this + FuncPtrAdapter is mapped to. Arguments to the right of + argv[argIndex] will not yet have been converted before this is + called. The function must return a key which uniquely identifies this function mapping context for _this_ FuncPtrAdapter instance (other instances are not considered), taking into account that C functions often take some sort of state object as one or more of their arguments. As an example, if the xWrap()'d function takes `(int,T*,functionPtr,X*)` and - this FuncPtrAdapter is the argv[2]nd arg, contextKey(2,argv) + this FuncPtrAdapter is the argv[2]nd arg, contextKey(argv,2) might return 'T@'+argv[1], or even just argv[1]. Note, however, that the (X*) argument will not yet have been processed by the time this is called and should not be used as @@ -1570,42 +1607,77 @@ self.WhWasmUtilInstaller = function(target){ use their pointers in the key because most C-strings in this constellation are transient. - Yes, that ^^^ is a bit awkward, but it's what we have. + Yes, that ^^^ is quite awkward, but it's what we have. The constructor only saves the above state for later, and does not actually bind any functions. Its convertArg() method is called via xWrap() to perform any bindings. - Shortcomings: function pointers which include C-string arguments - may still need a level of hand-written wrappers around them, - depending on how they're used, in order to provide the client - with JS strings. + Shortcomings: + + - These "reverse" bindings, i.e. calling into a JS-defined + function from a WASM-defined function (the generated proxy + wrapper), lack all type conversion support. That means, for + example, that... + + - Function pointers which include C-string arguments may still + need a level of hand-written wrappers around them, depending on + how they're used, in order to provide the client with JS + strings. Alternately, clients will need to perform such conversions + on their own, e.g. using cstrtojs(). Or maybe we can find a way + to perform such conversions here, via addition of an xWrap()-style + function signature to the options argument. */ xArg.FuncPtrAdapter = class FuncPtrAdapter extends AbstractArgAdapter { constructor(opt) { super(opt); + if(xArg.FuncPtrAdapter.warnOnUse){ + console.warn('xArg.FuncPtrAdapter is an internal-only API', + 'and is not intended to be invoked from', + 'client-level code. Invoked with:',opt); + } this.signature = opt.signature; - if(!opt.bindScope && (opt.contextKey instanceof Function)){ - opt.bindScope = 'context'; - }else if(FuncPtrAdapter.bindScopes.indexOf(opt.bindScope)<0){ + if(opt.contextKey instanceof Function){ + this.contextKey = opt.contextKey; + if(!opt.bindScope) opt.bindScope = 'context'; + } + this.bindScope = opt.bindScope + || toss("FuncPtrAdapter options requires a bindScope (explicit or implied)."); + if(FuncPtrAdapter.bindScopes.indexOf(opt.bindScope)<0){ toss("Invalid options.bindScope ("+opt.bindMod+") for FuncPtrAdapter. "+ "Expecting one of: ("+FuncPtrAdapter.bindScopes.join(', ')+')'); } - this.bindScope = opt.bindScope; - if(opt.contextKey) this.contextKey = opt.contextKey /*else inherit one*/; this.isTransient = 'transient'===this.bindScope; this.isContext = 'context'===this.bindScope; - if( ('singleton'===this.bindScope) ) this.singleton = []; - else this.singleton = undefined; + this.isPermanent = 'permanent'===this.bindScope; + this.singleton = ('singleton'===this.bindScope) ? [] : undefined; //console.warn("FuncPtrAdapter()",opt,this); + this.callProxy = (opt.callProxy instanceof Function) + ? opt.callProxy : undefined; } + /** If true, the constructor emits a warning. The intent is that + this be set to true after bootstrapping of the higher-level + client library is complete, to warn downstream clients that + they shouldn't be relying on this implemenation detail which + does not have a stable interface. */ + static warnOnUse = false; + + /** If true, convertArg() will FuncPtrAdapter.debugOut() when it + (un)installs a function binding to/from WASM. Note that + deinstallation of bindScope=transient bindings happens + via scopedAllocPop() so will not be output. */ + static debugFuncInstall = false; + + /** Function used for debug output. */ + static debugOut = console.debug.bind(console); + static bindScopes = [ - 'transient', 'context', 'singleton' + 'transient', 'context', 'singleton', 'permanent' ]; /* Dummy impl. Overwritten per-instance as needed. */ - contextKey(argIndex,argv){ + contextKey(argv,argIndex){ return this; } @@ -1638,18 +1710,28 @@ self.WhWasmUtilInstaller = function(target){ See the parent class's convertArg() docs for details on what exactly the 2nd and 3rd arguments are. */ - convertArg(v,argIndex,argv){ - //console.warn("FuncPtrAdapter.convertArg()",this.signature,this.transient,v); + convertArg(v,argv,argIndex){ + //FuncPtrAdapter.debugOut("FuncPtrAdapter.convertArg()",this.signature,this.transient,v); let pair = this.singleton; if(!pair && this.isContext){ - pair = this.contextMap(this.contextKey(argIndex, argv)); + pair = this.contextMap(this.contextKey(argv,argIndex)); } if(pair && pair[0]===v) return pair[1]; if(v instanceof Function){ + /* Install a WASM binding and return its pointer. */ + if(this.callProxy) v = this.callProxy(v); const fp = __installFunction(v, this.signature, this.isTransient); + if(FuncPtrAdapter.debugFuncInstall){ + FuncPtrAdapter.debugOut("FuncPtrAdapter installed", this, + this.contextKey(argv,argIndex), '@'+fp, v); + } if(pair){ /* Replace existing stashed mapping */ if(pair[1]){ + if(FuncPtrAdapter.debugFuncInstall){ + FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, + this.contextKey(argv,argIndex), '@'+pair[1], v); + } try{target.uninstallFunction(pair[1])} catch(e){/*ignored*/} } @@ -1660,10 +1742,13 @@ self.WhWasmUtilInstaller = function(target){ }else if(target.isPtr(v) || null===v || undefined===v){ if(pair && pair[1] && pair[1]!==v){ /* uninstall stashed mapping and replace stashed mapping with v. */ - //console.warn("FuncPtrAdapter is uninstalling function", this.contextKey(argIndex,argv),v); + if(FuncPtrAdapter.debugFuncInstall){ + FuncPtrAdapter.debugOut("FuncPtrAdapter uninstalling", this, + this.contextKey(argv,argIndex), '@'+pair[1], v); + } try{target.uninstallFunction(pair[1])} catch(e){/*ignored*/} - pair[0] = pair[1] = (v || 0); + pair[0] = pair[1] = (v | 0); } return v || 0; }else{ @@ -1893,21 +1978,24 @@ self.WhWasmUtilInstaller = function(target){ const scope = target.scopedAllocPush(); try{ /* - Maintenance reminder re. arguments passed to convertArgs(): + Maintenance reminder re. arguments passed to convertArg(): The public interface of argument adapters is that they take ONE argument and return a (possibly) converted result for it. The passing-on of arguments after the first is an - internal impl. detail for the sake of AbstractArgAdapter, and - not to be relied on or documented for other cases. The fact - that this is how AbstractArgAdapter.convertArgs() gets its 2nd+ - arguments, and how FuncPtrAdapter.contextKey() gets its - args, is also an implementation detail and subject to - change. i.e. the public interface of 1 argument is stable. - The fact that any arguments may be passed in after that one, - and what those arguments are, is _not_ part of the public - interface and is _not_ stable. + internal implementation detail for the sake of + AbstractArgAdapter, and not to be relied on or documented + for other cases. The fact that this is how + AbstractArgAdapter.convertArgs() gets its 2nd+ arguments, + and how FuncPtrAdapter.contextKey() gets its args, is also + an implementation detail and subject to change. i.e. the + public interface of 1 argument is stable. The fact that any + arguments may be passed in after that one, and what those + arguments are, is _not_ part of the public interface and is + _not_ stable. */ - for(const i in args) args[i] = cxw.convertArgNoCheck(argTypes[i], args[i], i, args); + for(const i in args) args[i] = cxw.convertArgNoCheck( + argTypes[i], args[i], args, i + ); return cxw.convertResultNoCheck(resultType, xf.apply(null,args)); }finally{ target.scopedAllocPop(scope); diff --git a/ext/wasm/demo-worker1-promiser.js b/ext/wasm/demo-worker1-promiser.js index a65cc31b6..ef955403c 100644 --- a/ext/wasm/demo-worker1-promiser.js +++ b/ext/wasm/demo-worker1-promiser.js @@ -76,9 +76,7 @@ await wtest('config-get', (ev)=>{ const r = ev.result; log('sqlite3.config subset:', r); - T.assert('boolean' === typeof r.bigIntEnabled) - .assert('string'===typeof r.wasmfsOpfsDir) - .assert('boolean' === typeof r.wasmfsOpfsEnabled); + T.assert('boolean' === typeof r.bigIntEnabled); sqConfig = r; }); logHtml('', @@ -213,7 +211,7 @@ sql: "select 'foo' foo, a bar, 'baz' baz from t limit 2", callback: resultRowTest3, columnNames: [], - rowMode: ':bar' + rowMode: '$bar' }, function(ev){ log("exec() result row:",ev); T.assert(2===resultRowTest3.counter); diff --git a/ext/wasm/module-symbols.html b/ext/wasm/module-symbols.html index 6298aeb37..865f1b333 100644 --- a/ext/wasm/module-symbols.html +++ b/ext/wasm/module-symbols.html @@ -133,10 +133,10 @@ The <code>sqlite3.version</code> object exposes the following... </p> <div id='list-version' class='pseudolist wide wrap-anywhere'></div> - + <a id="sqlite3-functions"></a> <h1><code>sqlite3_...()</code> Function List</h1> - + <p>The <code>sqlite3.capi</code> namespace exposes the following <a href='https://sqlite.org/c3ref/funclist.html'><code>sqlite3_...()</code> functions</a>... @@ -194,6 +194,7 @@ <script>(async function(){ const apiLinks = Object.assign(Object.create(null),{ sqlite3_aggregate_context: 'www:/c3ref/aggregate_context.html', + sqlite3_auto_extension: 'wasm:/api-c-style.md#auto-extension', sqlite3_bind_blob: 'www:/c3ref/bind_blob.html', sqlite3_bind_double: 'www:/c3ref/bind_blob.html', sqlite3_bind_int: 'www:/c3ref/bind_blob.html', @@ -205,6 +206,7 @@ sqlite3_bind_text: 'www:/c3ref/bind_blob.html', sqlite3_busy_handler: 'www:/c3ref/busy_handler.html', sqlite3_busy_timeout: 'www:/c3ref/busy_timeout.html', + sqlite3_cancel_auto_extension: 'wasm:/api-c-style.md#auto-extension', sqlite3_changes: 'www:/c3ref/changes.html', sqlite3_changes64: 'www:/c3ref/changes.html', sqlite3_clear_bindings: 'www:/c3ref/clear_bindings.html', @@ -220,9 +222,12 @@ sqlite3_column_text: 'www:/c3ref/column_blob.html', sqlite3_column_type: 'www:/c3ref/column_blob.html', sqlite3_column_value: 'www:/c3ref/column_blob.html', + sqlite3_commit_hook: 'wasm:/api-c-style.md#hook-api', sqlite3_compileoption_get: 'www:/c3ref/compileoption_get.html', sqlite3_compileoption_used: 'www:/c3ref/compileoption_get.html', sqlite3_complete: 'www:/c3ref/complete.html', + sqlite3_config: 'www:/c3ref/config.html', + sqlite3_context_db_handle: 'www:/c3ref/context_db_handle.html', sqlite3_create_collation: 'www:/c3ref/create_collation.html', sqlite3_create_collation_v2: 'www:/c3ref/create_collation.html', sqlite3_create_function: 'wasm:/api-c-style.md#sqlite3_create_function', @@ -272,6 +277,7 @@ sqlite3_realloc: 'www:/c3ref/free.html', sqlite3_realloc64: 'www:/c3ref/free.html', sqlite3_reset: 'www:/c3ref/reset.html', + sqlite3_reset_auto_extension: 'wasm:/api-c-style.md#auto-extension', sqlite3_result_blob: 'www:/c3ref/result_blob.html', sqlite3_result_double: 'www:/c3ref/result_blob.html', sqlite3_result_error: 'www:/c3ref/result_blob.html', @@ -286,9 +292,10 @@ sqlite3_result_text: 'www:/c3ref/result_blob.html', sqlite3_result_zeroblob: 'www:/c3ref/result_blob.html', sqlite3_result_zeroblob64: 'www:/c3ref/result_blob.html', + sqlite3_rollback_hook: 'wasm:/api-c-style.md#hook-api', sqlite3_serialize: 'www:/c3ref/serialize.html', sqlite3_set_authorizer: 'wasm:/api-c-style.md#sqlite3_set_authorizer', - sqlite3_set_auxdata: 'www:/c3ref/set_auxdata.html', + sqlite3_set_auxdata: 'www:/c3ref/get_auxdata.html', sqlite3_set_last_insert_rowid: 'www:/c3ref/set_last_insert_rowid', sqlite3_shutdown: 'www:/c3ref/initialize.html', sqlite3_sourceid: 'www:/c3ref/libversion.html', @@ -308,6 +315,7 @@ sqlite3_total_changes64: 'www:/c3ref/total_changes.html', sqlite3_trace_v2: 'www:/c3ref/trace_v2.html', sqlite3_txn_state: 'www:/c3ref/txn_state.html', + sqlite3_update_hook: 'wasm:/api-c-style.md#hook-api', sqlite3_uri_boolean: 'www:/c3ref/uri_boolean.html', sqlite3_uri_int64: 'www:/c3ref/uri_boolean.html', sqlite3_uri_key: 'www:/c3ref/uri_boolean.html', diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index ae745a28d..7f5de5166 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -1010,7 +1010,12 @@ self.sqlite3InitModule = sqlite3InitModule; T.assert(P.quota >= 4096) .assert(remaining === P.quota) .mustThrowMatching(()=>P.alloc(0), isAllocErr) - .mustThrowMatching(()=>P.alloc(-1), isAllocErr); + .mustThrowMatching(()=>P.alloc(-1), isAllocErr) + .mustThrowMatching( + ()=>P.alloc('i33'), + (e)=>e instanceof sqlite3.WasmAllocError + ); + ; let p1 = P.alloc(12); T.assert(p1 === stack - 16/*8-byte aligned*/) .assert(P.pointer === p1); @@ -1029,7 +1034,7 @@ self.sqlite3InitModule = sqlite3InitModule; T.assert(P.pointer === stack); try { - const [p1, p2, p3] = P.allocChunks(3,4); + const [p1, p2, p3] = P.allocChunks(3,'i32'); T.assert(P.pointer === stack-16/*always rounded to multiple of 8*/) .assert(p2 === p1 + 4) .assert(p3 === p2 + 4); @@ -1143,7 +1148,7 @@ self.sqlite3InitModule = sqlite3InitModule; } } }; - + T.assert(wasm.isPtr(db.pointer)) .mustThrowMatching(()=>db.pointer=1, /read-only/) .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1)) @@ -1183,7 +1188,7 @@ self.sqlite3InitModule = sqlite3InitModule; T.assert(0 === rc); const stack = wasm.pstack.pointer; try { - const [pCur, pHi] = wasm.pstack.allocChunks(2,8); + const [pCur, pHi] = wasm.pstack.allocChunks(2,'i64'); rc = capi.sqlite3_db_status(this.db, capi.SQLITE_DBSTATUS_LOOKASIDE_USED, pCur, pHi, 0); T.assert(0===rc); @@ -1353,6 +1358,16 @@ self.sqlite3InitModule = sqlite3InitModule; if(wasm.bigIntEnabled){ T.assert(4n===db.changes(false,true)); } + + let vals = db.selectValues('select a from t order by a limit 2'); + T.assert( 2 === vals.length ) + .assert( 1===vals[0] && 3===vals[1] ); + vals = db.selectValues('select a from t order by a limit $L', + {$L:2}, capi.SQLITE_TEXT); + T.assert( 2 === vals.length ) + .assert( '1'===vals[0] && '3'===vals[1] ); + vals = undefined; + let blob = db.selectValue("select b from t where a='blob'"); T.assert(blob instanceof Uint8Array). assert(0x68===blob[0] && 0x69===blob[1]); @@ -1614,7 +1629,6 @@ self.sqlite3InitModule = sqlite3InitModule; //////////////////////////////////////////////////////////////////// .t({ name:'Scalar UDFs', - //predicate: ()=>false, test: function(sqlite3){ const db = this.db; db.createFunction("foo",(pCx,a,b)=>a+b); @@ -1625,6 +1639,7 @@ self.sqlite3InitModule = sqlite3InitModule; db.createFunction("bar", { arity: -1, xFunc: (pCx,...args)=>{ + T.assert(db.pointer === capi.sqlite3_context_db_handle(pCx)); let rc = 0; for(const v of args) rc += v; return rc; @@ -1680,6 +1695,45 @@ self.sqlite3InitModule = sqlite3InitModule; sqlite3.capi.SQLITE_MISUSE === rc, "For invalid arg count." ); + + /* Confirm that we can map and unmap the same function with + multiple arities... */ + const fCounts = [0,0]; + const fArityCheck = function(pCx){ + return ++fCounts[arguments.length-1]; + }; + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; + rc = capi.sqlite3_create_function_v2( + db, "nary", 0, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0 + ); + T.assert( 0===rc ); + rc = capi.sqlite3_create_function_v2( + db, "nary", 1, capi.SQLITE_UTF8, 0, fArityCheck, 0, 0, 0 + ); + T.assert( 0===rc ); + const sqlFArity0 = "select nary()"; + const sqlFArity1 = "select nary(1)"; + T.assert( 1 === db.selectValue(sqlFArity0) ) + .assert( 1 === fCounts[0] ).assert( 0 === fCounts[1] ); + T.assert( 1 === db.selectValue(sqlFArity1) ) + .assert( 1 === fCounts[0] ).assert( 1 === fCounts[1] ); + capi.sqlite3_create_function_v2( + db, "nary", 0, capi.SQLITE_UTF8, 0, 0, 0, 0, 0 + ); + T.mustThrowMatching((()=>db.selectValue(sqlFArity0)), + (e)=>((e instanceof sqlite3.SQLite3Error) + && e.message.indexOf("wrong number of arguments")>0), + "0-arity variant was uninstalled."); + T.assert( 2 === db.selectValue(sqlFArity1) ) + .assert( 1 === fCounts[0] ).assert( 2 === fCounts[1] ); + capi.sqlite3_create_function_v2( + db, "nary", 1, capi.SQLITE_UTF8, 0, 0, 0, 0, 0 + ); + T.mustThrowMatching((()=>db.selectValue(sqlFArity1)), + (e)=>((e instanceof sqlite3.SQLite3Error) + && e.message.indexOf("no such function")>0), + "1-arity variant was uninstalled."); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; } }) @@ -1728,7 +1782,7 @@ self.sqlite3InitModule = sqlite3InitModule; // sqlite3_result_xyz() and return undefined. Both are // functionally equivalent. } - }); + }); T.assert(18===db.selectValue('select summerN(1,8,9), summerN(2,3,4)')); T.mustThrowMatching(()=>{ db.createFunction('nope',{ @@ -1855,7 +1909,6 @@ self.sqlite3InitModule = sqlite3InitModule; .assert('select 2;' === rc[1]) .assert('-- empty\n; select 3' === rc[2] /* Strange but true. */); - T.mustThrowMatching(()=>{ db.exec({sql:'', returnValue: 'nope'}); }, /^Invalid returnValue/); @@ -1882,18 +1935,18 @@ self.sqlite3InitModule = sqlite3InitModule; T.assert(3===resultRows.length) .assert(2===resultRows[1]); T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a')); + + /** Demonstrate the JS-simplified form of the sqlite3_exec() callback... */ let colCount = 0, rowCount = 0; - const execCallback = function(pVoid, nCols, aVals, aNames){ - //console.warn("execCallback(",arguments,")"); - colCount = nCols; - ++rowCount; - T.assert(2===aVals.length) - .assert(2===aNames.length) - .assert(+(aVals[1]) === 2 * +(aVals[0])); - }; let rc = capi.sqlite3_exec( - db.pointer, "select a, a*2 from foo.bar", execCallback, - 0, 0 + db, "select a, a*2 from foo.bar", function(aVals, aNames){ + //console.warn("execCallback(",arguments,")"); + colCount = aVals.length; + ++rowCount; + T.assert(2===aVals.length) + .assert(2===aNames.length) + .assert(+(aVals[1]) === 2 * +(aVals[0])); + }, 0, 0 ); T.assert(0===rc).assert(3===rowCount).assert(2===colCount); rc = capi.sqlite3_exec( @@ -1902,8 +1955,48 @@ self.sqlite3InitModule = sqlite3InitModule; }, 0, 0 ); T.assert(capi.SQLITE_ABORT === rc); + + /* Demonstrate how to get access to the "full" callback + signature, as opposed to the simplified JS-specific one... */ + rowCount = colCount = 0; + const pCb = wasm.installFunction('i(pipp)', function(pVoid,nCols,aVals,aCols){ + /* Tip: wasm.cArgvToJs() can be used to convert aVals and + aCols to arrays: const vals = wasm.cArgvToJs(nCols, + aVals); */ + ++rowCount; + colCount = nCols; + T.assert(2 === nCols) + .assert(wasm.isPtr(pVoid)) + .assert(wasm.isPtr(aVals)) + .assert(wasm.isPtr(aCols)) + .assert(+wasm.cstrToJs(wasm.peekPtr(aVals + wasm.ptrSizeof)) + === 2 * +wasm.cstrToJs(wasm.peekPtr(aVals))); + return 0; + }); + try { + T.assert(wasm.isPtr(pCb)); + rc = capi.sqlite3_exec( + db, new TextEncoder('utf-8').encode("select a, a*2 from foo.bar"), + pCb, 0, 0 + ); + T.assert(0===rc) + .assert(3===rowCount) + .assert(2===colCount); + }finally{ + wasm.uninstallFunction(pCb); + } + + // Demonstrate that an OOM result does not propagate through sqlite3_exec()... + rc = capi.sqlite3_exec( + db, ["select a,"," a*2 from foo.bar"], (aVals, aNames)=>{ + sqlite3.WasmAllocError.toss("just testing"); + }, 0, 0 + ); + T.assert(capi.SQLITE_ABORT === rc); + db.exec("detach foo"); - T.mustThrow(()=>db.exec("select * from foo.bar")); + T.mustThrow(()=>db.exec("select * from foo.bar"), + "Because foo is no longer attached."); }) //////////////////////////////////////////////////////////////////// @@ -2595,6 +2688,277 @@ self.sqlite3InitModule = sqlite3InitModule; ;/* end OPFS tests */ //////////////////////////////////////////////////////////////////////// + T.g('Hook APIs') + .t({ + name: "sqlite3_commit/rollback/update_hook()", + predicate: ()=>wasm.bigIntEnabled || "Update hook requires int64", + test: function(sqlite3){ + let countCommit = 0, countRollback = 0;; + const db = new sqlite3.oo1.DB(':memory:',1 ? 'c' : 'ct'); + let rc = capi.sqlite3_commit_hook(db, (p)=>{ + ++countCommit; + return (1 === p) ? 0 : capi.SQLITE_ERROR; + }, 1); + T.assert( 0 === rc /*void pointer*/ ); + + // Commit hook... + db.exec("BEGIN; SELECT 1; COMMIT"); + T.assert(0 === countCommit, + "No-op transactions (mostly) do not trigger commit hook."); + db.exec("BEGIN EXCLUSIVE; SELECT 1; COMMIT"); + T.assert(1 === countCommit, + "But EXCLUSIVE transactions do."); + db.transaction((d)=>{d.exec("create table t(a)");}); + T.assert(2 === countCommit); + + // Rollback hook: + rc = capi.sqlite3_rollback_hook(db, (p)=>{ + ++countRollback; + T.assert( 2 === p ); + }, 2); + T.assert( 0 === rc /*void pointer*/ ); + T.mustThrowMatching(()=>{ + db.transaction('drop table t',()=>{}) + }, (e)=>{ + return (capi.SQLITE_MISUSE === e.resultCode) + && ( e.message.indexOf('Invalid argument') > 0 ); + }); + T.assert(0 === countRollback, "Transaction was not started."); + T.mustThrowMatching(()=>{ + db.transaction('immediate', ()=>{ + sqlite3.SQLite3Error.toss(capi.SQLITE_FULL,'testing rollback hook'); + }); + }, (e)=>{ + return capi.SQLITE_FULL === e.resultCode + }); + T.assert(1 === countRollback); + + // Update hook... + const countUpdate = Object.create(null); + capi.sqlite3_update_hook(db, (p,op,dbName,tbl,rowid)=>{ + T.assert('main' === dbName.toLowerCase()) + .assert('t' === tbl.toLowerCase()) + .assert(3===p) + .assert('bigint' === typeof rowid); + switch(op){ + case capi.SQLITE_INSERT: + case capi.SQLITE_UPDATE: + case capi.SQLITE_DELETE: + countUpdate[op] = (countUpdate[op]||0) + 1; + break; + default: toss("Unexpected hook operator:",op); + } + }, 3); + db.transaction((d)=>{ + d.exec([ + "insert into t(a) values(1);", + "update t set a=2;", + "update t set a=3;", + "delete from t where a=3" + // update hook is not called for an unqualified DELETE + ]); + }); + T.assert(1 === countRollback) + .assert(3 === countCommit) + .assert(1 === countUpdate[capi.SQLITE_INSERT]) + .assert(2 === countUpdate[capi.SQLITE_UPDATE]) + .assert(1 === countUpdate[capi.SQLITE_DELETE]); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; + T.assert(1 === capi.sqlite3_commit_hook(db, 0, 0)); + T.assert(2 === capi.sqlite3_rollback_hook(db, 0, 0)); + T.assert(3 === capi.sqlite3_update_hook(db, 0, 0)); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; + db.close(); + } + })/* commit/rollback/update hooks */ + .t({ + name: "sqlite3_preupdate_hook()", + predicate: ()=>wasm.bigIntEnabled || "Pre-update hook requires int64", + test: function(sqlite3){ + const db = new sqlite3.oo1.DB(':memory:', 1 ? 'c' : 'ct'); + const countHook = Object.create(null); + let rc = capi.sqlite3_preupdate_hook( + db, function(p, pDb, op, zDb, zTbl, iKey1, iKey2){ + T.assert(9 === p) + .assert(db.pointer === pDb) + .assert(1 === capi.sqlite3_preupdate_count(pDb)) + .assert( 0 > capi.sqlite3_preupdate_blobwrite(pDb) ); + countHook[op] = (countHook[op]||0) + 1; + switch(op){ + case capi.SQLITE_INSERT: + case capi.SQLITE_UPDATE: + T.assert('number' === typeof capi.sqlite3_preupdate_new_js(pDb, 0)); + break; + case capi.SQLITE_DELETE: + T.assert('number' === typeof capi.sqlite3_preupdate_old_js(pDb, 0)); + break; + default: toss("Unexpected hook operator:",op); + } + }, + 9 + ); + db.transaction((d)=>{ + d.exec([ + "create table t(a);", + "insert into t(a) values(1);", + "update t set a=2;", + "update t set a=3;", + "delete from t where a=3" + ]); + }); + T.assert(1 === countHook[capi.SQLITE_INSERT]) + .assert(2 === countHook[capi.SQLITE_UPDATE]) + .assert(1 === countHook[capi.SQLITE_DELETE]); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = true; + db.close(); + //wasm.xWrap.FuncPtrAdapter.debugFuncInstall = false; + } + })/*pre-update hooks*/ + ;/*end hook API tests*/ + + //////////////////////////////////////////////////////////////////////// + T.g('Auto-extension API') + .t({ + name: "Auto-extension sanity checks.", + test: function(sqlite3){ + let counter = 0; + const fp = wasm.installFunction('i(ppp)', function(pDb,pzErr,pApi){ + ++counter; + return 0; + }); + (new sqlite3.oo1.DB()).close(); + T.assert( 0===counter ); + capi.sqlite3_auto_extension(fp); + (new sqlite3.oo1.DB()).close(); + T.assert( 1===counter ); + (new sqlite3.oo1.DB()).close(); + T.assert( 2===counter ); + capi.sqlite3_cancel_auto_extension(fp); + wasm.uninstallFunction(fp); + (new sqlite3.oo1.DB()).close(); + T.assert( 2===counter ); + } + }); + + //////////////////////////////////////////////////////////////////////// + T.g('Session API') + .t({ + name: 'Session API sanity checks', + predicate: ()=>!!capi.sqlite3changegroup_add, + test: function(sqlite3){ + warn("The session API tests could use some expansion."); + const db1 = new sqlite3.oo1.DB(), db2 = new sqlite3.oo1.DB(); + const sqlInit = [ + "create table t(rowid INTEGER PRIMARY KEY,a,b); ", + "insert into t(rowid,a,b) values", + "(1,'a1','b1'),", + "(2,'a2','b2'),", + "(3,'a3','b3');" + ].join(''); + db1.exec(sqlInit); + db2.exec(sqlInit); + T.assert(3 === db1.selectValue("select count(*) from t")) + .assert('b3' === db1.selectValue('select b from t where rowid=3')); + const stackPtr = wasm.pstack.pointer; + try{ + let ppOut = wasm.pstack.allocPtr(); + let rc = capi.sqlite3session_create(db1, "main", ppOut); + T.assert(0===rc); + let pSession = wasm.peekPtr(ppOut); + T.assert(pSession && wasm.isPtr(pSession)); + capi.sqlite3session_table_filter(pSession, (pCtx, tbl)=>{ + T.assert('t' === tbl).assert( 99 === pCtx ); + return 1; + }, 99); + db1.exec([ + "update t set b='bTwo' where rowid=2;", + "update t set a='aThree' where rowid=3;", + "delete from t where rowid=1;", + "insert into t(rowid,a,b) values(4,'a4','b4')" + ]); + T.assert('bTwo' === db1.selectValue("select b from t where rowid=2")) + .assert(undefined === db1.selectValue('select a from t where rowid=1')) + .assert('b4' === db1.selectValue('select b from t where rowid=4')) + .assert(3 === db1.selectValue('select count(*) from t')); + + const testSessionEnable = false; + if(testSessionEnable){ + rc = capi.sqlite3session_enable(pSession, 0); + T.assert( 0 === rc ) + .assert( 0 === capi.sqlite3session_enable(pSession, -1) ); + db1.exec("delete from t where rowid=2;"); + rc = capi.sqlite3session_enable(pSession, 1); + T.assert( rc > 0 ) + .assert( capi.sqlite3session_enable(pSession, -1) > 0 ) + .assert(undefined === db1.selectValue('select a from t where rowid=2')); + }else{ + warn("sqlite3session_enable() tests disabled due to unexpected results.", + "(Possibly a tester misunderstanding, as opposed to a bug.)"); + } + let db1Count = db1.selectValue("select count(*) from t"); + T.assert( db1Count === (testSessionEnable ? 2 : 3) ); + + /* Capture changeset and destroy session. */ + let pnChanges = wasm.pstack.alloc('i32'), + ppChanges = wasm.pstack.allocPtr(); + rc = capi.sqlite3session_changeset(pSession, pnChanges, ppChanges); + T.assert( 0 === rc ); + capi.sqlite3session_delete(pSession); + pSession = 0; + const pChanges = wasm.peekPtr(ppChanges), + nChanges = wasm.peek32(pnChanges); + T.assert( pChanges && wasm.isPtr( pChanges ) ) + .assert( nChanges > 0 ); + + /* Revert db1 via an inverted changeset, but keep pChanges + and nChanges for application to db2. */ + rc = capi.sqlite3changeset_invert( nChanges, pChanges, pnChanges, ppChanges ); + T.assert( 0 === rc ); + rc = capi.sqlite3changeset_apply( + db1, wasm.peek32(pnChanges), wasm.peekPtr(ppChanges), 0, (pCtx, eConflict, pIter)=>{ + return 1; + }, 0 + ); + T.assert( 0 === rc ); + wasm.dealloc( wasm.peekPtr(ppChanges) ); + pnChanges = ppChanges = 0; + T.assert('b2' === db1.selectValue("select b from t where rowid=2")) + .assert('a1' === db1.selectValue('select a from t where rowid=1')) + .assert(undefined === db1.selectValue('select b from t where rowid=4')); + db1Count = db1.selectValue("select count(*) from t"); + T.assert(3 === db1Count); + + /* Apply pre-reverted changeset (pChanges, nChanges) to + db2... */ + rc = capi.sqlite3changeset_apply( + db2, nChanges, pChanges, 0, (pCtx, eConflict, pIter)=>{ + return pCtx ? 1 : 0 + }, 1 + ); + wasm.dealloc( pChanges ); + T.assert( 0 === rc ) + .assert( 'b4' === db2.selectValue('select b from t where rowid=4') ) + .assert( 'aThree' === db2.selectValue('select a from t where rowid=3') ) + .assert( undefined === db2.selectValue('select b from t where rowid=1') ); + if(testSessionEnable){ + T.assert( (undefined === db2.selectValue('select b from t where rowid=2')), + "But... the session was disabled when rowid=2 was deleted?" ); + log("rowids from db2.t:",db2.selectValues('select rowid from t order by rowid')); + T.assert( 3 === db2.selectValue('select count(*) from t') ); + }else{ + T.assert( 'bTwo' === db2.selectValue('select b from t where rowid=2') ) + .assert( 3 === db2.selectValue('select count(*) from t') ); + } + }finally{ + wasm.pstack.restore(stackPtr); + db1.close(); + db2.close(); + } + } + })/*session API sanity tests*/ + ;/*end of session API group*/; + + //////////////////////////////////////////////////////////////////////// log("Loading and initializing sqlite3 WASM module..."); if(!self.sqlite3InitModule && !isUIThread()){ /* Vanilla worker, as opposed to an ES6 module worker */ @@ -1,5 +1,5 @@ -C Incorporate\swasi-sdk\sRANLIB\sconfigure\sscript\spatch\sfrom\sthe\sVMware\sOCTO\steam. -D 2022-12-24T15:34:16.082 +C Merge\strunk\sinto\swasi-patches\sbranch\sand\sadd\smissing\syes/no\sresult\sto\sthe\sconfigure\sscript's\soutput\sfor\sthe\s--with-wasi-sdk=PATH\stest. +D 2023-01-11T22:45:20.283 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -33,8 +33,8 @@ F autoconf/tea/win/nmakehlp.c b01f822eabbe1ed2b64e70882d97d48402b42d2689a1ea0034 F autoconf/tea/win/rules.vc c511f222b80064096b705dbeb97060ee1d6b6d63 F config.guess 883205ddf25b46f10c181818bf42c09da9888884af96f79e1719264345053bd6 F config.sub c2d0260f17f3e4bc0b6808fccf1b291cb5e9126c14fc5890efc77b9fd0175559 -F configure 7932e767cf730e8161b65bca56dee4d3bba56acd90808ac262e8c6345d1ca006 x -F configure.ac b5f9355c46b73222ea1a9a16be868e016adda9b9b6349bfc3f72ae267f38331c +F configure 6ba84abf7878bb072d460766d29e492028468775d4c09464940343092eb6493c x +F configure.ac 4654d32ac0a0d0b48f1e1e79bdc3d777b723cf2f63c33eb1d7c4ed8b435938e8 F contrib/sqlitecon.tcl 210a913ad63f9f991070821e599d600bd913e0ad F doc/F2FS.txt c1d4a0ae9711cfe0e1d8b019d154f1c29e0d3abfe820787ba1e9ed7691160fcd F doc/json-enhancements.md e356fc834781f1f1aa22ee300027a270b2c960122468499bf347bb123ce1ea4f @@ -119,7 +119,7 @@ F ext/fts5/fts5_config.c 501e7d3566bc92766b0e11c0109a7c5a6146bc41144195459af5422 F ext/fts5/fts5_expr.c 40174a64829d30cc86e8266306ad24980f6911edd5ca0b8c1ce7821ea1341b88 F ext/fts5/fts5_hash.c d4fb70940359f2120ccd1de7ffe64cc3efe65de9e8995b822cd536ff64c96982 F ext/fts5/fts5_index.c e879315306f368b5681d32cffc90770b124649814214615b88d79ddd957f6747 -F ext/fts5/fts5_main.c 3fd46be6a7aaac1d4210d4df0c7f9b1e78d1f0af566bfa2ab58d945ffa328ff7 +F ext/fts5/fts5_main.c 2bf44b9efdecc7fd936f320064666235b91bba8a6c4f3dc6e2d7eae6662570ed F ext/fts5/fts5_storage.c 76c6085239eb44424004c022e9da17a5ecd5aaec859fba90ad47d3b08f4c8082 F ext/fts5/fts5_tcl.c b1445cbe69908c411df8084a10b2485500ac70a9c747cdc8cda175a3da59d8ae F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee @@ -194,7 +194,7 @@ F ext/fts5/test/fts5leftjoin.test c0b4cafb9661379e576dc4405c0891d8fcc27826807405 F ext/fts5/test/fts5matchinfo.test 10c9a6f7fe61fb132299c4183c012770b10c4d5c2f2edb6df0b6607f683d737a F ext/fts5/test/fts5merge.test e92a8db28b45931e7a9c7b1bbd36101692759d00274df74d83fd29d25d53b3a6 F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2 -F ext/fts5/test/fts5misc.test f0c5d5f6bc64f7cec522042f0ceb79c9195a4cde9fceb2af3718b3f10c8b7168 +F ext/fts5/test/fts5misc.test d6d4fdd7ec164e69e50af539137c0565362a4124547bf841ba474f092298637b F ext/fts5/test/fts5multi.test a15bc91cdb717492e6e1b66fec1c356cb57386b980c7ba5af1915f97fe878581 F ext/fts5/test/fts5multiclient.test 5ff811c028d6108045ffef737f1e9f05028af2458e456c0937c1d1b8dea56d45 F ext/fts5/test/fts5near.test 211477940142d733ac04fad97cb24095513ab2507073a99c2765c3ddd2ef58bd @@ -267,19 +267,19 @@ F ext/lsm1/lsm-test/lsmtest_tdb4.c cbe230727b9413d244062943371af1421ace472ccb023 F ext/lsm1/lsm-test/lsmtest_util.c 241622db5a332a09c8e6e7606b617d288a37b557f7d3bce0bb97809f67cc2806 F ext/lsm1/lsm-test/lsmtest_win32.c 0e0a224674c4d3170631c41b026b56c7e1672b151f5261e1b4cc19068641da2d F ext/lsm1/lsm.h 0f6f64ff071471cb87bf98beb8386566f30ea001 -F ext/lsm1/lsmInt.h 5983690e05e83653cc01ba9d8fbf8455e534ddf8349ed9adedbf46a7549760b0 -F ext/lsm1/lsm_ckpt.c 0eabfaf812ddb4ea43add38f05e430694cd054eb622c3e35af4c43118a2d5321 -F ext/lsm1/lsm_file.c 3c51841d5b3e7da162693cbac9a9f47eeedf6bcbbe2969a4d25e30c428c9fe36 +F ext/lsm1/lsmInt.h 22ed041ac98a94b654432c33e0857d0a128e35d1935f2ce49f64614f03607929 +F ext/lsm1/lsm_ckpt.c ad9a8028d401be9e76f20c4d86d49f33f4fc27785577b452ca955094314a72b4 +F ext/lsm1/lsm_file.c 5486f4a63b19e4d7d972ee2482f29ebdf06c29544f31845f713cccb5199f9ad1 F ext/lsm1/lsm_log.c a8bf334532109bba05b09a504ee45fc393828b0d034ca61ab45e3940709d9a7c -F ext/lsm1/lsm_main.c b5703f8042e71d3a2d65e671f6832e077e79e89e9975818f67f969922618db63 +F ext/lsm1/lsm_main.c 87770a9c7e73859fce7620cb79623776ba4b30369086229ad82c3e6eeaf45457 F ext/lsm1/lsm_mem.c 4c51ea9fa285ee6e35301b33491642d071740a0a F ext/lsm1/lsm_mutex.c 378edf0a2b142b4f7640ee982df06d50b98788ea -F ext/lsm1/lsm_shared.c 76adfc1ed9ffebaf92746dde4b370ccc48143ca8b05b563816eadd2aadf1c525 -F ext/lsm1/lsm_sorted.c 6f7d8cf7a7d3d3f1ab5d9ba6347e8f39f3d73c00ec48afcd0c4bcbefd806f9b8 +F ext/lsm1/lsm_shared.c c67282a4f2c91e2a3362bdd40a81f9041cd587973ffc4bca8b8fbdab5470dee1 +F ext/lsm1/lsm_sorted.c 8bf11fa5ee03e49858375316aee89bb8fa7c353e1a4138a82312ae5aad4f274b F ext/lsm1/lsm_str.c 65e361b488c87b10bf3e5c0070b14ffc602cf84f094880bece77bbf6678bca82 F ext/lsm1/lsm_tree.c 682679d7ef2b8b6f2fe77aeb532c8d29695bca671c220b0abac77069de5fb9fb F ext/lsm1/lsm_unix.c 11e0a5c19d754a4e1d93dfad06de8cc201f10f886b8e61a4c599ed34e334fc24 -F ext/lsm1/lsm_varint.c 43f954af668a66c7928b81597c14d6ad4be9fedbc276bbd80f52fa28a02fdb62 +F ext/lsm1/lsm_varint.c fe134ad7b2db1ecd99b6a155d2f3625cfd497730e227ae18892452e457b73327 F ext/lsm1/lsm_vtab.c e57aa3eb456bf2b98064014027e097c9402d6dec7b59564ddbfa1c0ead8f96c5 F ext/lsm1/lsm_win32.c 0a4acbd7e8d136dd3a5753f0a9e7a9802263a9d96cef3278cf120bcaa724db7c F ext/lsm1/test/lsm1_common.tcl 5ed4bab07c93be2e4f300ebe46007ecf4b3e20bc5fbe1dedaf04a8774a6d8d82 @@ -289,7 +289,7 @@ F ext/misc/README.md d6dd0fe1d8af77040216798a6a2b0c46c73054d2f0ea544fbbcdccf6f23 F ext/misc/amatch.c e3ad5532799cee9a97647f483f67f43b38796b84b5a8c60594fe782a4338f358 F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb F ext/misc/appendvfs.c 9642c7a194a2a25dca7ad3e36af24a0a46d7702168c4ad7e59c9f9b0e16a3824 -F ext/misc/base64.c d9bcb499d4edf42d1d981cc49af0a084532af288f982facb83e23c2e46f7c009 x +F ext/misc/base64.c d43d2b209c8ab70ca3f860104bb353b0f52a1c5462a2466140025c954e4f3ea7 x F ext/misc/base85.c 4b53d66c50e120e8697dd2a8ea6ddbc8750a4a1f6bcc6e0b7202a3998b0852bc F ext/misc/basexx.c 5e859e1820620aa8080fb9145eb47089de426ae808f6abb01a8e12921c3a8e67 F ext/misc/blobio.c a867c4c4617f6ec223a307ebfe0eabb45e0992f74dd47722b96f3e631c0edb2a @@ -341,7 +341,7 @@ F ext/misc/vfsstat.c 474d08efc697b8eba300082cb1eb74a5f0f3df31ed257db1cb07e72ab0e F ext/misc/vtablog.c 5538acd0c8ddaae372331bee11608d76973436b77d6a91e8635cfc9432fba5ae F ext/misc/vtshim.c 1976e6dd68dd0d64508c91a6dfab8e75f8aaf6cd F ext/misc/wholenumber.c a838d1bea913c514ff316c69695efbb49ea3b8cb37d22afc57f73b6b010b4546 -F ext/misc/zipfile.c bc4f8c552c8eed68fddc44600da9eed5d4de9c0e765b1256cf8750288097307f +F ext/misc/zipfile.c f98239261488397618ce4754c500626d1de20cd2d44bf2f2d571d7ddaab668a7 F ext/misc/zorder.c b0ff58fa643afa1d846786d51ea8d5c4b6b35aa0254ab5a82617db92f3adda64 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test c62904bd9526dcdc3496a21199aaf14ae191bbadbf67f076bf16be6b3f2115c2 @@ -494,7 +494,7 @@ F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34ce F ext/wasm/GNUmakefile ffe0e9818a3b6b36c85a1d10dab76b220a8f5cd83439c62e50223a7970b3d68a F ext/wasm/README-dist.txt 2d670b426fc7c613b90a7d2f2b05b433088fe65181abead970980f0a4a75ea20 F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9 -F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 4c7788042196cecab32f87d8e4965c183fea59037603888059f244b1752babcc +F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api d6a5078f48a5301ed17b9a30331075d9b2506e1360c1f0dee0c7816c10acd9ab F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 F ext/wasm/api/README.md 77a2f1f2fc60a35def7455dffc8d3f2c56385d6ac5c6cecc60fa938252ea2c54 F ext/wasm/api/extern-post-js.c-pp.js 8923f76c3d2213159e12d641dc750523ead5c848185dc4996fae5cc12397f88d @@ -503,32 +503,32 @@ F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08 F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62 F ext/wasm/api/pre-js.c-pp.js b88499dc303c21fc3f55f2c364a0f814f587b60a95784303881169f9e91c1d5f F ext/wasm/api/sqlite3-api-cleanup.js 680d5ccfff54459db136a49b2199d9f879c8405d9c99af1dda0cc5e7c29056f4 -F ext/wasm/api/sqlite3-api-glue.js ba532798e577497da9221bf9ac7d286619eec4d16736c927f1d10f3c8d21ada3 -F ext/wasm/api/sqlite3-api-oo1.js 5393fb0b325d2fdafada7fdbfb9219af9a865631acb351d5c5196a982b632c8b -F ext/wasm/api/sqlite3-api-prologue.js b0302c61abf21966c8cf9788453fea29c790633f7a14a92e05e6db994b590d11 -F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f +F ext/wasm/api/sqlite3-api-glue.js 0a93e58aabf52b32ddccbb107a1fd4552f2505e103ab63396c4d0a0743704785 +F ext/wasm/api/sqlite3-api-oo1.js e9fba119e9b1716b3f731838ed1ab18741401bcf4c51d2a4a6e9d1d23cf9d771 +F ext/wasm/api/sqlite3-api-prologue.js 0b9b463db92881990ab6995c863c5968969da3d2a5029112037cbf425c9cb6b1 +F ext/wasm/api/sqlite3-api-worker1.js c9ef8865f072e61251260b218aa4ed614a21a25e9e3cc6f22acf81794d32fc0b F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3 F ext/wasm/api/sqlite3-opfs-async-proxy.js 7795b84b66a7a8dedc791340709b310bb497c3c72a80bef364fa2a58e2ddae3f F ext/wasm/api/sqlite3-v-helper.js 6f6c3e390a72e08b0a5b16a0d567d7af3c04d172831853a29d72a6f1dd40ff24 F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 66daf6fb6843bea615fe193109e1542efbeca24f560ee9da63375a910bb48115 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 -F ext/wasm/api/sqlite3-wasm.c 313489816e1733a10ece74a92cbea65d3ee241eb07d98088e96258cc211c9719 +F ext/wasm/api/sqlite3-wasm.c 76625a70937a8522d014ef686c32db5b53a3ee61850323f5c601d2ac39fe52fe F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b -F ext/wasm/api/sqlite3-worker1.js 1e54ea3d540161bcfb2100368a2fc0cad871a207b8336afee1c445715851ec54 +F ext/wasm/api/sqlite3-worker1.js 9d3d3dfc70bff8998c1d8ff6d881cf1c3d52468d635417f02796151fe6b31cd7 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e F ext/wasm/c-pp.c 92285f7bce67ed7b7020b40fde8ed0982c442b63dc33df9dfd4b658d4a6c0779 F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f -F ext/wasm/common/whwasmutil.js 700fb1b702986522d2177fe8247bfbab3040e82cb4f6c35e929c4c85fbd7ffc5 +F ext/wasm/common/whwasmutil.js cad510071533dbe47787bce53f2e0dcab5b05d2dde1dbe477d8fb04e00d4e8c4 F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6 F ext/wasm/demo-jsstorage.html 409c4be4af5f207fb2877160724b91b33ea36a3cd8c204e8da1acb828ffe588e F ext/wasm/demo-jsstorage.js 44e3ae7ec2483b6c511384c3c290beb6f305c721186bcf5398ca4e00004a06b8 F ext/wasm/demo-worker1-promiser.html 1de7c248c7c2cfd4a5783d2aa154bce62d74c6de98ab22f5786620b3354ed15f -F ext/wasm/demo-worker1-promiser.js b85a2bb1b918db4f09dfa24419241cb3edad7791389425c2505092e9b715017d +F ext/wasm/demo-worker1-promiser.js b99c550763fa792c204e9a7cceadd976004036d9fc3e22fab7051712e30d207d F ext/wasm/demo-worker1.html 2c178c1890a2beb5a5fecb1453e796d067a4b8d3d2a04d65ca2eb1ab2c68ef5d F ext/wasm/demo-worker1.js a619adffc98b75b66c633b00f747b856449a134a9a0357909287d80a182d70fa F ext/wasm/dist.make 5523b02e824db5ab8176e3eedc2e709fe1204d8f4d6e52e8321cdf6830114b72 @@ -541,7 +541,7 @@ F ext/wasm/index-dist.html c806b6005145b71d64240606e9c6e0bf56878ee8829c66fe7486c F ext/wasm/index.html cc8b174ff01be282b399e64b58bdf3c921d7020da5d4e22e88bbbb4a6787a209 F ext/wasm/jaccwabyt/jaccwabyt.js 06f2ef1ad640c26c593def3d960336e9bb789819b920516480895c38ed5f58fa F ext/wasm/jaccwabyt/jaccwabyt.md 37911f00db12cbcca73aa1ed72594430365f30aafae2fa9c886961de74e5e0eb -F ext/wasm/module-symbols.html 573317801087e67c946a157783715d5716e027fcf484918a0c3aae4e627cc93d +F ext/wasm/module-symbols.html 841de62fc198988b8330e238c260e70ec93028b096e1a1234db31b187a899d10 F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06 F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18 F ext/wasm/speedtest1-wasmfs.html 7a301f4f5b6ad4f5d37fd6e7ca03a2f5d5547fd289da60a39075a93d7646d354 @@ -555,7 +555,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555 F ext/wasm/test-opfs-vfs.js f09266873e1a34d9bdb6d3981ec8c9e382f31f215c9fd2f9016d2394b8ae9b7b F ext/wasm/tester1-worker.html d43f3c131d88f10d00aff3e328fed13c858d674ea2ff1ff90225506137f85aa9 F ext/wasm/tester1.c-pp.html d34bef3d48e5cbc1c7c06882ad240fec49bf88f5f65696cc2c72c416933aa406 -F ext/wasm/tester1.c-pp.js dec750b65ec33ff267b35370ab746899859beb0a7c695cb9b087663d5b144512 +F ext/wasm/tester1.c-pp.js 7786fa3bdf074be8935e3a77b1ca135fd36ef6f87823c0695deaca585b133d06 F ext/wasm/tests/opfs/concurrency/index.html 86d8ac435074d1e7007b91105f4897f368c165e8cecb6a9aa3d81f5cf5dcbe70 F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -584,10 +584,10 @@ F src/auth.c f4fa91b6a90bbc8e0d0f738aa284551739c9543a367071f55574681e0f24f8cf F src/backup.c a2891172438e385fdbe97c11c9745676bec54f518d4447090af97189fd8e52d7 F src/bitvec.c 7c849aac407230278445cb069bebc5f89bf2ddd87c5ed9459b070a9175707b3d F src/btmutex.c 6ffb0a22c19e2f9110be0964d0731d2ef1c67b5f7fabfbaeb7b9dabc4b7740ca -F src/btree.c 2f794c217e52fdf4322bf37ee7778331b4d93aed2c00b5d67f914c0239a9edcc -F src/btree.h 49da925329574798be3cbb745a49d069a9e67c99900d8a0d04b1e934d60394ea -F src/btreeInt.h 88ad499c92b489afedbfefc3f067c4d15023ec021afe622db240dc9d2277cfa5 -F src/build.c e7b131773a3647701660d1b929b9947bccc9b3397788459168dd04a9e29a1f1f +F src/btree.c 8b776a47d1e791ca78b539b8356cf32e449a613201cf64b87e7f01c62f79bd1b +F src/btree.h aa354b9bad4120af71e214666b35132712b8f2ec11869cb2315c52c81fad45cc +F src/btreeInt.h 06bb2c1a07172d5a1cd27a2a5d617b93b1e976c5873709c31964786f86365a6e +F src/build.c c55ab6d1b089ceef57160e840f05f692955ac90944c3d04fcf01d97fd7bfd08d F src/callback.c 4cd7225b26a97f7de5fee5ae10464bed5a78f2adefe19534cc2095b3a8ca484a F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e F src/ctime.c 20507cc0b0a6c19cd882fcd0eaeda32ae6a4229fb4b024cfdf3183043d9b703d @@ -600,21 +600,21 @@ F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 F src/fkey.c 722f20779f5342a787922deded3628d8c74b5249cab04098cf17ee2f2aaff002 F src/func.c 68f610a44962814a4ba593fc95137a6682cb7e65086f22167e167b75ee3432e7 F src/global.c e06ff8e0acd85aec13563c9ecb44fbbf38232ccf73594998fd880b92d619594b -F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19 +F src/hash.c c6af5f96a7a76d000f07c5402c48c318c2566beecdee9e78b9d9f60ce7119565 F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h b638809e083b601b618df877b2e89cb87c2a47a01f4def10be4c4ebb54664ac7 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 1b11a2e33ee52db93c02fddac67e39d00161d61b69fac2675b82f2aa68c1b61c F src/json.c 7749b98c62f691697c7ee536b570c744c0583cab4a89200fdd0fc2aa8cc8cbd6 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa -F src/loadext.c 25663175950c5c4404b9377840b7b4c6fe5c53b415caf43634c62f442c02a9a7 -F src/main.c 5fba7c69ac63d728090d164930855d8f1dea37cce02858d77a9500ad20261a4b +F src/loadext.c b04eb648cedc45efe4298e1ef439ac4f0096ae27b5f01accb0a1f49d57789128 +F src/main.c f5ed7b748d7e54b96ac577a9d87af9461f13f092bbe684d473448010333402dc F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c c12a42539b1ba105e3707d0e628ad70e611040d8f5e38cf942cee30c867083de F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 F src/mem3.c 30301196cace2a085cbedee1326a49f4b26deff0af68774ca82c1f7c06fda4f6 -F src/mem5.c 5a3dbd8ac8a6501152a4fc1fcae9b0900c2d7eb0589c4ec7456fdde15725a26c +F src/mem5.c b7da5c10a726aacacc9ad7cdcb0667deec643e117591cc69cf9b4b9e7f3e96ff F src/memdb.c 559c42e61eb70cd6d4bc692b042497133c6d96c09a3d514d92f3dac72268e223 F src/memjournal.c c283c6c95d940eb9dc70f1863eef3ee40382dbd35e5a1108026e7817c206e8a0 F src/msvc.h 3a15918220367a8876be3fa4f2abe423a861491e84b864fb2b7426bf022a28f8 @@ -627,9 +627,9 @@ F src/notify.c 89a97dc854c3aa62ad5f384ef50c5a4a11d70fcc69f86de3e991573421130ed6 F src/os.c 81c9c1c52eab711e27e33fd51fe5788488d3a02bc1a71439857abbee5d0d2c97 F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63 F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 -F src/os_kv.c 73f89ab97ecdb3216857d2acc8395103f89164eaadac87cce4e9e16445c89541 +F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 -F src/os_unix.c 1086fcd1e024f009576d64bb7e1fc7f2cbcb5076c26246c5be127143330a9658 +F src/os_unix.c ca8e0cd2fb8ec774cfe6bb1f101e480f2a8f913716976d95c5556fee780c099e F src/os_win.c 295fe45f18bd86f2477f4cd79f3377c6f883ceb941b1f46808665c73747f2345 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c d3122cf67f327f1e2df12d06236a3473a8099542071e257067552f42917f172d @@ -638,24 +638,24 @@ F src/parse.y 8e67d820030d2655b9942ffe61c1e7e6b96cea2f2f72183533299393907d0564 F src/pcache.c f4268f7f73c6a3db12ce22fd25bc68dc42315d19599414ab1207d7cf32f79197 F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 F src/pcache1.c dee95e3cd2b61e6512dc814c5ab76d5eb36f0bfc9441dbb4260fccc0d12bbddc -F src/pragma.c 894c2621d35edd4beea9b331cfdb1b42032394420074d2294c8febe548eea8a1 +F src/pragma.c 23e74aaa441a03e6d97098db5883f53ee50cc50d294ecefb916437b8484012b3 F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 F src/prepare.c 9ebd3a1b12bbd1951f0d6db850f32cf5d4547a6ab8bb9e958d75dfbe4e60d0a3 F src/printf.c ff4b05e38bf928ff1b80d3dda4f977b10fe39ecbfe69c018224c7e5594fb2455 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c efea4e5fbecfd6d0a9071b0be0d952620991673391b6ffaaf4c277b0bb674633 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c 83de67e4857be2866d048c98e93f65461d8a0408ca4ce88fec68ebfe030997ae -F src/shell.c.in 8e411527c1b470c16e83be447ad6f8e957af7cf35a92ccce96e73fa286377b6f -F src/sqlite.h.in 0d6a58aaf31e88f309d4ca0e06611182785adaa969ae0ab44db2febcc6045b77 +F src/select.c 146222acbf9ea4e7f99c45a9e3cb4a0ba7e02c4f0df5186094ea1f9ea9907a02 +F src/shell.c.in 007d30baf6aa51e5b7148facab9c5a92ada9e684a2436b70062c7cbca320d1b0 +F src/sqlite.h.in 7be540a872826ab3374534b4dae855faf03b28db78e995e6fe98cb833c3607c4 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 -F src/sqlite3ext.h c4b9fa7a7e2bcdf850cfeb4b8a91d5ec47b7a00033bc996fd2ee96cbf2741f5f -F src/sqliteInt.h 98bc12d3191ff4c70e8491b639784c83d577b7177377d4fb344826ac56bae8a8 +F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 +F src/sqliteInt.h 079ccd9c161f4b74967188fd6321810159fdc4c32371b68559719828fac20f43 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/tclsqlite.c 4e64ba300a5a26e0f1170e09032429faeb65e45e8f3d1a7833e8edb69fc2979e -F src/test1.c 98f4a4525e10b0df164ba7f33950824348bb29fd582101e0f93b1af72f493154 +F src/test1.c 39c13c25f5c4e75a07cb6bd34cfc1b8aa97ecc6f80f08953fa164db7e92ce71e F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef F src/test3.c 61798bb0d38b915067a8c8e03f5a534b431181f802659a6616f9b4ff7d872644 F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 @@ -667,7 +667,7 @@ F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 -F src/test_bestindex.c 8294d8223b7f18a3ddb7f9a0e30815dcca4e61681f78b538c870f7d934f88b81 +F src/test_bestindex.c 1627f782e866a3f4b5ecd01cb46813686bf36612afc9755f26058717fe90b24e F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 F src/test_config.c 8264637b06a3c1f0727c88d1ea32dcf7986b9e7e358a970cae87cdac8a5b2708 @@ -715,15 +715,15 @@ F src/trigger.c 5e68b790f022b8dafbfb0eb244786512a95c9575fc198719d2557d73e5795858 F src/update.c f118e51768d2c1309e3c81e9f91141b22b8a1339cbc5969b1b2d810feaa25b22 F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 -F src/util.c 313f3154e2b85a447326f5dd15de8d31a4df6ab0c3579bd58f426ff634ec9050 +F src/util.c 0e5cf9062a796f0f1b6b3228b121b2344932c05425f7c8b5a7cb245812473bbd F src/vacuum.c 84ce7f01f8a7a08748e107a441db83bcec13970190ddcb0c9ff522adbc1c23fd -F src/vdbe.c b2f2cbaae707685b9860bd54cdd6657076c28e82c725d795ae389c10850a97a6 +F src/vdbe.c 238635c1c40d42d9ded72994b81d4127f99d6d09e9279bdd37f6f34f4025adee F src/vdbe.h 73b904a6b3bb27f308c6cc287a5751ebc7f1f89456be0ed068a12b92844c6e8c F src/vdbeInt.h fc15815b7bdafbb27e7f027faba2b0112e87d382c0d72241672528806ebc0db5 F src/vdbeapi.c 4ee67890913c1d2469c68e3ad2e7ddeab57ac5924a64bbfd0906a8ea0d542c7f F src/vdbeaux.c 9eb7394126b88e55f81321d5e87636e0cd5e5856818d644d8abd6bfbb9c0be9d F src/vdbeblob.c 5e61ce31aca17db8fb60395407457a8c1c7fb471dde405e0cd675974611dcfcd -F src/vdbemem.c 6cfed43758d57b6e3b99d9cdedfeccd86e45a07e427b22d8487cbdbebb6c522a +F src/vdbemem.c 316d518115f3720b4097f0231e2a3d6eefd06c787eccf44972f8d8f462153421 F src/vdbesort.c 43756031ca7430f7aec3ef904824a7883c4ede783e51f280d99b9b65c0796e35 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 F src/vdbevtab.c aae4bd769410eb7e1d02c42613eec961d514459b1c3c1c63cfc84e92a137daac @@ -735,8 +735,8 @@ F src/walker.c f890a3298418d7cba3b69b8803594fdc484ea241206a8dfa99db6dd36f8cbb3b F src/where.c d0d8e3cb2c11e77ba0f8f9ed8eada9d84dbd377167cdcf387b8eeb824c35a3ad F src/whereInt.h e25203e5bfee149f5f1225ae0166cfb4f1e65490c998a024249e98bb0647377c F src/wherecode.c 76bca3379219880d2527493b71a3be49e696f75396d3481e4de5d4ceec7886b2 -F src/whereexpr.c 05295b44b54eea76d1ba766f0908928d0e20e990c249344c9521454d3d09c7ae -F src/window.c 14836767adb26573b50f528eb37f8b1336f2c430ab38de7cead1e5c546bb4d8c +F src/whereexpr.c 7c5671a04b00c876bec5e99fd4e6f688065feb4773160fbf76fd7900d2901777 +F src/window.c 9ea4dc243420e029586c8e1ed5929fad2eae438279341ae9defc66e8f905aabc F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9 @@ -793,7 +793,7 @@ F test/attach2.test 256bd240da1835fb8408dd59fb7ef71f8358c7a756c46662434d11d07ba3 F test/attach3.test c59d92791070c59272e00183b7353eeb94915976 F test/attach4.test 00e754484859998d124d144de6d114d920f2ed6ca2f961e6a7f4183c714f885e F test/attachmalloc.test 67309af95c6b765c13e7d2279d7fccbef78e6eb0565d75d51cefd5dc88784549 -F test/auth.test 0f246deec5cb2f6f893f8fbb76628f182c08fe40f178b254dd72467ca012f657 +F test/auth.test 4fbeaa283637dd06e1bec5bf92dc9c39e27ef83fd20844bdcf1a85c0e6fc160d F test/auth2.test 9eb7fce9f34bf1f50d3f366fb3e606be5a2000a1 F test/auth3.test 76d20a7fa136d63bcfcf8bcb65c0b1455ed71078d81f22bcd0550d3eb18594ab F test/autoanalyze1.test b9cc3f32a990fa56669b668d237c6d53e983554ae80c0604992e18869a0b2dec @@ -828,6 +828,7 @@ F test/bestindex6.test 16942535b551273f3ad9df8d7cc4b7f22b1fcd8882714358859eb049a F test/bestindex7.test f094c669a6400777f4d2ddc3ed28e39169f1adb5be3d59b55f22ccf8c414b71e F test/bestindex8.test 333ad8c6a554b885a49b68c019166eda92b05f493a92b36b0acdf7f766d04dad F test/bestindex9.test bf2eb8556e8d5c00ef3ee18c521751cd03c1b55454b6e7683b4c6742e3131b23 +F test/bestindexA.test dd7b7439a46169b45d0305c4cbbb14fc20c7044acc2055c767d2f838b3479c3f F test/between.test b9a65fb065391980119e8a781a7409d3fcf059d89968279c750e190a9a1d5263 F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -1182,7 +1183,7 @@ F test/genesis.tcl 1e2e2e8e5cc4058549a154ff1892fe5c9de19f98 F test/having.test a89236dd8d55aa50c4805f82ac9daf64d477a44d712d8209c118978d0ca21ec9 F test/hexlit.test 4a6a5f46e3c65c4bf1fa06f5dd5a9507a5627751 F test/hidden.test 23c1393a79e846d68fd902d72c85d5e5dcf98711 -F test/hook.test bb9e03b93fa4bc3bf0f164e455ddaee3603dee0501cb61119baed557c8f936be +F test/hook.test 18cae9140fa7f9a6f346e892a3fe3e31b2ca0be1494cd01b918adb74281016a6 F test/hook2.test b9ff3b8c6519fb67f33192f1afe86e7782ee4ac8 F test/icu.test 716a6b89fbabe5cc63e0cd4c260befb08fd7b9d761f04d43669233292f0753b1 F test/ieee754.test b0945d12be7d255f3dfa18e2511b17ca37e0edd2b803231c52d05b86c04ab26e @@ -1227,10 +1228,10 @@ F test/insertfault.test ac63d14ea3b49c573673a572f4014b9117383a03e497c58f308b5c77 F test/instr.test 107df2b9b74a4b59315916b575590a08f2a714de0754abe541f10a0971d0a2a4 F test/instrfault.test 95e28efade652e6d51ae11b377088fe523a581a07ec428009e152a4dd0e0f44c F test/intarray.test bb976b0b3df0ebb6a2eddfb61768280440e672beba5460ed49679ea984ccf440 -F test/interrupt.test 16ea879ec728cb76414c148c5f24afd5d1f91054 +F test/interrupt.test ac1ef50ec9ab8e4f0e17c47629f82539d4b22558904e321ed5abea2e6187da7a F test/interrupt2.test e4408ca770a6feafbadb0801e54a0dcd1a8d108d F test/intpkey.test aee694afed1a65c86c4e69ad030224b3fc268113d00685234d40079fca16bad3 -F test/intreal.test 2a87e85a5949bd55e41ef04c58f5800587c5380bdbc559ff1c79b614b0e6a533 +F test/intreal.test 68829a8bb073ee1610ca3f8f9e0f99b0371fb36e0fa64862dd5ced4ef03c2343 F test/io.test f138f3fe696d1ed8c51dfea5b325910d319a1b29e1d25ea57231a02092f02cca F test/ioerr.test 530d05801ff1b6327018b2e140da34a74effa2773a844ddb8dc79c32e9567318 F test/ioerr2.test 2593563599e2cc6b6b4fcf5878b177bdd5d8df26 @@ -1398,7 +1399,7 @@ F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff F test/permutations.test 3e0d6eea70e5087f3240b1a2fe621b0c73445f38a262029f0a1d2d89564026f7 F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f -F test/pragma.test 620622fb0815f1cbea8e26e1d8abad38e0cbcbed8927fd84048fe9fd6239e323 +F test/pragma.test a74a9c9642e5d7e32f5a2aa77a2ed64ec5b69fecff39d52c4daf5945a2a4de65 F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f F test/pragma3.test 92a46bbea12322dd94a404f49edcfbfc913a2c98115f0d030a7459bb4712ef31 F test/pragma4.test ca5e4dfc46adfe490f75d73734f70349d95a199e6510973899e502eef2c8b1f8 @@ -1568,7 +1569,7 @@ F test/swarmvtab.test 250231404fcac88f61a6c147bb0e3a118ed879278cd3ccb0ae2d3a729e F test/swarmvtab2.test c948cb2fdfc5b01d85e8f6d6504854202dc1a0782ab2a0ed61538f27cbd0aa5c F test/swarmvtab3.test 41a3ab47cb7a834d4e5336425103b617410a67bb95d335ef536f887587ece073 F test/swarmvtabfault.test 8a67a9f27c61073a47990829e92bc0c64420a807cb642b15a25f6c788210ed95 -F test/symlink.test 72b22238d4405ba34df8e60b335d290a3b1129fd5c260835c944c1e4e77288a9 +F test/symlink.test 4368af0e213dd6e726a6240a16f2bb96a5a58f83f2d5d60652f27547b28cbf06 F test/symlink2.test 9531f475a53d8781c4f81373f87faf2e2aff4f5fb2102ec6386e0c827916a670 F test/sync.test 89539f4973c010eda5638407e71ca7fddbcd8e0594f4c9980229f804d4333092 F test/sync2.test 8f9f7d4f6d5be8ca8941a8dadcc4299e558cb6a1ff653a9469146c7a76ef2039 @@ -1772,7 +1773,7 @@ F test/triggerE.test 612969cb57a4ef792059ad6d01af0117e1ae862c283753ffcc9a6428642 F test/triggerF.test 5d76f0a8c428ff87a4d5ed52da06f6096a2c787a1e21b846111dfac4123de3ad F test/triggerG.test 2b816093c91ba73c733cfa8aedcc210ad819d72a98b1da30768a3c56505233e9 F test/triggerupfrom.test d1f9e56090408115c522bee626cc33a2f3370f627a5e341d832589d72e3aa271 -F test/trustschema1.test 4e970aef0bfe0cee139703cc7209d0e0f07725d999b180ba50770f49edef1494 +F test/trustschema1.test d2996bb284859c99956ac706160eab9f086919da738d19bfef3ac431cce8fd47 F test/tt3_checkpoint.c ac7ca661d739280c89d9c253897df64a59a49369bd1247207ac0f655b622579d F test/tt3_index.c 39eec10a35f57672225be4d182862152896dee4a F test/tt3_lookaside1.c 0377e202c3c2a50d688cb65ba203afeda6fafeb9 @@ -1819,7 +1820,7 @@ F test/vacuum6.test b137b04bf3392d3f5c3b8fda0ce85a6775a70ca112f6559f74ff52dc9ce0 F test/vacuummem.test 4b30f5b95a9ff86e9d5c20741e50a898b2dc10b0962a3211571eb165357003fb F test/varint.test bbce22cda8fc4d135bcc2b589574be8410614e62 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 -F test/view.test a7c463254fd7ce86e861b60ddf489d5c14f22e8052f5ab2f5ff57d3eb17ccdea +F test/view.test d4c4281e1679245829db35597817282f60dc513fc39cc5439078f009bd118487 F test/view2.test db32c8138b5b556f610b35dfddd38c5a58a292f07fda5281eedb0851b2672679 F test/view3.test ad8a8290ee2b55ff6ce66c9ef1ce3f1e47926273a3814e1c425293e128a95456 F test/vt02.c 33ecddc0832d4cd24e9e9fa83d868981b1e049462f4ec9080710353f6479a534 @@ -1959,7 +1960,7 @@ F test/writecrash.test f1da7f7adfe8d7f09ea79b42e5ca6dcc41102f27f8e334ad71539501d F test/zeroblob.test 7b74cefc7b281dfa2b07cd237987fbe94b4a2037a7771e9e83f2d5f608b1d99e F test/zeroblobfault.test 861d8191a0d944dfebb3cb4d2c5b4e46a5a119eaec5a63dd996c2389f8063441 F test/zerodamage.test 9c41628db7e8d9e8a0181e59ea5f189df311a9f6ce99cc376dc461f66db6f8dc -F test/zipfile.test 0d8758d8c0d63f16644f959689f78969d223789d998964276554039f067b4548 +F test/zipfile.test 4178a2de98739e9adac41cb8f0be5553df060e470c846f51fdbed247117728a7 F test/zipfile2.test 9903388a602a3834189857a985106ff95c3bba6a3969e0134127df991889db5d F test/zipfilefault.test 44d4d7a7f7cca7521d569d7f71026b241d65a6b1757aa409c1a168827edbbc2c F tool/GetFile.cs 47852aa0d806fe47ed1ac5138bdce7f000fe87aaa7f28107d0cb1e26682aeb44 @@ -2044,7 +2045,7 @@ F tool/varint.c 5d94cb5003db9dbbcbcc5df08d66f16071aee003 F tool/vdbe-compress.tcl 1dcb7632e57cf57105248029e6e162fddaf6c0fccb3bb9e6215603752c5a2d4a F tool/vdbe_profile.tcl 3ac5a4a9449f4baf77059358ea050db3e34395ccf59c5464d29b91746d5b961e F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 -F tool/warnings.sh d58dc38367cc776550f90327e205d7946802d4004fb9f291fd8b81256bc1eedd +F tool/warnings.sh ab651bb82586c43ff8b560beceac959735bf917b44c5e0f67ba3426e474f29f8 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F vsixtest/App.xaml b76d3b48860e7454775c47ea38ffea9c4abe3e85 F vsixtest/App.xaml.cpp 41158ee43269820136fa3bba00c0bd91b26cc38b650ee392aec2a8d823e54318 @@ -2067,8 +2068,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 52f40ab12e437c59af2b91c7ac105ab7784db57fc8d9ab7a1356f17092681f43 -R 93cdd50555e4edfd8abc358599b58d5a +P 478fe96ee7f3ffd5732231b75e6f04e898a59368b3c91f79e6af9496dfcef6d3 7526c46632578a2b602622b9debc406b52af4a42cc880970c4307d13853d59d3 +R 5ee0a5dc148df08bb72fa9f7b42ab4d1 U stephan -Z 748c24f9d507d828fe21c349ea9e2c67 +Z 38bf5f86327b02eef8312b8f75a4ee2f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 433404363..ffbab4fa1 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -478fe96ee7f3ffd5732231b75e6f04e898a59368b3c91f79e6af9496dfcef6d3
\ No newline at end of file +adc0ede0a43241b85563408d0de8e640a75ec4f7bdfe5e48acc5cdd6b182b88c
\ No newline at end of file diff --git a/src/btree.c b/src/btree.c index d38b7a551..fae572536 100644 --- a/src/btree.c +++ b/src/btree.c @@ -8914,9 +8914,13 @@ static int btreeOverwriteContent( /* ** Overwrite the cell that cursor pCur is pointing to with fresh content -** contained in pX. +** contained in pX. In this variant, pCur is pointing to an overflow +** cell. */ -static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ +static SQLITE_NOINLINE int btreeOverwriteOverflowCell( + BtCursor *pCur, /* Cursor pointing to cell to ovewrite */ + const BtreePayload *pX /* Content to write into the cell */ +){ int iOffset; /* Next byte of pX->pData to write */ int nTotal = pX->nData + pX->nZero; /* Total bytes of to write */ int rc; /* Return code */ @@ -8925,16 +8929,12 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ Pgno ovflPgno; /* Next overflow page to write */ u32 ovflPageSize; /* Size to write on overflow page */ - if( pCur->info.pPayload + pCur->info.nLocal > pPage->aDataEnd - || pCur->info.pPayload < pPage->aData + pPage->cellOffset - ){ - return SQLITE_CORRUPT_BKPT; - } + assert( pCur->info.nLocal<nTotal ); /* pCur is an overflow cell */ + /* Overwrite the local portion first */ rc = btreeOverwriteContent(pPage, pCur->info.pPayload, pX, 0, pCur->info.nLocal); if( rc ) return rc; - if( pCur->info.nLocal==nTotal ) return SQLITE_OK; /* Now overwrite the overflow pages */ iOffset = pCur->info.nLocal; @@ -8964,6 +8964,29 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ return SQLITE_OK; } +/* +** Overwrite the cell that cursor pCur is pointing to with fresh content +** contained in pX. +*/ +static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ + int nTotal = pX->nData + pX->nZero; /* Total bytes of to write */ + MemPage *pPage = pCur->pPage; /* Page being written */ + + if( pCur->info.pPayload + pCur->info.nLocal > pPage->aDataEnd + || pCur->info.pPayload < pPage->aData + pPage->cellOffset + ){ + return SQLITE_CORRUPT_BKPT; + } + if( pCur->info.nLocal==nTotal ){ + /* The entire cell is local */ + return btreeOverwriteContent(pPage, pCur->info.pPayload, pX, + 0, pCur->info.nLocal); + }else{ + /* The cell contains overflow content */ + return btreeOverwriteOverflowCell(pCur, pX); + } +} + /* ** Insert a new record into the BTree. The content of the new record @@ -10159,6 +10182,41 @@ Pager *sqlite3BtreePager(Btree *p){ #ifndef SQLITE_OMIT_INTEGRITY_CHECK /* +** Record an OOM error during integrity_check +*/ +static void checkOom(IntegrityCk *pCheck){ + pCheck->rc = SQLITE_NOMEM; + pCheck->mxErr = 0; /* Causes integrity_check processing to stop */ + if( pCheck->nErr==0 ) pCheck->nErr++; +} + +/* +** Invoke the progress handler, if appropriate. Also check for an +** interrupt. +*/ +static void checkProgress(IntegrityCk *pCheck){ + sqlite3 *db = pCheck->db; + if( AtomicLoad(&db->u1.isInterrupted) ){ + pCheck->rc = SQLITE_INTERRUPT; + pCheck->nErr++; + pCheck->mxErr = 0; + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( db->xProgress ){ + assert( db->nProgressOps>0 ); + pCheck->nStep++; + if( (pCheck->nStep % db->nProgressOps)==0 + && db->xProgress(db->pProgressArg) + ){ + pCheck->rc = SQLITE_INTERRUPT; + pCheck->nErr++; + pCheck->mxErr = 0; + } + } +#endif +} + +/* ** Append a message to the error message string. */ static void checkAppendMsg( @@ -10167,6 +10225,7 @@ static void checkAppendMsg( ... ){ va_list ap; + checkProgress(pCheck); if( !pCheck->mxErr ) return; pCheck->mxErr--; pCheck->nErr++; @@ -10180,7 +10239,7 @@ static void checkAppendMsg( sqlite3_str_vappendf(&pCheck->errMsg, zFormat, ap); va_end(ap); if( pCheck->errMsg.accError==SQLITE_NOMEM ){ - pCheck->bOomFault = 1; + checkOom(pCheck); } } #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ @@ -10222,7 +10281,6 @@ static int checkRef(IntegrityCk *pCheck, Pgno iPage){ checkAppendMsg(pCheck, "2nd reference to page %d", iPage); return 1; } - if( AtomicLoad(&pCheck->db->u1.isInterrupted) ) return 1; setPageReferenced(pCheck, iPage); return 0; } @@ -10245,7 +10303,7 @@ static void checkPtrmap( rc = ptrmapGet(pCheck->pBt, iChild, &ePtrmapType, &iPtrmapParent); if( rc!=SQLITE_OK ){ - if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ) pCheck->bOomFault = 1; + if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ) checkOom(pCheck); checkAppendMsg(pCheck, "Failed to read ptrmap key=%d", iChild); return; } @@ -10429,6 +10487,8 @@ static int checkTreePage( /* Check that the page exists */ + checkProgress(pCheck); + if( pCheck->mxErr==0 ) goto end_of_check; pBt = pCheck->pBt; usableSize = pBt->usableSize; if( iPage==0 ) return 0; @@ -10674,13 +10734,14 @@ end_of_check: ** the unverified btrees. Except, if aRoot[1] is 1, then the freelist ** checks are still performed. */ -char *sqlite3BtreeIntegrityCheck( +int sqlite3BtreeIntegrityCheck( sqlite3 *db, /* Database connection that is running the check */ Btree *p, /* The btree to be checked */ Pgno *aRoot, /* An array of root pages numbers for individual trees */ int nRoot, /* Number of entries in aRoot[] */ int mxErr, /* Stop reporting errors after this many */ - int *pnErr /* Write number of errors seen to this variable */ + int *pnErr, /* OUT: Write number of errors seen to this variable */ + char **pzOut /* OUT: Write the error message string here */ ){ Pgno i; IntegrityCk sCheck; @@ -10703,18 +10764,12 @@ char *sqlite3BtreeIntegrityCheck( assert( p->inTrans>TRANS_NONE && pBt->inTransaction>TRANS_NONE ); VVA_ONLY( nRef = sqlite3PagerRefcount(pBt->pPager) ); assert( nRef>=0 ); + memset(&sCheck, 0, sizeof(sCheck)); sCheck.db = db; sCheck.pBt = pBt; sCheck.pPager = pBt->pPager; sCheck.nPage = btreePagecount(sCheck.pBt); sCheck.mxErr = mxErr; - sCheck.nErr = 0; - sCheck.bOomFault = 0; - sCheck.zPfx = 0; - sCheck.v1 = 0; - sCheck.v2 = 0; - sCheck.aPgRef = 0; - sCheck.heap = 0; sqlite3StrAccumInit(&sCheck.errMsg, 0, zErr, sizeof(zErr), SQLITE_MAX_LENGTH); sCheck.errMsg.printfFlags = SQLITE_PRINTF_INTERNAL; if( sCheck.nPage==0 ){ @@ -10723,12 +10778,12 @@ char *sqlite3BtreeIntegrityCheck( sCheck.aPgRef = sqlite3MallocZero((sCheck.nPage / 8)+ 1); if( !sCheck.aPgRef ){ - sCheck.bOomFault = 1; + checkOom(&sCheck); goto integrity_ck_cleanup; } sCheck.heap = (u32*)sqlite3PageMalloc( pBt->pageSize ); if( sCheck.heap==0 ){ - sCheck.bOomFault = 1; + checkOom(&sCheck); goto integrity_ck_cleanup; } @@ -10809,16 +10864,17 @@ char *sqlite3BtreeIntegrityCheck( integrity_ck_cleanup: sqlite3PageFree(sCheck.heap); sqlite3_free(sCheck.aPgRef); - if( sCheck.bOomFault ){ + *pnErr = sCheck.nErr; + if( sCheck.nErr==0 ){ sqlite3_str_reset(&sCheck.errMsg); - sCheck.nErr++; + *pzOut = 0; + }else{ + *pzOut = sqlite3StrAccumFinish(&sCheck.errMsg); } - *pnErr = sCheck.nErr; - if( sCheck.nErr==0 ) sqlite3_str_reset(&sCheck.errMsg); /* Make sure this analysis did not leave any unref() pages. */ assert( nRef==sqlite3PagerRefcount(pBt->pPager) ); sqlite3BtreeLeave(p); - return sqlite3StrAccumFinish(&sCheck.errMsg); + return sCheck.rc; } #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ diff --git a/src/btree.h b/src/btree.h index c9b9d8017..b9078f901 100644 --- a/src/btree.h +++ b/src/btree.h @@ -329,7 +329,15 @@ const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); u32 sqlite3BtreePayloadSize(BtCursor*); sqlite3_int64 sqlite3BtreeMaxRecordSize(BtCursor*); -char *sqlite3BtreeIntegrityCheck(sqlite3*,Btree*,Pgno*aRoot,int nRoot,int,int*); +int sqlite3BtreeIntegrityCheck( + sqlite3 *db, /* Database connection that is running the check */ + Btree *p, /* The btree to be checked */ + Pgno *aRoot, /* An array of root pages numbers for individual trees */ + int nRoot, /* Number of entries in aRoot[] */ + int mxErr, /* Stop reporting errors after this many */ + int *pnErr, /* OUT: Write number of errors seen to this variable */ + char **pzOut /* OUT: Write the error message string here */ +); struct Pager *sqlite3BtreePager(Btree*); i64 sqlite3BtreeRowCountEst(BtCursor*); diff --git a/src/btreeInt.h b/src/btreeInt.h index af295dd50..79c3296d0 100644 --- a/src/btreeInt.h +++ b/src/btreeInt.h @@ -681,8 +681,8 @@ struct BtCursor { /* -** This structure is passed around through all the sanity checking routines -** in order to keep track of some global state information. +** This structure is passed around through all the PRAGMA integrity_check +** checking routines in order to keep track of some global state information. ** ** The aRef[] array is allocated so that there is 1 bit for each page in ** the database. As the integrity-check proceeds, for each page used in @@ -698,7 +698,8 @@ struct IntegrityCk { Pgno nPage; /* Number of pages in the database */ int mxErr; /* Stop accumulating errors when this reaches zero */ int nErr; /* Number of messages written to zErrMsg so far */ - int bOomFault; /* A memory allocation error has occurred */ + int rc; /* SQLITE_OK, SQLITE_NOMEM, or SQLITE_INTERRUPT */ + u32 nStep; /* Number of steps into the integrity_check process */ const char *zPfx; /* Error message prefix */ Pgno v1; /* Value for first %u substitution in zPfx */ int v2; /* Value for second %d substitution in zPfx */ diff --git a/src/build.c b/src/build.c index 60d59c0c4..0390e321f 100644 --- a/src/build.c +++ b/src/build.c @@ -307,6 +307,7 @@ void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ char saveBuf[PARSE_TAIL_SZ]; if( pParse->nErr ) return; + if( pParse->eParseMode ) return; assert( pParse->nested<10 ); /* Nesting should only be of limited depth */ va_start(ap, zFormat); zSql = sqlite3VMPrintf(db, zFormat, ap); diff --git a/src/hash.c b/src/hash.c index 96f41361b..1f0062a8f 100644 --- a/src/hash.c +++ b/src/hash.c @@ -166,12 +166,13 @@ static HashElem *findElementWithHash( count = pH->count; } if( pHash ) *pHash = h; - while( count-- ){ + while( count ){ assert( elem!=0 ); if( sqlite3StrICmp(elem->pKey,pKey)==0 ){ return elem; } elem = elem->next; + count--; } return &nullElement; } diff --git a/src/loadext.c b/src/loadext.c index c14338f8b..40bde9ce8 100644 --- a/src/loadext.c +++ b/src/loadext.c @@ -510,7 +510,9 @@ static const sqlite3_api_routines sqlite3Apis = { #endif sqlite3_db_name, /* Version 3.40.0 and later */ - sqlite3_value_encoding + sqlite3_value_encoding, + /* Version 3.41.0 and later */ + sqlite3_is_interrupted }; /* True if x is the directory separator character diff --git a/src/main.c b/src/main.c index a2d96ad28..eaecb56df 100644 --- a/src/main.c +++ b/src/main.c @@ -1796,7 +1796,9 @@ int sqlite3_busy_timeout(sqlite3 *db, int ms){ */ void sqlite3_interrupt(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) ){ + if( !sqlite3SafetyCheckOk(db) + && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) + ){ (void)SQLITE_MISUSE_BKPT; return; } @@ -1804,6 +1806,21 @@ void sqlite3_interrupt(sqlite3 *db){ AtomicStore(&db->u1.isInterrupted, 1); } +/* +** Return true or false depending on whether or not an interrupt is +** pending on connection db. +*/ +int sqlite3_is_interrupted(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) + && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) + ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return AtomicLoad(&db->u1.isInterrupted)!=0; +} /* ** This function is exactly the same as sqlite3_create_function(), except @@ -1848,7 +1865,7 @@ int sqlite3CreateFunc( /* The SQLITE_INNOCUOUS flag is the same bit as SQLITE_FUNC_UNSAFE. But ** the meaning is inverted. So flip the bit. */ assert( SQLITE_FUNC_UNSAFE==SQLITE_INNOCUOUS ); - extraFlags ^= SQLITE_FUNC_UNSAFE; + extraFlags ^= SQLITE_FUNC_UNSAFE; /* tag-20230109-1 */ #ifndef SQLITE_OMIT_UTF16 @@ -1866,11 +1883,11 @@ int sqlite3CreateFunc( case SQLITE_ANY: { int rc; rc = sqlite3CreateFunc(db, zFunctionName, nArg, - (SQLITE_UTF8|extraFlags)^SQLITE_FUNC_UNSAFE, + (SQLITE_UTF8|extraFlags)^SQLITE_FUNC_UNSAFE, /* tag-20230109-1 */ pUserData, xSFunc, xStep, xFinal, xValue, xInverse, pDestructor); if( rc==SQLITE_OK ){ rc = sqlite3CreateFunc(db, zFunctionName, nArg, - (SQLITE_UTF16LE|extraFlags)^SQLITE_FUNC_UNSAFE, + (SQLITE_UTF16LE|extraFlags)^SQLITE_FUNC_UNSAFE, /* tag-20230109-1*/ pUserData, xSFunc, xStep, xFinal, xValue, xInverse, pDestructor); } if( rc!=SQLITE_OK ){ diff --git a/src/mem5.c b/src/mem5.c index b61b93e11..02f4c2744 100644 --- a/src/mem5.c +++ b/src/mem5.c @@ -424,9 +424,13 @@ static int memsys5Roundup(int n){ if( n<=mem5.szAtom ) return mem5.szAtom; return mem5.szAtom*2; } - if( n>0x40000000 ) return 0; + if( n>0x10000000 ){ + if( n>0x40000000 ) return 0; + if( n>0x20000000 ) return 0x40000000; + return 0x20000000; + } for(iFullSz=mem5.szAtom*8; iFullSz<n; iFullSz *= 4); - if( (iFullSz/2)>=n ) return iFullSz/2; + if( (iFullSz/2)>=(i64)n ) return iFullSz/2; return iFullSz; } diff --git a/src/os_kv.c b/src/os_kv.c index 45955d18f..5e0ea49f1 100644 --- a/src/os_kv.c +++ b/src/os_kv.c @@ -417,8 +417,7 @@ static int kvvfsDecode(const char *a, char *aOut, int nOut){ if( j+n>nOut ) return -1; memset(&aOut[j], 0, n); j += n; - c = aIn[i]; - if( c==0 ) break; + if( c==0 || mult==1 ) break; /* progress stalled if mult==1 */ }else{ aOut[j] = c<<4; c = kvvfsHexValue[aIn[++i]]; diff --git a/src/os_unix.c b/src/os_unix.c index e5ce36ebf..78ce223d9 100644 --- a/src/os_unix.c +++ b/src/os_unix.c @@ -6506,12 +6506,10 @@ static void appendOnePathElement( if( zName[0]=='.' ){ if( nName==1 ) return; if( zName[1]=='.' && nName==2 ){ - if( pPath->nUsed<=1 ){ - pPath->rc = SQLITE_ERROR; - return; + if( pPath->nUsed>1 ){ + assert( pPath->zOut[0]=='/' ); + while( pPath->zOut[--pPath->nUsed]!='/' ){} } - assert( pPath->zOut[0]=='/' ); - while( pPath->zOut[--pPath->nUsed]!='/' ){} return; } } diff --git a/src/pragma.c b/src/pragma.c index a0aa123fc..527b2a734 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1958,7 +1958,8 @@ 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; + int jmp2, jmp3, jmp4, jmp5, label6; + int kk; int ckUniq = sqlite3VdbeMakeLabel(pParse); if( pPk==pIdx ) continue; r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 0, &jmp3, @@ -1976,13 +1977,32 @@ void sqlite3Pragma( sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); jmp4 = integrityCheckResultRow(v); sqlite3VdbeJumpHere(v, jmp2); + + /* Any indexed columns with non-BINARY collations must still hold + ** the exact same text value as the table. */ + label6 = 0; + for(kk=0; kk<pIdx->nKeyCol; kk++){ + if( pIdx->azColl[kk]==sqlite3StrBINARY ) continue; + if( label6==0 ) label6 = sqlite3VdbeMakeLabel(pParse); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur+j, kk, 3); + sqlite3VdbeAddOp3(v, OP_Ne, 3, label6, r1+kk); VdbeCoverage(v); + } + if( label6 ){ + int jmp6 = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeResolveLabel(v, label6); + sqlite3VdbeLoadString(v, 3, "row "); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + sqlite3VdbeLoadString(v, 4, " values differ from index "); + sqlite3VdbeGoto(v, jmp5-1); + sqlite3VdbeJumpHere(v, jmp6); + } + /* For UNIQUE indexes, verify that only one entry exists with the ** current key. The entry is unique if (1) any column is NULL ** or (2) the next entry has a different key */ if( IsUniqueIndex(pIdx) ){ int uniqOk = sqlite3VdbeMakeLabel(pParse); int jmp6; - int kk; for(kk=0; kk<pIdx->nKeyCol; kk++){ int iCol = pIdx->aiColumn[kk]; assert( iCol!=XN_ROWID && iCol<pTab->nCol ); diff --git a/src/select.c b/src/select.c index eee9e29bb..2288858ea 100644 --- a/src/select.c +++ b/src/select.c @@ -1870,7 +1870,6 @@ static void generateSortTail( #else /* if !defined(SQLITE_ENABLE_COLUMN_METADATA) */ # define columnType(A,B,C,D,E) columnTypeImpl(A,B) #endif -#ifndef SQLITE_OMIT_DECLTYPE static const char *columnTypeImpl( NameContext *pNC, #ifndef SQLITE_ENABLE_COLUMN_METADATA @@ -1901,7 +1900,7 @@ static const char *columnTypeImpl( Table *pTab = 0; /* Table structure column is extracted from */ Select *pS = 0; /* Select the column is extracted from */ int iCol = pExpr->iColumn; /* Index of column in pTab */ - while( ALWAYS(pNC) && !pTab ){ + while( pNC && !pTab ){ SrcList *pTabList = pNC->pSrcList; for(j=0;j<pTabList->nSrc && pTabList->a[j].iCursor!=pExpr->iTable;j++); if( j<pTabList->nSrc ){ @@ -1912,7 +1911,7 @@ static const char *columnTypeImpl( } } - if( NEVER(pTab==0) ){ + if( pTab==0 ){ /* At one time, code such as "SELECT new.x" within a trigger would ** cause this condition to run. Since then, we have restructured how ** trigger code is generated and so this condition is no longer @@ -2017,7 +2016,6 @@ static const char *columnTypeImpl( #endif return zType; } -#endif /* !defined(SQLITE_OMIT_DECLTYPE) */ /* ** Generate code that will tell the VDBE the declaration types of columns @@ -2311,6 +2309,7 @@ void sqlite3SubqueryColumnTypes( int i,j; Expr *p; struct ExprList_item *a; + NameContext sNC; assert( pSelect!=0 ); assert( (pSelect->selFlags & SF_Resolved)!=0 ); @@ -2319,6 +2318,8 @@ void sqlite3SubqueryColumnTypes( if( db->mallocFailed ) return; while( pSelect->pPrior ) pSelect = pSelect->pPrior; a = pSelect->pEList->a; + memset(&sNC, 0, sizeof(sNC)); + sNC.pSrcList = pSelect->pSrc; for(i=0, pCol=pTab->aCol; i<pTab->nCol; i++, pCol++){ const char *zType; i64 n; @@ -2344,28 +2345,31 @@ void sqlite3SubqueryColumnTypes( pCol->affinity = SQLITE_AFF_BLOB; } } - if( pCol->affinity==SQLITE_AFF_NUMERIC - || pCol->affinity==SQLITE_AFF_FLEXNUM - ){ - zType = "NUM"; - }else{ - zType = 0; - for(j=1; j<SQLITE_N_STDTYPE; j++){ - if( sqlite3StdTypeAffinity[j]==pCol->affinity ){ - zType = sqlite3StdType[j]; - break; - } - } - } - if( zType ){ - i64 m = sqlite3Strlen30(zType); - n = sqlite3Strlen30(pCol->zCnName); - pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); - if( pCol->zCnName ){ - memcpy(&pCol->zCnName[n+1], zType, m+1); - pCol->colFlags |= COLFLAG_HASTYPE; + zType = columnType(&sNC, p, 0, 0, 0); + if( zType==0 || pCol->affinity!=sqlite3AffinityType(zType, 0) ){ + if( pCol->affinity==SQLITE_AFF_NUMERIC + || pCol->affinity==SQLITE_AFF_FLEXNUM + ){ + zType = "NUM"; }else{ - testcase( pCol->colFlags & COLFLAG_HASTYPE ); + zType = 0; + for(j=1; j<SQLITE_N_STDTYPE; j++){ + if( sqlite3StdTypeAffinity[j]==pCol->affinity ){ + zType = sqlite3StdType[j]; + break; + } + } + } + } + if( zType ){ + i64 m = sqlite3Strlen30(zType); + n = sqlite3Strlen30(pCol->zCnName); + pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+m+2); + if( pCol->zCnName ){ + memcpy(&pCol->zCnName[n+1], zType, m+1); + pCol->colFlags |= COLFLAG_HASTYPE; + }else{ + testcase( pCol->colFlags & COLFLAG_HASTYPE ); pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); } } diff --git a/src/shell.c.in b/src/shell.c.in index ae9215fda..2c2bd557e 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -166,6 +166,14 @@ typedef unsigned char u8; # define SHELL_USE_LOCAL_GETLINE 1 #endif +#ifndef deliberate_fall_through +/* Quiet some compilers about some of our intentional code. */ +# if GCC_VERSION>=7000000 +# define deliberate_fall_through __attribute__((fallthrough)); +# else +# define deliberate_fall_through +# endif +#endif #if defined(_WIN32) || defined(WIN32) # if SQLITE_OS_WINRT @@ -4681,7 +4689,7 @@ static const char *(azHelp[]) = { #endif ".prompt MAIN CONTINUE Replace the standard prompts", #ifndef SQLITE_SHELL_FIDDLE - ".quit Exit this program", + ".quit Stop interpreting input stream, exit if primary.", ".read FILE Read input from FILE or command output", " If FILE begins with \"|\", it is a command that generates the input.", #endif @@ -6839,7 +6847,7 @@ static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){ break; case AR_SWITCH_APPEND: pAr->bAppend = 1; - /* Fall thru into --file */ + deliberate_fall_through; case AR_SWITCH_FILE: pAr->zFile = zArg; break; @@ -11167,7 +11175,7 @@ static QuickScanState quickscan(char *zLine, QuickScanState qss, break; case '[': cin = ']'; - /* fall thru */ + deliberate_fall_through; case '`': case '\'': case '"': cWait = cin; qss = QSS_HasDark | cWait; @@ -11203,7 +11211,7 @@ static QuickScanState quickscan(char *zLine, QuickScanState qss, ++zLine; continue; } - /* fall thru */ + deliberate_fall_through; case ']': cWait = 0; CONTINUE_PROMPT_AWAITC(pst, 0); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 210a728c5..bf1477ef2 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -174,8 +174,8 @@ extern "C" { ** function is provided for use in DLLs since DLL users usually do not have ** direct access to string constants within the DLL. ^The ** sqlite3_libversion_number() function returns an integer equal to -** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns -** a pointer to a string constant whose value is the same as the +** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns +** a pointer to a string constant whose value is the same as the ** [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built ** using an edited copy of [the amalgamation], then the last four characters ** of the hash might be different from [SQLITE_SOURCE_ID].)^ @@ -190,20 +190,20 @@ int sqlite3_libversion_number(void); /* ** CAPI3REF: Run-Time Library Compilation Options Diagnostics ** -** ^The sqlite3_compileoption_used() function returns 0 or 1 -** indicating whether the specified option was defined at -** compile time. ^The SQLITE_ prefix may be omitted from the -** option name passed to sqlite3_compileoption_used(). +** ^The sqlite3_compileoption_used() function returns 0 or 1 +** indicating whether the specified option was defined at +** compile time. ^The SQLITE_ prefix may be omitted from the +** option name passed to sqlite3_compileoption_used(). ** ** ^The sqlite3_compileoption_get() function allows iterating ** over the list of options that were defined at compile time by ** returning the N-th compile time option string. ^If N is out of range, -** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ -** prefix is omitted from any strings returned by +** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ +** prefix is omitted from any strings returned by ** sqlite3_compileoption_get(). ** ** ^Support for the diagnostic functions sqlite3_compileoption_used() -** and sqlite3_compileoption_get() may be omitted by specifying the +** and sqlite3_compileoption_get() may be omitted by specifying the ** [SQLITE_OMIT_COMPILEOPTION_DIAGS] option at compile time. ** ** See also: SQL functions [sqlite_compileoption_used()] and @@ -227,7 +227,7 @@ const char *sqlite3_compileoption_get(int N); ** SQLite can be compiled with or without mutexes. When ** the [SQLITE_THREADSAFE] C preprocessor macro is 1 or 2, mutexes ** are enabled and SQLite is threadsafe. When the -** [SQLITE_THREADSAFE] macro is 0, +** [SQLITE_THREADSAFE] macro is 0, ** the mutexes are omitted. Without the mutexes, it is not safe ** to use SQLite concurrently from more than one thread. ** @@ -284,14 +284,14 @@ typedef struct sqlite3 sqlite3; ** ** ^The sqlite3_int64 and sqlite_int64 types can store integer values ** between -9223372036854775808 and +9223372036854775807 inclusive. ^The -** sqlite3_uint64 and sqlite_uint64 types can store integer values +** sqlite3_uint64 and sqlite_uint64 types can store integer values ** between 0 and +18446744073709551615 inclusive. */ #ifdef SQLITE_INT64_TYPE typedef SQLITE_INT64_TYPE sqlite_int64; # ifdef SQLITE_UINT64_TYPE typedef SQLITE_UINT64_TYPE sqlite_uint64; -# else +# else typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; # endif #elif defined(_MSC_VER) || defined(__BORLANDC__) @@ -323,7 +323,7 @@ typedef sqlite_uint64 sqlite3_uint64; ** resources are deallocated. ** ** Ideally, applications should [sqlite3_finalize | finalize] all -** [prepared statements], [sqlite3_blob_close | close] all [BLOB handles], and +** [prepared statements], [sqlite3_blob_close | close] all [BLOB handles], and ** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated ** with the [sqlite3] object prior to attempting to close the object. ** ^If the database connection is associated with unfinalized prepared @@ -367,7 +367,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** The sqlite3_exec() interface is a convenience wrapper around ** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()], ** that allows an application to run multiple statements of SQL -** without having to use a lot of C code. +** without having to use a lot of C code. ** ** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded, ** semicolon-separate SQL statements passed into its 2nd argument, @@ -407,7 +407,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** from [sqlite3_column_name()]. ** ** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer -** to an empty string, or a pointer that contains only whitespace and/or +** to an empty string, or a pointer that contains only whitespace and/or ** SQL comments, then no SQL statements are evaluated and the database ** is not changed. ** @@ -766,7 +766,7 @@ struct sqlite3_file { ** requested lock, then the call to xLock() is a no-op. ** xUnlock() downgrades the database file lock to either SHARED or NONE. * If the lock is already at or below the requested lock state, then the call -** to xUnlock() is a no-op. +** to xUnlock() is a no-op. ** The xCheckReservedLock() method checks whether any database connection, ** either in this process or in some other process, is holding a RESERVED, ** PENDING, or EXCLUSIVE lock on the file. It returns true @@ -894,7 +894,7 @@ struct sqlite3_io_methods { ** <li>[[SQLITE_FCNTL_CHUNK_SIZE]] ** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS ** extends and truncates the database file in chunks of a size specified -** by the user. The fourth argument to [sqlite3_file_control()] should +** by the user. The fourth argument to [sqlite3_file_control()] should ** point to an integer (type int) containing the new chunk-size to use ** for the nominated database. Allocating database file space in large ** chunks (say 1MB at a time), may reduce file-system fragmentation and @@ -917,24 +917,24 @@ struct sqlite3_io_methods { ** <li>[[SQLITE_FCNTL_SYNC]] ** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and ** sent to the VFS immediately before the xSync method is invoked on a -** database file descriptor. Or, if the xSync method is not invoked -** because the user has configured SQLite with -** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place +** database file descriptor. Or, if the xSync method is not invoked +** because the user has configured SQLite with +** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place ** of the xSync method. In most cases, the pointer argument passed with ** this file-control is NULL. However, if the database file is being synced ** as part of a multi-database commit, the argument points to a nul-terminated -** string containing the transactions super-journal file name. VFSes that -** do not need this signal should silently ignore this opcode. Applications -** should not call [sqlite3_file_control()] with this opcode as doing so may -** disrupt the operation of the specialized VFSes that do require it. +** string containing the transactions super-journal file name. VFSes that +** do not need this signal should silently ignore this opcode. Applications +** should not call [sqlite3_file_control()] with this opcode as doing so may +** disrupt the operation of the specialized VFSes that do require it. ** ** <li>[[SQLITE_FCNTL_COMMIT_PHASETWO]] ** The [SQLITE_FCNTL_COMMIT_PHASETWO] opcode is generated internally by SQLite ** and sent to the VFS after a transaction has been committed immediately ** but before the database is unlocked. VFSes that do not need this signal ** should silently ignore this opcode. Applications should not call -** [sqlite3_file_control()] with this opcode as doing so may disrupt the -** operation of the specialized VFSes that do require it. +** [sqlite3_file_control()] with this opcode as doing so may disrupt the +** operation of the specialized VFSes that do require it. ** ** <li>[[SQLITE_FCNTL_WIN32_AV_RETRY]] ** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic @@ -1007,7 +1007,7 @@ struct sqlite3_io_methods { ** upper-most shim only. ** ** <li>[[SQLITE_FCNTL_PRAGMA]] -** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] +** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] ** file control is sent to the open [sqlite3_file] object corresponding ** to the database file to which the pragma statement refers. ^The argument ** to the [SQLITE_FCNTL_PRAGMA] file control is an array of @@ -1018,7 +1018,7 @@ struct sqlite3_io_methods { ** of the char** argument point to a string obtained from [sqlite3_mprintf()] ** or the equivalent and that string will become the result of the pragma or ** the error message if the pragma fails. ^If the -** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal +** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal ** [PRAGMA] processing continues. ^If the [SQLITE_FCNTL_PRAGMA] ** file control returns [SQLITE_OK], then the parser assumes that the ** VFS has handled the PRAGMA itself and the parser generates a no-op @@ -1058,7 +1058,7 @@ struct sqlite3_io_methods { ** The argument is a pointer to a value of type sqlite3_int64 that ** is an advisory maximum number of bytes in the file to memory map. The ** pointer is overwritten with the old value. The limit is not changed if -** the value originally pointed to is negative, and so the current limit +** the value originally pointed to is negative, and so the current limit ** can be queried by passing in a pointer to a negative number. This ** file-control is used internally to implement [PRAGMA mmap_size]. ** @@ -1102,7 +1102,7 @@ struct sqlite3_io_methods { ** <li>[[SQLITE_FCNTL_RBU]] ** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by ** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for -** this opcode. +** this opcode. ** ** <li>[[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]] ** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then @@ -1119,7 +1119,7 @@ struct sqlite3_io_methods { ** ** <li>[[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]] ** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write -** operations since the previous successful call to +** operations since the previous successful call to ** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically. ** This file control returns [SQLITE_OK] if and only if the writes were ** all performed successfully and have been committed to persistent storage. @@ -1131,7 +1131,7 @@ struct sqlite3_io_methods { ** ** <li>[[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]] ** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write -** operations since the previous successful call to +** operations since the previous successful call to ** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back. ** ^This file control takes the file descriptor out of batch write mode ** so that all subsequent write operations are independent. @@ -1140,8 +1140,8 @@ struct sqlite3_io_methods { ** ** <li>[[SQLITE_FCNTL_LOCK_TIMEOUT]] ** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode is used to configure a VFS -** to block for up to M milliseconds before failing when attempting to -** obtain a file lock using the xLock or xShmLock methods of the VFS. +** to block for up to M milliseconds before failing when attempting to +** obtain a file lock using the xLock or xShmLock methods of the VFS. ** The parameter is a pointer to a 32-bit signed integer that contains ** the value that M is to be set to. Before returning, the 32-bit signed ** integer is overwritten with the previous value of M. @@ -1345,14 +1345,14 @@ typedef const char *sqlite3_filename; ** the [sqlite3_file] can safely store a pointer to the ** filename if it needs to remember the filename for some reason. ** If the zFilename parameter to xOpen is a NULL pointer then xOpen -** must invent its own temporary name for the file. ^Whenever the +** must invent its own temporary name for the file. ^Whenever the ** xFilename parameter is NULL it will also be the case that the ** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE]. ** ** The flags argument to xOpen() includes all bits set in ** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()] ** or [sqlite3_open16()] is used, then flags includes at least -** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. +** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. ** If xOpen() opens a file read-only then it sets *pOutFlags to ** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set. ** @@ -1394,10 +1394,10 @@ typedef const char *sqlite3_filename; ** ^The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction ** with the [SQLITE_OPEN_CREATE] flag, which are both directly ** analogous to the O_EXCL and O_CREAT flags of the POSIX open() -** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the +** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the ** SQLITE_OPEN_CREATE, is used to indicate that file should always ** be created, and that it is an error if it already exists. -** It is <i>not</i> used to indicate the file should be opened +** It is <i>not</i> used to indicate the file should be opened ** for exclusive access. ** ** ^At least szOsFile bytes of memory are allocated by SQLite @@ -1421,7 +1421,7 @@ typedef const char *sqlite3_filename; ** non-zero error code if there is an I/O error or if the name of ** the file given in the second argument is illegal. If SQLITE_OK ** is returned, then non-zero or zero is written into *pResOut to indicate -** whether or not the file is accessible. +** whether or not the file is accessible. ** ** ^SQLite will always allocate at least mxPathname+1 bytes for the ** output buffer xFullPathname. The exact size of the output buffer @@ -1441,16 +1441,16 @@ typedef const char *sqlite3_filename; ** method returns a Julian Day Number for the current date and time as ** a floating point value. ** ^The xCurrentTimeInt64() method returns, as an integer, the Julian -** Day Number multiplied by 86400000 (the number of milliseconds in -** a 24-hour day). +** Day Number multiplied by 86400000 (the number of milliseconds in +** a 24-hour day). ** ^SQLite will use the xCurrentTimeInt64() method to get the current -** date and time if that method is available (if iVersion is 2 or +** date and time if that method is available (if iVersion is 2 or ** greater and the function pointer is not NULL) and will fall back ** to xCurrentTime() if xCurrentTimeInt64() is unavailable. ** ** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces ** are not used by the SQLite core. These optional interfaces are provided -** by some VFSes to facilitate testing of the VFS code. By overriding +** by some VFSes to facilitate testing of the VFS code. By overriding ** system calls with functions under its control, a test program can ** simulate faults and error conditions that would otherwise be difficult ** or impossible to induce. The set of system calls that can be overridden @@ -1541,7 +1541,7 @@ struct sqlite3_vfs { ** </ul> ** ** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as -** was given on the corresponding lock. +** was given on the corresponding lock. ** ** The xShmLock method can transition between unlocked and SHARED or ** between unlocked and EXCLUSIVE. It cannot transition between SHARED @@ -1704,7 +1704,7 @@ int sqlite3_db_config(sqlite3*, int op, ...); ** This object is used in only one place in the SQLite interface. ** A pointer to an instance of this object is the argument to ** [sqlite3_config()] when the configuration option is -** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. +** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. ** By creating an instance of this object ** and passing it to [sqlite3_config]([SQLITE_CONFIG_MALLOC]) ** during configuration, an application can specify an alternative @@ -1827,7 +1827,7 @@ struct sqlite3_mem_methods { ** SQLITE_CONFIG_SERIALIZED configuration option.</dd> ** ** [[SQLITE_CONFIG_MALLOC]] <dt>SQLITE_CONFIG_MALLOC</dt> -** <dd> ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is +** <dd> ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is ** a pointer to an instance of the [sqlite3_mem_methods] structure. ** The argument specifies ** alternative low-level memory allocation routines to be used in place of @@ -1878,7 +1878,7 @@ struct sqlite3_mem_methods { ** [[SQLITE_CONFIG_PAGECACHE]] <dt>SQLITE_CONFIG_PAGECACHE</dt> ** <dd> ^The SQLITE_CONFIG_PAGECACHE option specifies a memory pool ** that SQLite can use for the database page cache with the default page -** cache implementation. +** cache implementation. ** This configuration option is a no-op if an application-defined page ** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2]. ** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to @@ -1906,7 +1906,7 @@ struct sqlite3_mem_methods { ** additional cache line. </dd> ** ** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt> -** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer +** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer ** that SQLite will use for all of its dynamic memory allocation needs ** beyond those provided for by [SQLITE_CONFIG_PAGECACHE]. ** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled @@ -1961,7 +1961,7 @@ struct sqlite3_mem_methods { ** configuration on individual connections.)^ </dd> ** ** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt> -** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is +** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is ** a pointer to an [sqlite3_pcache_methods2] object. This object specifies ** the interface to a custom page cache implementation.)^ ** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.</dd> @@ -1975,7 +1975,7 @@ struct sqlite3_mem_methods { ** <dd> The SQLITE_CONFIG_LOG option is used to configure the SQLite ** global [error log]. ** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a -** function with a call signature of void(*)(void*,int,const char*), +** function with a call signature of void(*)(void*,int,const char*), ** and a pointer to void. ^If the function pointer is not NULL, it is ** invoked by [sqlite3_log()] to process each logging event. ^If the ** function pointer is NULL, the [sqlite3_log()] interface becomes a no-op. @@ -2084,7 +2084,7 @@ struct sqlite3_mem_methods { ** [[SQLITE_CONFIG_STMTJRNL_SPILL]] ** <dt>SQLITE_CONFIG_STMTJRNL_SPILL ** <dd>^The SQLITE_CONFIG_STMTJRNL_SPILL option takes a single parameter which -** becomes the [statement journal] spill-to-disk threshold. +** becomes the [statement journal] spill-to-disk threshold. ** [Statement journals] are held in memory until their size (in bytes) ** exceeds this threshold, at which point they are written to disk. ** Or if the threshold is -1, statement journals are always held @@ -2106,7 +2106,7 @@ struct sqlite3_mem_methods { ** than the configured sorter-reference size threshold - then a reference ** is stored in each sorted record and the required column values loaded ** from the database as records are returned in sorted order. The default -** value for this option is to never use this optimization. Specifying a +** value for this option is to never use this optimization. Specifying a ** negative value for this option restores the default behaviour. ** This option is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option. @@ -2134,7 +2134,7 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ #define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ #define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ -/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ #define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ #define SQLITE_CONFIG_PCACHE 14 /* no-op */ #define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ @@ -2169,7 +2169,7 @@ struct sqlite3_mem_methods { ** <dl> ** [[SQLITE_DBCONFIG_LOOKASIDE]] ** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt> -** <dd> ^This option takes three additional arguments that determine the +** <dd> ^This option takes three additional arguments that determine the ** [lookaside memory allocator] configuration for the [database connection]. ** ^The first argument (the third parameter to [sqlite3_db_config()] is a ** pointer to a memory buffer to use for lookaside memory. @@ -2187,7 +2187,7 @@ struct sqlite3_mem_methods { ** when the "current value" returned by ** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero. ** Any attempt to change the lookaside memory configuration when lookaside -** memory is in use leaves the configuration unchanged and returns +** memory is in use leaves the configuration unchanged and returns ** [SQLITE_BUSY].)^</dd> ** ** [[SQLITE_DBCONFIG_ENABLE_FKEY]] @@ -2679,8 +2679,12 @@ sqlite3_int64 sqlite3_total_changes64(sqlite3*); ** ^A call to sqlite3_interrupt(D) that occurs when there are no running ** SQL statements is a no-op and has no effect on SQL statements ** that are started after the sqlite3_interrupt() call returns. +** +** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether +** or not an interrupt is currently in effect for [database connection] D. */ void sqlite3_interrupt(sqlite3*); +int sqlite3_is_interrupted(sqlite3*); /* ** CAPI3REF: Determine If An SQL Statement Is Complete @@ -3288,7 +3292,7 @@ SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, ** execution of the prepared statement, such as at the start of each ** trigger subprogram. ^The P argument is a pointer to the ** [prepared statement]. ^The X argument is a pointer to a string which -** is the unexpanded SQL text of the prepared statement or an SQL comment +** is the unexpanded SQL text of the prepared statement or an SQL comment ** that indicates the invocation of a trigger. ^The callback can compute ** the same text that would have been returned by the legacy [sqlite3_trace()] ** interface by using the X argument when X begins with "--" and invoking @@ -3298,13 +3302,13 @@ SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, ** <dd>^An SQLITE_TRACE_PROFILE callback provides approximately the same ** information as is provided by the [sqlite3_profile()] callback. ** ^The P argument is a pointer to the [prepared statement] and the -** X argument points to a 64-bit integer which is the estimated of -** the number of nanosecond that the prepared statement took to run. +** X argument points to a 64-bit integer which is approximately +** the number of nanoseconds that the prepared statement took to run. ** ^The SQLITE_TRACE_PROFILE callback is invoked when the statement finishes. ** ** [[SQLITE_TRACE_ROW]] <dt>SQLITE_TRACE_ROW</dt> ** <dd>^An SQLITE_TRACE_ROW callback is invoked whenever a prepared -** statement generates a single row of result. +** statement generates a single row of result. ** ^The P argument is a pointer to the [prepared statement] and the ** X argument is unused. ** @@ -3394,7 +3398,7 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** CAPI3REF: Opening A New Database Connection ** CONSTRUCTOR: sqlite3 ** -** ^These routines open an SQLite database file as specified by the +** ^These routines open an SQLite database file as specified by the ** filename argument. ^The filename argument is interpreted as UTF-8 for ** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte ** order for sqlite3_open16(). ^(A [database connection] handle is usually @@ -3423,13 +3427,18 @@ void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** ** <dl> ** ^(<dt>[SQLITE_OPEN_READONLY]</dt> -** <dd>The database is opened in read-only mode. If the database does not -** already exist, an error is returned.</dd>)^ +** <dd>The database is opened in read-only mode. If the database does +** not already exist, an error is returned.</dd>)^ ** ** ^(<dt>[SQLITE_OPEN_READWRITE]</dt> -** <dd>The database is opened for reading and writing if possible, or reading -** only if the file is write protected by the operating system. In either -** case the database must already exist, otherwise an error is returned.</dd>)^ +** <dd>The database is opened for reading and writing if possible, or +** reading only if the file is write protected by the operating +** system. In either case the database must already exist, otherwise +** an error is returned. For historical reasons, if opening in +** read-write mode fails due to OS-level permissions, an attempt is +** made to open it in read-only mode. [sqlite3_db_readonly()] can be +** used to determine whether the database is actually +** read-write.</dd>)^ ** ** ^(<dt>[SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]</dt> ** <dd>The database is opened for reading and writing, and is created if @@ -5410,10 +5419,21 @@ int sqlite3_create_window_function( ** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in ** schema structures such as [CHECK constraints], [DEFAULT clauses], ** [expression indexes], [partial indexes], or [generated columns]. -** The SQLITE_DIRECTONLY flags is a security feature which is recommended -** for all [application-defined SQL functions], and especially for functions -** that have side-effects or that could potentially leak sensitive -** information. +** <p> +** The SQLITE_DIRECTONLY flag is recommended for any +** [application-defined SQL function] +** that has side-effects or that could potentially leak sensitive information. +** This will prevent attacks in which an application is tricked +** into using a database file that has had its schema surreptiously +** modified to invoke the application-defined function in ways that are +** harmful. +** <p> +** Some people say it is good practice to set SQLITE_DIRECTONLY on all +** [application-defined SQL functions], regardless of whether or not they +** are security sensitive, as doing so prevents those functions from being used +** inside of the database schema, and thus ensures that the database +** can be inspected and modified using generic tools (such as the [CLI]) +** that do not have access to the application-defined functions. ** </dd> ** ** [[SQLITE_INNOCUOUS]] <dt>SQLITE_INNOCUOUS</dt><dd> @@ -10097,6 +10117,10 @@ int sqlite3_db_cacheflush(sqlite3*); ** function is not defined for operations on WITHOUT ROWID tables, or for ** DELETE operations on rowid tables. ** +** ^The sqlite3_update_hook(D,C,P) function returns the P argument from +** the previous call on the same [database connection] D, or NULL for +** the first call on D. +** ** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()], ** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces ** provide additional information about a preupdate event. These routines diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h index 79702d7b2..19e030028 100644 --- a/src/sqlite3ext.h +++ b/src/sqlite3ext.h @@ -359,6 +359,8 @@ struct sqlite3_api_routines { const char *(*db_name)(sqlite3*,int); /* Version 3.40.0 and later */ int (*value_encoding)(sqlite3_value*); + /* Version 3.41.0 and later */ + int (*is_interrupted)(sqlite3*); }; /* @@ -685,6 +687,8 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_db_name sqlite3_api->db_name /* Version 3.40.0 and later */ #define sqlite3_value_encoding sqlite3_api->value_encoding +/* Version 3.41.0 and later */ +#define sqlite3_is_interrupted sqlite3_api->is_interrupted #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 838c6e7b5..09e817406 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1929,8 +1929,14 @@ struct FuncDestructor { ** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG ** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API ** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API -** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS +** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS -- opposite meanings!!! ** SQLITE_FUNC_ENCMASK depends on SQLITE_UTF* macros in the API +** +** Note that even though SQLITE_FUNC_UNSAFE and SQLITE_INNOCUOUS have the +** same bit value, their meanings are inverted. SQLITE_FUNC_UNSAFE is +** used internally and if set means tha the function has side effects. +** SQLITE_INNOCUOUS is used by application code and means "not unsafe". +** See multiple instances of tag-20230109-1. */ #define SQLITE_FUNC_ENCMASK 0x0003 /* SQLITE_UTF8, SQLITE_UTF16BE or UTF16LE */ #define SQLITE_FUNC_LIKE 0x0004 /* Candidate for the LIKE optimization */ @@ -2047,7 +2053,7 @@ struct FuncDestructor { {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } #define JFUNCTION(zName, nArg, iArg, xFunc) \ - {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|\ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|\ SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define INLINE_FUNC(zName, nArg, iArg, mFlags) \ @@ -4496,13 +4502,11 @@ int sqlite3HeapNearlyFull(void); #ifdef SQLITE_USE_ALLOCA # define sqlite3StackAllocRaw(D,N) alloca(N) # define sqlite3StackAllocRawNN(D,N) alloca(N) -# define sqlite3StackAllocZero(D,N) memset(alloca(N), 0, N) # define sqlite3StackFree(D,P) # define sqlite3StackFreeNN(D,P) #else # define sqlite3StackAllocRaw(D,N) sqlite3DbMallocRaw(D,N) # define sqlite3StackAllocRawNN(D,N) sqlite3DbMallocRawNN(D,N) -# define sqlite3StackAllocZero(D,N) sqlite3DbMallocZero(D,N) # define sqlite3StackFree(D,P) sqlite3DbFree(D,P) # define sqlite3StackFreeNN(D,P) sqlite3DbFreeNN(D,P) #endif @@ -5004,7 +5008,7 @@ int sqlite3FixExpr(DbFixer*, Expr*); int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); int sqlite3RealSameAsInt(double,sqlite3_int64); i64 sqlite3RealToI64(double); -void sqlite3Int64ToText(i64,char*); +int sqlite3Int64ToText(i64,char*); int sqlite3AtoF(const char *z, double*, int, u8); int sqlite3GetInt32(const char *, int*); int sqlite3GetUInt32(const char*, u32*); diff --git a/src/test1.c b/src/test1.c index a314e90d8..dda77a90a 100644 --- a/src/test1.c +++ b/src/test1.c @@ -5537,7 +5537,6 @@ static int SQLITE_TCLAPI test_stmt_int( return TCL_OK; } - /* ** Usage: sqlite3_interrupt DB ** @@ -5560,6 +5559,29 @@ static int SQLITE_TCLAPI test_interrupt( } /* +** Usage: sqlite3_is_interrupted DB +** +** return true if an interrupt is current in effect on DB +*/ +static int SQLITE_TCLAPI test_is_interrupted( + void * clientData, + Tcl_Interp *interp, + int argc, + char **argv +){ + sqlite3 *db; + int rc; + if( argc!=2 ){ + Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " DB", 0); + return TCL_ERROR; + } + if( getDbPointer(interp, argv[1], &db) ) return TCL_ERROR; + rc = sqlite3_is_interrupted(db); + Tcl_AppendResult(interp, rc ? "1" : "0", (void*)0); + return TCL_OK; +} + +/* ** Usage: sqlite_delete_function DB function-name ** ** Delete the user function 'function-name' from database handle DB. It @@ -8631,6 +8653,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "sqlite3_key", (Tcl_CmdProc*)test_key }, { "sqlite3_rekey", (Tcl_CmdProc*)test_rekey }, { "sqlite3_interrupt", (Tcl_CmdProc*)test_interrupt }, + { "sqlite3_is_interrupted", (Tcl_CmdProc*)test_is_interrupted }, { "sqlite_delete_function", (Tcl_CmdProc*)delete_function }, { "sqlite_delete_collation", (Tcl_CmdProc*)delete_collation }, { "sqlite3_get_autocommit", (Tcl_CmdProc*)get_autocommit }, diff --git a/src/test_bestindex.c b/src/test_bestindex.c index 67a0c8258..65f063e50 100644 --- a/src/test_bestindex.c +++ b/src/test_bestindex.c @@ -101,8 +101,10 @@ #ifndef SQLITE_OMIT_VIRTUALTABLE + typedef struct tcl_vtab tcl_vtab; typedef struct tcl_cursor tcl_cursor; +typedef struct TestFindFunction TestFindFunction; /* ** A fs virtual-table object @@ -111,6 +113,7 @@ struct tcl_vtab { sqlite3_vtab base; Tcl_Interp *interp; Tcl_Obj *pCmd; + TestFindFunction *pFindFunctionList; sqlite3 *db; }; @@ -120,6 +123,13 @@ struct tcl_cursor { sqlite3_stmt *pStmt; /* Read data from here */ }; +struct TestFindFunction { + tcl_vtab *pTab; + const char *zName; + TestFindFunction *pNext; +}; + + /* ** Dequote string z in place. */ @@ -223,6 +233,11 @@ static int tclConnect( /* The xDisconnect and xDestroy methods are also the same */ static int tclDisconnect(sqlite3_vtab *pVtab){ tcl_vtab *pTab = (tcl_vtab*)pVtab; + while( pTab->pFindFunctionList ){ + TestFindFunction *p = pTab->pFindFunctionList; + pTab->pFindFunctionList = p->pNext; + sqlite3_free(p); + } Tcl_DecrRefCount(pTab->pCmd); sqlite3_free(pTab); return SQLITE_OK; @@ -398,7 +413,7 @@ static void testBestIndexObjConstraints( for(ii=0; ii<pIdxInfo->nConstraint; ii++){ struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[ii]; Tcl_Obj *pElem = Tcl_NewObj(); - const char *zOp = "?"; + const char *zOp = 0; Tcl_IncrRefCount(pElem); @@ -438,7 +453,11 @@ static void testBestIndexObjConstraints( } Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("op", -1)); - Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj(zOp, -1)); + if( zOp ){ + Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj(zOp, -1)); + }else{ + Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pCons->op)); + } Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("column", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pCons->iColumn)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("usable", -1)); @@ -693,6 +712,83 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ return rc; } +static void tclFunction(sqlite3_context *pCtx, int nArg, sqlite3_value **apArg){ + TestFindFunction *p = (TestFindFunction*)sqlite3_user_data(pCtx); + Tcl_Interp *interp = p->pTab->interp; + Tcl_Obj *pScript = 0; + Tcl_Obj *pRet = 0; + int ii; + + pScript = Tcl_DuplicateObj(p->pTab->pCmd); + Tcl_IncrRefCount(pScript); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("function", -1)); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(p->zName, -1)); + + for(ii=0; ii<nArg; ii++){ + const char *zArg = (const char*)sqlite3_value_text(apArg[ii]); + Tcl_ListObjAppendElement(interp, pScript, + (zArg ? Tcl_NewStringObj(zArg, -1) : Tcl_NewObj()) + ); + } + Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(pScript); + + pRet = Tcl_GetObjResult(interp); + sqlite3_result_text(pCtx, Tcl_GetString(pRet), -1, SQLITE_TRANSIENT); +} + +static int tclFindFunction( + sqlite3_vtab *tab, + int nArg, + const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), /* OUT */ + void **ppArg /* OUT */ +){ + int iRet = 0; + tcl_vtab *pTab = (tcl_vtab*)tab; + Tcl_Interp *interp = pTab->interp; + Tcl_Obj *pScript = 0; + int rc = SQLITE_OK; + + pScript = Tcl_DuplicateObj(pTab->pCmd); + Tcl_IncrRefCount(pScript); + Tcl_ListObjAppendElement( + interp, pScript, Tcl_NewStringObj("xFindFunction", -1) + ); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewIntObj(nArg)); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(zName, -1)); + rc = Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(pScript); + + if( rc==SQLITE_OK ){ + Tcl_Obj *pObj = Tcl_GetObjResult(interp); + + if( Tcl_GetIntFromObj(interp, pObj, &iRet) ){ + rc = SQLITE_ERROR; + }else if( iRet>0 ){ + int nName = strlen(zName); + int nByte = nName + 1 + sizeof(TestFindFunction); + TestFindFunction *pNew = 0; + + pNew = (TestFindFunction*)sqlite3_malloc(nByte); + if( pNew==0 ){ + iRet = 0; + }else{ + memset(pNew, 0, nByte); + pNew->zName = (const char*)&pNew[1]; + memcpy((char*)pNew->zName, zName, nName); + pNew->pTab = pTab; + pNew->pNext = pTab->pFindFunctionList; + pTab->pFindFunctionList = pNew; + *ppArg = (void*)pNew; + *pxFunc = tclFunction; + } + } + } + + return iRet; +} + /* ** A virtual table module that provides read-only access to a ** Tcl global variable namespace. @@ -716,7 +812,7 @@ static sqlite3_module tclModule = { 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ - 0, /* xFindMethod */ + tclFindFunction, /* xFindFunction */ 0, /* xRename */ }; diff --git a/src/util.c b/src/util.c index 72faafea5..23c6b1a66 100644 --- a/src/util.c +++ b/src/util.c @@ -632,11 +632,14 @@ do_atof_calc: #endif /* -** Render an signed 64-bit integer as text. Store the result in zOut[]. +** Render an signed 64-bit integer as text. Store the result in zOut[] and +** return the length of the string that was stored, in bytes. The value +** returned does not include the zero terminator at the end of the output +** string. ** ** The caller must ensure that zOut[] is at least 21 bytes in size. */ -void sqlite3Int64ToText(i64 v, char *zOut){ +int sqlite3Int64ToText(i64 v, char *zOut){ int i; u64 x; char zTemp[22]; @@ -653,6 +656,7 @@ void sqlite3Int64ToText(i64 v, char *zOut){ }while( x ); if( v<0 ) zTemp[i--] = '-'; memcpy(zOut, &zTemp[i+1], sizeof(zTemp)-1-i); + return sizeof(zTemp)-2-i; } /* diff --git a/src/vdbe.c b/src/vdbe.c index 7705b68ec..92dc1e1ed 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -394,7 +394,7 @@ static void applyAffinity( assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL || affinity==SQLITE_AFF_NUMERIC || affinity==SQLITE_AFF_FLEXNUM ); if( (pRec->flags & MEM_Int)==0 ){ /*OPTIMIZATION-IF-FALSE*/ - if( (pRec->flags & MEM_Real)==0 ){ + if( (pRec->flags & (MEM_Real|MEM_IntReal))==0 ){ if( pRec->flags & MEM_Str ) applyNumericAffinity(pRec,1); }else if( affinity<=SQLITE_AFF_REAL ){ sqlite3VdbeIntegerAffinity(pRec); @@ -1150,6 +1150,12 @@ case OP_Halt: { #ifdef SQLITE_DEBUG if( pOp->p2==OE_Abort ){ sqlite3VdbeAssertAbortable(p); } #endif + + /* A deliberately coded "OP_Halt SQLITE_INTERNAL * * * *" opcode indicates + ** something is wrong with the code generator. Raise and assertion in order + ** to bring this to the attention of fuzzers and other testing tools. */ + assert( pOp->p1!=SQLITE_INTERNAL ); + if( p->pFrame && pOp->p1==SQLITE_OK ){ /* Halt the sub-program. Return control to the parent frame. */ pFrame = p->pFrame; @@ -3142,7 +3148,7 @@ case OP_TypeCheck: { } case COLTYPE_REAL: { testcase( (pIn1->flags & (MEM_Real|MEM_IntReal))==MEM_Real ); - testcase( (pIn1->flags & (MEM_Real|MEM_IntReal))==MEM_IntReal ); + assert( (pIn1->flags & MEM_IntReal)==0 ); if( pIn1->flags & MEM_Int ){ /* When applying REAL affinity, if the result is still an MEM_Int ** that will fit in 6 bytes, then change the type to MEM_IntReal @@ -6120,6 +6126,9 @@ case OP_Sort: { /* jump */ ** If the table or index is not empty, fall through to the following ** instruction. ** +** If P2 is zero, that is an assertion that the P1 table is never +** empty and hence the jump will never be taken. +** ** This opcode leaves the cursor configured to move in forward order, ** from the beginning toward the end. In other words, the cursor is ** configured to use Next, not Prev. @@ -6131,6 +6140,8 @@ case OP_Rewind: { /* jump, ncycle */ assert( pOp->p1>=0 && pOp->p1<p->nCursor ); assert( pOp->p5==0 ); + assert( pOp->p2>=0 && pOp->p2<p->nOp ); + pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( isSorter(pC)==(pOp->opcode==OP_SorterSort) ); @@ -6150,9 +6161,10 @@ case OP_Rewind: { /* jump, ncycle */ } if( rc ) goto abort_due_to_error; pC->nullRow = (u8)res; - assert( pOp->p2>0 && pOp->p2<p->nOp ); - VdbeBranchTaken(res!=0,2); - if( res ) goto jump_to_p2; + if( pOp->p2>0 ){ + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + } break; } @@ -6958,13 +6970,14 @@ case OP_IntegrityCk: { pIn1 = &aMem[pOp->p1]; assert( pOp->p5<db->nDb ); assert( DbMaskTest(p->btreeMask, pOp->p5) ); - z = sqlite3BtreeIntegrityCheck(db, db->aDb[pOp->p5].pBt, &aRoot[1], nRoot, - (int)pnErr->u.i+1, &nErr); + rc = sqlite3BtreeIntegrityCheck(db, db->aDb[pOp->p5].pBt, &aRoot[1], nRoot, + (int)pnErr->u.i+1, &nErr, &z); sqlite3VdbeMemSetNull(pIn1); if( nErr==0 ){ assert( z==0 ); - }else if( z==0 ){ - goto no_mem; + }else if( rc ){ + sqlite3_free(z); + goto abort_due_to_error; }else{ pnErr->u.i -= nErr-1; sqlite3VdbeMemSetStr(pIn1, z, -1, SQLITE_UTF8, sqlite3_free); diff --git a/src/vdbemem.c b/src/vdbemem.c index f14599def..d9909decc 100644 --- a/src/vdbemem.c +++ b/src/vdbemem.c @@ -114,9 +114,9 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ i64 x; assert( (p->flags&MEM_Int)*2==sizeof(x) ); memcpy(&x, (char*)&p->u, (p->flags&MEM_Int)*2); - sqlite3Int64ToText(x, zBuf); + p->n = sqlite3Int64ToText(x, zBuf); #else - sqlite3Int64ToText(p->u.i, zBuf); + p->n = sqlite3Int64ToText(p->u.i, zBuf); #endif }else{ sqlite3StrAccumInit(&acc, 0, zBuf, sz, 0); @@ -124,6 +124,7 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ (p->flags & MEM_IntReal)!=0 ? (double)p->u.i : p->u.r); assert( acc.zText==zBuf && acc.mxAlloc<=0 ); zBuf[acc.nChar] = 0; /* Fast version of sqlite3StrAccumFinish(&acc) */ + p->n = acc.nChar; } } @@ -151,6 +152,7 @@ static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ ** This routine is for use inside of assert() statements only. */ int sqlite3VdbeMemValidStrRep(Mem *p){ + Mem tmp; char zBuf[100]; char *z; int i, j, incr; @@ -167,7 +169,8 @@ int sqlite3VdbeMemValidStrRep(Mem *p){ assert( p->enc==SQLITE_UTF8 || p->z[((p->n+1)&~1)+1]==0 ); } if( (p->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ) return 1; - vdbeMemRenderNum(sizeof(zBuf), zBuf, p); + memcpy(&tmp, p, sizeof(tmp)); + vdbeMemRenderNum(sizeof(zBuf), zBuf, &tmp); z = p->z; i = j = 0; incr = 1; @@ -436,7 +439,7 @@ int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ vdbeMemRenderNum(nByte, pMem->z, pMem); assert( pMem->z!=0 ); - pMem->n = sqlite3Strlen30NN(pMem->z); + assert( pMem->n==sqlite3Strlen30NN(pMem->z) ); pMem->enc = SQLITE_UTF8; pMem->flags |= MEM_Str|MEM_Term; if( bForce ) pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); @@ -676,32 +679,35 @@ int sqlite3VdbeBooleanValue(Mem *pMem, int ifNull){ } /* -** The MEM structure is already a MEM_Real. Try to also make it a -** MEM_Int if we can. +** The MEM structure is already a MEM_Real or MEM_IntReal. Try to +** make it a MEM_Int if we can. */ void sqlite3VdbeIntegerAffinity(Mem *pMem){ - i64 ix; assert( pMem!=0 ); - assert( pMem->flags & MEM_Real ); + assert( pMem->flags & (MEM_Real|MEM_IntReal) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); - ix = doubleToInt64(pMem->u.r); - - /* Only mark the value as an integer if - ** - ** (1) the round-trip conversion real->int->real is a no-op, and - ** (2) The integer is neither the largest nor the smallest - ** possible integer (ticket #3922) - ** - ** The second and third terms in the following conditional enforces - ** the second condition under the assumption that addition overflow causes - ** values to wrap around. - */ - if( pMem->u.r==ix && ix>SMALLEST_INT64 && ix<LARGEST_INT64 ){ - pMem->u.i = ix; + if( pMem->flags & MEM_IntReal ){ MemSetTypeFlag(pMem, MEM_Int); + }else{ + i64 ix = doubleToInt64(pMem->u.r); + + /* Only mark the value as an integer if + ** + ** (1) the round-trip conversion real->int->real is a no-op, and + ** (2) The integer is neither the largest nor the smallest + ** possible integer (ticket #3922) + ** + ** The second and third terms in the following conditional enforces + ** the second condition under the assumption that addition overflow causes + ** values to wrap around. + */ + if( pMem->u.r==ix && ix>SMALLEST_INT64 && ix<LARGEST_INT64 ){ + pMem->u.i = ix; + MemSetTypeFlag(pMem, MEM_Int); + } } } diff --git a/src/whereexpr.c b/src/whereexpr.c index 24246c1aa..b466c2da2 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -1620,6 +1620,13 @@ void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ assert( pWC->a[ii].eOperator==WO_ROWVAL ); continue; } + if( pWC->a[ii].nChild ){ + /* If this term has child terms, then they are also part of the + ** pWC->a[] array. So this term can be ignored, as a LIMIT clause + ** will only be added if each of the child terms passes the + ** (leftCursor==iCsr) test below. */ + continue; + } if( pWC->a[ii].leftCursor!=iCsr ) return; } diff --git a/src/window.c b/src/window.c index 1ed3e4921..8dd35ee30 100644 --- a/src/window.c +++ b/src/window.c @@ -2944,8 +2944,7 @@ void sqlite3WindowCodeStep( VdbeCoverageNeverNullIf(v, op==OP_Ge); /* NeverNull because bound <expr> */ VdbeCoverageNeverNullIf(v, op==OP_Le); /* values previously checked */ windowAggFinal(&s, 0); - sqlite3VdbeAddOp2(v, OP_Rewind, s.current.csr, 1); - VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp1(v, OP_Rewind, s.current.csr); windowReturnOneRow(&s); sqlite3VdbeAddOp1(v, OP_ResetSorter, s.current.csr); sqlite3VdbeAddOp2(v, OP_Goto, 0, lblWhereEnd); @@ -2957,13 +2956,10 @@ void sqlite3WindowCodeStep( } if( pMWin->eStart!=TK_UNBOUNDED ){ - sqlite3VdbeAddOp2(v, OP_Rewind, s.start.csr, 1); - VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp1(v, OP_Rewind, s.start.csr); } - sqlite3VdbeAddOp2(v, OP_Rewind, s.current.csr, 1); - VdbeCoverageNeverTaken(v); - sqlite3VdbeAddOp2(v, OP_Rewind, s.end.csr, 1); - VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp1(v, OP_Rewind, s.current.csr); + sqlite3VdbeAddOp1(v, OP_Rewind, s.end.csr); if( regPeer && pOrderBy ){ sqlite3VdbeAddOp3(v, OP_Copy, regNewPeer, regPeer, pOrderBy->nExpr-1); sqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.start.reg, pOrderBy->nExpr-1); diff --git a/test/auth.test b/test/auth.test index d8afa2dbf..7df9ad373 100644 --- a/test/auth.test +++ b/test/auth.test @@ -2246,6 +2246,19 @@ ifcapable altertable&&vtab { } {main t1 {} {}} } +# 2022-12-28 +# The sqlite3_declare_vtab() call that occurs during pragma_table_list +# should not cause an authentication failure. +# +do_test auth-1.359 { + proc auth {code arg1 arg2 arg3 arg4 args} { + if {$code=="SQLITE_UPDATE"} { + return SQLITE_DENY + } + return SQLITE_OK + } + catchsql {SELECT * FROM pragma_table_list WHERE name='xyzzy';} +} {0 {}} do_test auth-2.1 { proc auth {code arg1 arg2 arg3 arg4 args} { diff --git a/test/bestindexA.test b/test/bestindexA.test new file mode 100644 index 000000000..650404eaa --- /dev/null +++ b/test/bestindexA.test @@ -0,0 +1,138 @@ +# 2020-01-29 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix bestindexA + +ifcapable !vtab { + finish_test + return +} + + +proc vtab_command {method args} { + switch -- $method { + xConnect { + return "CREATE TABLE x(a, b, c)" + } + + xBestIndex { + set hdl [lindex $args 0] + set clist [$hdl constraints] + foreach c $clist { + array set C $c + lappend ::vtab_constraints [list $C(op) $C(column)] + } + return [list] + } + + xFilter { + return "" + } + + xFindFunction { + foreach {nArg name} $args {} + if {$nArg==2 && $name=="even"} { + return 152 + } + return 0 + } + + } + + return {} +} + +register_tcl_module db +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING tcl(vtab_command); +} + +proc do_xbestindex_test {tn sql res} { + set script [subst { + execsql "$sql" + set ::vtab_constraints + }] + + uplevel [list do_test $tn $script [list {*}$res]] + set ::vtab_constraints [list] +} + +do_xbestindex_test 1.1 { + SELECT * FROM t1 WHERE a=? +} { + {eq 0} +} + +do_xbestindex_test 1.2 { + SELECT * FROM t1 WHERE a=? LIMIT 10 +} { + {eq 0} + {limit 0} +} + +do_xbestindex_test 1.3 { + SELECT * FROM t1 WHERE a=? AND (b+1)=? LIMIT 10 +} { + {eq 0} +} + +proc error_function {args} { error "not a function!" } +db function even error_function + +do_xbestindex_test 1.4 { + SELECT * FROM t1 WHERE even(a, ?) +} { + {152 0} +} + +do_xbestindex_test 1.5 { + SELECT * FROM t1 WHERE b=10 AND even(a, ?) +} { + {eq 1} + {152 0} +} + +do_xbestindex_test 1.6 { + SELECT * FROM t1 WHERE b=10 LIMIT 10 +} { + {eq 1} + {limit 0} +} + +do_xbestindex_test 1.7 { + SELECT * FROM t1 WHERE even(b,?) LIMIT 10 +} { + {152 1} + {limit 0} +} + +do_xbestindex_test 1.8 { + SELECT * FROM t1 WHERE b!=? LIMIT 10 +} { + {ne 1} + {limit 0} +} + +do_xbestindex_test 1.9 { + SELECT * FROM t1 WHERE ?=a LIMIT 10 +} { + {eq 0} + {limit 0} +} + + +finish_test + + + diff --git a/test/hook.test b/test/hook.test index bb868df8b..3d735875d 100644 --- a/test/hook.test +++ b/test/hook.test @@ -16,6 +16,7 @@ # sqlite_commit_hook (tests hook-1..hook-3 inclusive) # sqlite_update_hook (tests hook-4-*) # sqlite_rollback_hook (tests hook-5.*) +# sqlite_preupdate_hook (tests hook-7..hook-12) # # $Id: hook.test,v 1.15 2009/04/07 14:14:23 danielk1977 Exp $ diff --git a/test/interrupt.test b/test/interrupt.test index 8774aee86..23d986987 100644 --- a/test/interrupt.test +++ b/test/interrupt.test @@ -94,14 +94,28 @@ ifcapable {vacuum && !default_autovacuum} { } 1 } ifcapable {explain} { - do_test interrupt-2.5 { + do_test interrupt-2.5.1 { + sqlite3_is_interrupted $DB + } {0} + do_test interrupt-2.5.2 { + unset -nocomplain ::interrupt_count + set ::interrupt_count 0 set sql {EXPLAIN SELECT max(a,b), a, b FROM t1} execsql $sql - set rc [catch {db eval $sql {sqlite3_interrupt $DB}} msg] + set rc [catch {db eval $sql { + sqlite3_interrupt $DB; + incr ::interrupt_count [sqlite3_is_interrupted $DB]; + }} msg] lappend rc $msg } {1 interrupted} + do_test interrupt-2.5.3 { + set ::interrupt_count + } {1} } integrity_check interrupt-2.6 +do_test interrupt-2.7 { + sqlite3_is_interrupted $DB +} {0} # Ticket #594. If an interrupt occurs in the middle of a transaction # and that transaction is later rolled back, the internal schema tables do diff --git a/test/intreal.test b/test/intreal.test index 1a3db0a23..d9ffe9909 100644 --- a/test/intreal.test +++ b/test/intreal.test @@ -95,4 +95,22 @@ do_execsql_test 3.0 { PRAGMA integrity_check; } {a {} ok} + +reset_db +do_execsql_test 4.0 { + CREATE TABLE t1(a REAL, b AS ('expr') ); +} +do_execsql_test 4.1 { + INSERT INTO t1 VALUES( REPLACE(0, '', 'expr') ); +} +do_execsql_test 4.2 { + INSERT INTO t1 SELECT REPLACE(4, '', 'expr'); +} +do_execsql_test 4.3 { + SELECT typeof(a), a FROM t1; +} { + real 0.0 + real 4.0 +} + finish_test diff --git a/test/pragma.test b/test/pragma.test index 4fc97648b..32e8fbef6 100644 --- a/test/pragma.test +++ b/test/pragma.test @@ -582,6 +582,59 @@ do_test pragma-3.30 { } } {} +# The values stored in indexes must be byte-for-byte identical to the +# values stored in tables. +# +reset_db +do_execsql_test pragma-3.40 { + CREATE TABLE t1( + a INTEGER PRIMARY KEY, + b TEXT COLLATE nocase, + c INT COLLATE nocase, + d TEXT + ); + INSERT INTO t1(a,b,c,d) VALUES + (1, 'one','one','one'), + (2, 'two','two','two'), + (3, 'three','three','three'), + (4, 'four','four','four'), + (5, 'five','five','five'); + CREATE INDEX t1bcd ON t1(b,c,d); + CREATE TABLE t2( + a INTEGER PRIMARY KEY, + b TEXT COLLATE nocase, + c INT COLLATE nocase, + d TEXT + ); + INSERT INTO t2(a,b,c,d) VALUES + (1, 'one','one','one'), + (2, 'two','two','TWO'), + (3, 'three','THREE','three'), + (4, 'FOUR','four','four'), + (5, 'FIVE','FIVE','five'); + CREATE INDEX t2bcd ON t2(b,c,d); + CREATE TEMP TABLE saved_schema AS SELECT name, rootpage FROM sqlite_schema; + PRAGMA writable_schema=ON; + UPDATE sqlite_schema + SET rootpage=(SELECT rootpage FROM saved_schema WHERE name='t2bcd') + WHERE name='t1bcd'; + UPDATE sqlite_schema + SET rootpage=(SELECT rootpage FROM saved_schema WHERE name='t1bcd') + WHERE name='t2bcd'; + PRAGMA Writable_schema=RESET; + SELECT integrity_check AS x FROM pragma_integrity_check ORDER BY 1; +} { + {row 2 missing from index t1bcd} + {row 2 missing from index t2bcd} + {row 3 values differ from index t1bcd} + {row 3 values differ from index t2bcd} + {row 4 values differ from index t1bcd} + {row 4 values differ from index t2bcd} + {row 5 values differ from index t1bcd} + {row 5 values differ from index t2bcd} +} +db eval {DROP TABLE t2} + # Test modifying the cache_size of an attached database. ifcapable pager_pragmas&&attach { do_test pragma-4.1 { diff --git a/test/symlink.test b/test/symlink.test index 98b2a32c0..685cae5a4 100644 --- a/test/symlink.test +++ b/test/symlink.test @@ -207,4 +207,32 @@ do_test 4.4.2 { list [file exists x/test.db-wal] [file exists w/test.db-wal] } {1 0} +#------------------------------------------------------------------------- +# Check that extra ".." in a path are ignored. +reset_db +do_execsql_test 5.0 { + CREATE TABLE xyz(x, y, z); + INSERT INTO xyz VALUES(1, 2, 3); +} + +set path [pwd] +set nLink [llength [split $path /]] +set path "[string repeat ../ [expr $nLink*2]]..${path}/test.db" + +sqlite3 db2 $path +do_execsql_test -db db2 5.1 { + SELECT * FROM xyz; +} {1 2 3} +db close + +forcedelete test.db2 +file link test.db2 $path +sqlite3 db2 test.db2 +do_execsql_test -db db2 5.2 { + SELECT * FROM xyz; +} {1 2 3} +forcedelete test.db2 + + + finish_test diff --git a/test/trustschema1.test b/test/trustschema1.test index dba954f14..8edaf8051 100644 --- a/test/trustschema1.test +++ b/test/trustschema1.test @@ -247,5 +247,16 @@ do_execsql_test 3.131 { SELECT * FROM t2; } {} +# 2023-01-09 https://sqlite.org/forum/forumpost/c88a671ad083d153 +# +do_execsql_test 4.1 { + PRAGMA trusted_schema=OFF; + CREATE VIEW test41(x) AS SELECT json_extract('{"a":123}','$.a'); + SELECT * FROM test41; +} 123 +do_execsql_test 4.2 { + PRAGMA trusted_schema=ON; + SELECT * FROM test41; +} 123 finish_test diff --git a/test/view.test b/test/view.test index 60eb7e527..90c9c9909 100644 --- a/test/view.test +++ b/test/view.test @@ -123,16 +123,16 @@ do_execsql_test view-1.10 { } {} do_execsql_test view-1.11 { PRAGMA table_info(v9a); -} {0 x INT 0 {} 0} +} {0 x INTEGER 0 {} 0} do_execsql_test view-1.12 { PRAGMA table_info(v9b); -} {0 x INT 0 {} 0} +} {0 x INTEGER 0 {} 0} do_execsql_test view-1.13 { PRAGMA table_info(v9c); -} {0 x INT 0 {} 0} +} {0 x INTEGER 0 {} 0} do_execsql_test view-1.14 { PRAGMA table_info(v9d); -} {0 x INT 0 {} 0} +} {0 x INTEGER 0 {} 0} do_test view-2.1 { execsql { diff --git a/test/zipfile.test b/test/zipfile.test index 8749f17c3..8b862ae84 100644 --- a/test/zipfile.test +++ b/test/zipfile.test @@ -853,6 +853,14 @@ do_execsql_test 17.1 { ) SELECT DISTINCT typeof(zipfile(0,0,x,0)) FROM vlist; } {blob} - + +# 2023-01-04 +# https://sqlite.org/forum/forumpost/d1c96a9032e564f8 +# Call to fopen() with a NULL filename. +# +do_catchsql_test 18.1 { + SELECT * FROM zipfile(NULL); +} {1 {cannot open file: }} + finish_test diff --git a/tool/warnings.sh b/tool/warnings.sh index a839d235b..60d2b4210 100644 --- a/tool/warnings.sh +++ b/tool/warnings.sh @@ -11,7 +11,13 @@ if uname | grep -i openbsd ; then else # Use these for testing on Linux and Mac OSX: WARNING_OPTS="-Wshadow -Wall -Wextra -pedantic-errors -Wno-long-long" - WARNING_ANDROID_OPTS="-Wshadow -Wall -Wextra" + gccvers=`gcc -v 2>&1 | grep '^gcc version'` + if test "$gccvers" '<' 'gcc version 6' + then + WARNING_ANDROID_OPTS="-Wshadow -Wall -Wextra" + else + WARNING_ANDROID_OPTS="-Wshadow -Wall -Wextra -Wimplicit-fallthrough=0" + fi fi rm -f sqlite3.c |