From: stephan Date: Wed, 26 Nov 2025 16:27:11 +0000 (+0000) Subject: Remove some dead code. Resolve the remaining kvvfs v1/v2 API compatibility confusion... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4ad869aa3f93f75e4bbc07186f76844da2aa47a2;p=thirdparty%2Fsqlite.git Remove some dead code. Resolve the remaining kvvfs v1/v2 API compatibility confusion by eliding the v1 API from Workers like we've always done. Add some tests. FossilOrigin-Name: 450b4bcca6038be58a73ff15ff18b9d70df661fe0bd9777273e0db6fbce5f296 --- diff --git a/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js index c476857177..a603dfacfc 100644 --- a/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js @@ -1,4 +1,3 @@ -//#if not omit-kvvfs /* 2025-11-21 @@ -11,15 +10,24 @@ *********************************************************************** - This file houses the "kvvfs" pieces of the JS API. Most of kvvfs is - implemented in src/os_kv.c and exposed for use here via - sqlite3-wasm.c. + This file houses the "kvvfs" pieces of the SQLite3 JS API. Most of + kvvfs is implemented in src/os_kv.c and exposed/extended for use + here via sqlite3-wasm.c. Main project home page: https://sqlite.org Documentation home page: https://sqlite.org/wasm */ - +//#if omit-kvvfs +globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ + /* These are JS plumbing, not part of the public API */ + delete sqlite3.capi.sqlite3_kvvfs_methods; + delete sqlite3.capi.KVVfsFile; +} +//#else +//#@policy error +//#savepoint begin +//#define kvvfs-v2-added-in=TBD /** kvvfs - the Key/Value VFS - is an SQLite3 VFS which delegates storage of its pages and metadata to a key-value store. @@ -76,10 +84,8 @@ e.g. asynchronously post updates to db pages to some back-end for backups. */ - globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 'use strict'; - /* We unregister the kvvfs VFS from Worker threads later on. */ const capi = sqlite3.capi, sqlite3_kvvfs_methods = capi.sqlite3_kvvfs_methods, KVVfsFile = capi.KVVfsFile, @@ -468,449 +474,414 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ */ const pFileHandles = new Map(); - { - /** - Original WASM functions for methods we partially override. - */ - const originalMethods = { - vfs: Object.create(null), - ioDb: Object.create(null), - ioJrnl: Object.create(null) - }; + /** + Original WASM functions for methods we partially override. + */ + const originalMethods = { + vfs: Object.create(null), + ioDb: Object.create(null), + ioJrnl: Object.create(null) + }; + + /** Returns the appropriate originalMethods[X] instance for the + given a KVVfsFile instance. */ + const originalIoMethods = (kvvfsFile)=> + originalMethods[kvvfsFile.$isJournal ? 'ioJrnl' : 'ioDb']; + + const pVfs = new capi.sqlite3_vfs(kvvfsMethods.$pVfs); + const pIoDb = new capi.sqlite3_io_methods(kvvfsMethods.$pIoDb); + const pIoJrnl = new capi.sqlite3_io_methods(kvvfsMethods.$pIoJrnl); + const recordHandler = + Object.create(null)/** helper for some vfs + routines. Populated later. */; + + /** + Implementations for members of the object referred to by + sqlite3__wasm_kvvfs_methods(). We swap out some native + implementations with these so that we can use JS Storage for + their backing store. + */ + const methodOverrides = { - /** Returns the appropriate originalMethods[X] instance for the - given a KVVfsFile instance. */ - const originalIoMethods = (kvvfsFile)=> - originalMethods[kvvfsFile.$isJournal ? 'ioJrnl' : 'ioDb']; - - const pVfs = new capi.sqlite3_vfs(kvvfsMethods.$pVfs); - const pIoDb = new capi.sqlite3_io_methods(kvvfsMethods.$pIoDb); - const pIoJrnl = new capi.sqlite3_io_methods(kvvfsMethods.$pIoJrnl); - const recordHandler = - Object.create(null)/** helper for some vfs - routines. Populated later. */; /** - Implementations for members of the object referred to by - sqlite3__wasm_kvvfs_methods(). We swap out some native - implementations with these so that we can use JS Storage for - their backing store. + sqlite3_kvvfs_methods's member methods. These perform the + fetching, setting, and removal of storage keys on behalf of + kvvfs. In the native impl these write each db page to a + separate file. This impl stores each db page as a single + record in a Storage object which is mapped to zClass. + + A db's size is stored in a record named kvvfs[-storagename]-sz + and the journal is stored in kvvfs[-storagename]-jrnl. The + [-storagename] part is a remnant of the native impl (so that + it has unique filenames per db) and is only used for + localStorage and sessionStorage. We elide that part (to save + space) from other storage objects but retain it on those two + to avoid invalidating pre-version-2 session/localStorage dbs. + + The interface docs for these methods are in src/os_kv.c's + kvrecordRead(), kvrecordWrite(), and kvrecordDelete(). */ - const methodOverrides = { - - /** - sqlite3_kvvfs_methods's member methods. These perform the - fetching, setting, and removal of storage keys on behalf of - kvvfs. In the native impl these write each db page to a - separate file. This impl stores each db page as a single - record in a Storage object which is mapped to zClass. - - A db's size is stored in a record named kvvfs[-storagename]-sz - and the journal is stored in kvvfs[-storagename]-jrnl. The - [-storagename] part is a remnant of the native impl (so that - it has unique filenames per db) and is only used for - localStorage and sessionStorage. We elide that part (to save - space) from other storage objects but retain it on those two - to avoid invalidating pre-version-2 session/localStorage dbs. - - The interface docs for these methods are in src/os_kv.c's - kvrecordRead(), kvrecordWrite(), and kvrecordDelete(). - */ - recordHandler: { - xRcrdRead: (zClass, zKey, zBuf, nBuf)=>{ - try{ - const jzClass = wasm.cstrToJs(zClass); - const store = storageForZClass(jzClass); - if( 0 ){ - debug("xRcrdRead", jzClass, wasm.cstrToJs(zKey), - zBuf, nBuf, store ); - } - if( !store ) return -1; - const zXKey = zKeyForStorage(store, zClass, zKey); - //if(!zXKey) return -3/*OOM*/; - const jXKey = wasm.cstrToJs(zXKey); - //debug("xRcrdRead zXKey", jzClass, wasm.cstrToJs(zXKey), store ); - const jV = store.storage.getItem(jXKey); - if(null===jV) return -1; - const nV = jV.length /* We are relying 100% on v being - ** ASCII so that jV.length is equal - ** to the C-string's byte length. */; - if( 0 ){ - debug("xRcrdRead", wasm.cstrToJs(zXKey), store, jV); - } - if(nBuf<=0) return nV; - else if(1===nBuf){ - wasm.poke(zBuf, 0); - return nV; - } - if( nBuf+1{ + try{ + const jzClass = wasm.cstrToJs(zClass); + const store = storageForZClass(jzClass); + if( 0 ){ + debug("xRcrdRead", jzClass, wasm.cstrToJs(zKey), + zBuf, nBuf, store ); } - }, - - xRcrdWrite: (zClass, zKey, zData)=>{ - try { - const jzClass = wasm.cstrToJs(zClass); - const store = storageForZClass(jzClass); - const zXKey = zKeyForStorage(store, zClass, zKey); - //if(!zXKey) return SQLITE_NOMEM; - const jxKey = wasm.cstrToJs(zXKey); - const jData = wasm.cstrToJs(zData); - store.storage.setItem(jxKey, jData); - notifyListeners('write', store, jxKey, jData); - return 0; - }catch(e){ - error("kvrecordWrite()",e); - return cache.setError(e, capi.SQLITE_IOERR); + if( !store ) return -1; + const zXKey = zKeyForStorage(store, zClass, zKey); + //if(!zXKey) return -3/*OOM*/; + const jXKey = wasm.cstrToJs(zXKey); + //debug("xRcrdRead zXKey", jzClass, wasm.cstrToJs(zXKey), store ); + const jV = store.storage.getItem(jXKey); + if(null===jV) return -1; + const nV = jV.length /* We are relying 100% on v being + ** ASCII so that jV.length is equal + ** to the C-string's byte length. */; + if( 0 ){ + debug("xRcrdRead", wasm.cstrToJs(zXKey), store, jV); } - }, - - xRcrdDelete: (zClass, zKey)=>{ - try { - const store = storageForZClass(zClass); - const zXKey = zKeyForStorage(store, zClass, zKey); - //if(!zXKey) return capi.SQLITE_NOMEM; - const jxKey = wasm.cstrToJs(zXKey); - store.storage.removeItem(jxKey); - notifyListeners('delete', store, jxKey); - return 0; - }catch(e){ - error("kvrecordDelete()",e); - return cache.setError(e, capi.SQLITE_IOERR); + if(nBuf<=0) return nV; + else if(1===nBuf){ + wasm.poke(zBuf, 0); + return nV; } - } - }/*recordHandler*/, - - /** - Override certain operations of the underlying sqlite3_vfs and - the two sqlite3_io_methods instances so that we can tie - Storage objects to db names. - */ - vfs:{ - /* sqlite3_kvvfs_methods::pVfs's methods */ - xOpen: function(pProtoVfs,zName,pProtoFile,flags,pOutFlags){ - cache.popError(); - try{ - //cache.zReadBuf ??= wasm.malloc(kvvfsMethods.$nBufferSize); - if( !zName ){ - zName = (cache.zEmpty ??= wasm.allocCString("")); - } - const jzClass = wasm.cstrToJs(zName); - //debug("xOpen",jzClass); - validateStorageName(jzClass, true); - util.assert( jzClass.length===wasm.cstrlen(zName), - "ASCII-only validation failed" ); - if( (flags & (capi.SQLITE_OPEN_MAIN_DB - | capi.SQLITE_OPEN_TEMP_DB - | capi.SQLITE_OPEN_TRANSIENT_DB)) - && cache.rxJournalSuffix.test(jzClass) ){ - toss3(capi.SQLITE_ERROR, - "DB files may not have a '-journal' suffix."); - } - const rc = originalMethods.vfs.xOpen(pProtoVfs, zName, pProtoFile, - flags, pOutFlags); - if( rc ) return rc; - let deleteAt0 = false; - if(wasm.isPtr(arguments[1]/*original zName*/)){ - if(capi.sqlite3_uri_boolean(zName, "delete-on-close", 0)){ - deleteAt0 = true; - } - } - const f = new KVVfsFile(pProtoFile); - util.assert(f.$zClass, "Missing f.$zClass"); - let s = storageForZClass(jzClass); - //debug("xOpen", jzClass, s); - if( s ){ - ++s.refc; - //no if( true===deleteAt0 ) s.deleteAtRefc0 = true; - s.files.push(f); - }else{ - /* TODO: a url flag which tells it to keep the storage - around forever so that future xOpen()s get the same - Storage-ish objects. We can accomplish that by - simply increasing the refcount once more. */ - wasm.poke32(pOutFlags, flags | sqlite3.SQLITE_OPEN_CREATE); - util.assert( !f.$isJournal, "Opening a journal before its db? "+jzClass ); - /* Map both zName and zName-journal to the same storage. */ - const nm = jzClass.replace(cache.rxJournalSuffix,''); - s = newStorageObj(nm); - installStorageAndJournal(s); - s.files.push(f); - s.deleteAtRefc0 = deleteAt0; - debug("xOpen installed storage handle [",nm, nm+"-journal","]", s); - } - pFileHandles.set(pProtoFile, {storage: s, file: f, jzClass}); - notifyListeners('open', s, s.files.length); - return 0; - }catch(e){ - warn("xOpen:",e); - return cache.setError(e); + if( nBuf+10 ){ - wasm.poke32( - pResOut, 1 - /* This poke is triggering an abort via xClose(). - If we poke 0 then no problem... except that - xAccess() doesn't report the truth. Same effect - if we move that to the native impl - os_kv.c's kvvfsAccess(). */ - ); - } - debug("xAccess", jzName, drc, pResOut, wasm.peek32(pResOut)); - return 0; - }else{ - const rc = originalMethods.vfs.xAccess( - pProtoVfs, zPath, flags, pResOut - /* This one is only valid for local/session storage. */ - ); - if( 0 && !!wasm.peek32(pResOut) ){ - /* The native impl, despite apparently hitting the - right data on this side of the call, never sets - pResOut to anything other than 0. */ - debug("xAccess *pResOut", wasm.cstrToJs(zPath), wasm.peek32(pResOut)); - } - return rc; - } - }catch(e){ - error('xAccess',e); - return cache.setError(e); + xRcrdWrite: (zClass, zKey, zData)=>{ + try { + const jzClass = wasm.cstrToJs(zClass); + const store = storageForZClass(jzClass); + const zXKey = zKeyForStorage(store, zClass, zKey); + //if(!zXKey) return SQLITE_NOMEM; + const jxKey = wasm.cstrToJs(zXKey); + const jData = wasm.cstrToJs(zData); + store.storage.setItem(jxKey, jData); + notifyListeners('write', store, jxKey, jData); + return 0; + }catch(e){ + error("kvrecordWrite()",e); + return cache.setError(e, capi.SQLITE_IOERR); + } + }, + + xRcrdDelete: (zClass, zKey)=>{ + try { + const store = storageForZClass(zClass); + const zXKey = zKeyForStorage(store, zClass, zKey); + //if(!zXKey) return capi.SQLITE_NOMEM; + const jxKey = wasm.cstrToJs(zXKey); + store.storage.removeItem(jxKey); + notifyListeners('delete', store, jxKey); + return 0; + }catch(e){ + error("kvrecordDelete()",e); + return cache.setError(e, capi.SQLITE_IOERR); + } + } + }/*recordHandler*/, + + /** + Override certain operations of the underlying sqlite3_vfs and + the two sqlite3_io_methods instances so that we can tie + Storage objects to db names. + */ + vfs:{ + /* sqlite3_kvvfs_methods::pVfs's methods */ + xOpen: function(pProtoVfs,zName,pProtoFile,flags,pOutFlags){ + cache.popError(); + try{ + //cache.zReadBuf ??= wasm.malloc(kvvfsMethods.$nBufferSize); + if( !zName ){ + zName = (cache.zEmpty ??= wasm.allocCString("")); + } + const jzClass = wasm.cstrToJs(zName); + //debug("xOpen",jzClass); + validateStorageName(jzClass, true); + util.assert( jzClass.length===wasm.cstrlen(zName), + "ASCII-only validation failed" ); + if( (flags & (capi.SQLITE_OPEN_MAIN_DB + | capi.SQLITE_OPEN_TEMP_DB + | capi.SQLITE_OPEN_TRANSIENT_DB)) + && cache.rxJournalSuffix.test(jzClass) ){ + toss3(capi.SQLITE_ERROR, + "DB files may not have a '-journal' suffix."); } - }, - - xRandomness: function(pVfs, nOut, pOut){ - const heap = wasm.heap8u(); - let i = 0; - const npOut = Number(pOut); - for(; i < nOut; ++i) heap[npOut + i] = (Math.random()*255000) & 0xFF; - return i; - }, - - xGetLastError: function(pVfs,nOut,pOut){ - const e = cache.popError(); - debug('xGetLastError',e); - if(e){ - const scope = wasm.scopedAllocPush(); - try{ - const [cMsg, n] = wasm.scopedAllocCString(e.message, true); - wasm.cstrncpy(pOut, cMsg, nOut); - if(n > nOut) wasm.poke8(wasm.ptr.add(pOut,nOut,-1), 0); - debug("set xGetLastError",e.message); - }catch(e){ - return capi.SQLITE_NOMEM; - }finally{ - wasm.scopedAllocPop(scope); + const rc = originalMethods.vfs.xOpen(pProtoVfs, zName, pProtoFile, + flags, pOutFlags); + if( rc ) return rc; + let deleteAt0 = false; + if(wasm.isPtr(arguments[1]/*original zName*/)){ + if(capi.sqlite3_uri_boolean(zName, "delete-on-close", 0)){ + deleteAt0 = true; } } - return e ? ((e.resultCode | 0) || capi.SQLITE_IOERR) : 0; + const f = new KVVfsFile(pProtoFile); + util.assert(f.$zClass, "Missing f.$zClass"); + let s = storageForZClass(jzClass); + //debug("xOpen", jzClass, s); + if( s ){ + ++s.refc; + //no if( true===deleteAt0 ) s.deleteAtRefc0 = true; + s.files.push(f); + }else{ + /* TODO: a url flag which tells it to keep the storage + around forever so that future xOpen()s get the same + Storage-ish objects. We can accomplish that by + simply increasing the refcount once more. */ + wasm.poke32(pOutFlags, flags | sqlite3.SQLITE_OPEN_CREATE); + util.assert( !f.$isJournal, "Opening a journal before its db? "+jzClass ); + /* Map both zName and zName-journal to the same storage. */ + const nm = jzClass.replace(cache.rxJournalSuffix,''); + s = newStorageObj(nm); + installStorageAndJournal(s); + s.files.push(f); + s.deleteAtRefc0 = deleteAt0; + debug("xOpen installed storage handle [",nm, nm+"-journal","]", s); + } + pFileHandles.set(pProtoFile, {storage: s, file: f, jzClass}); + notifyListeners('open', s, s.files.length); + return 0; + }catch(e){ + warn("xOpen:",e); + return cache.setError(e); } - -//#if nope - // these impls work but there's currently no pressing need _not_ use - // the native impls. - xCurrentTime: function(pVfs,pOut){ - wasm.poke64f(pOut, 2440587.5 + (Date.now()/86400000)); + }/*xOpen()*/, + + xDelete: function(pVfs, zName, iSyncFlag){ + cache.popError(); + try{ + const jzName = wasm.cstrToJs(zName); + //debug("xDelete", jzName, storageForZClass(jzName)); + if( cache.rxJournalSuffix.test(jzName) ){ + recordHandler.xRcrdDelete(zName, cache.zKeyJrnl); + } return 0; - }, + }catch(e){ + warn("xDelete",e); + return cache.setError(e); + } + }, - xCurrentTimeInt64: function(pVfs,pOut){ - wasm.poke64(pOut, (2440587.5 * 86400000) + Date.now()); + xAccess: function(pProtoVfs, zPath, flags, pResOut){ + cache.popError(); + try{ + /** + In every test run to date, if this function sets + *pResOut to anything other than 0, the VFS fails to + function. Why that that is is a mystery. How it seems + to work despite never reporting "file found" is a + mystery. + */ + wasm.poke32(pResOut, 0); return 0; + }catch(e){ + error('xAccess',e); + return cache.setError(e); } -//#endif }, - /** - kvvfs has separate sqlite3_api_methods impls for some of the - methods depending on whether it's a db or journal file. Some - of the methods use shared impls but others are specific to - either db or journal files. - */ - ioDb:{ - /* sqlite3_kvvfs_methods::pIoDb's methods */ - xClose: function(pFile){ + xRandomness: function(pVfs, nOut, pOut){ + const heap = wasm.heap8u(); + let i = 0; + const npOut = Number(pOut); + for(; i < nOut; ++i) heap[npOut + i] = (Math.random()*255000) & 0xFF; + return i; + }, + + xGetLastError: function(pVfs,nOut,pOut){ + const e = cache.popError(); + debug('xGetLastError',e); + if(e){ + const scope = wasm.scopedAllocPush(); try{ - const h = pFileHandles.get(pFile); - //debug("xClose", pFile, h); - if( h ){ - pFileHandles.delete(pFile); - const s = storageForZClass(h.jzClass); - s.files = s.files.filter((v)=>v!==h.file); - if( --s.refc<=0 && s.deleteAtRefc0 ){ - deleteStorage(s); - } - originalIoMethods(h.file).xClose(pFile); - h.file.dispose(); - notifyListeners('close', s, s.files.length); - }else{ - /* Can happen if xOpen fails */ - } - return 0; + const [cMsg, n] = wasm.scopedAllocCString(e.message, true); + wasm.cstrncpy(pOut, cMsg, nOut); + if(n > nOut) wasm.poke8(wasm.ptr.add(pOut,nOut,-1), 0); + debug("set xGetLastError",e.message); }catch(e){ - error("xClose",e); - return capi.SQLITE_ERROR; + return capi.SQLITE_NOMEM; + }finally{ + wasm.scopedAllocPop(scope); } - }, + } + return e ? ((e.resultCode | 0) || capi.SQLITE_IOERR) : 0; + } + //#if nope - xRead: function(pFile,pTgt,n,iOff64){}, - xWrite: function(pFile,pSrc,n,iOff64){}, - xTruncate: function(pFile,i64){}, - xSync: function(pFile,flags){}, - xFileControl: function(pFile, opId, pArg){}, - xFileSize: function(pFile,pi64Out){}, - xLock: function(pFile,iLock){}, - xUnlock: function(pFile,iLock){}, - xCheckReservedLock: function(pFile,piOut){}, - xFileControl: function(pFile,iOp,pArg){}, - xSectorSize: function(pFile){}, - xDeviceCharacteristics: function(pFile){} + // these impls work but there's currently no pressing need _not_ use + // the native impls. + xCurrentTime: function(pVfs,pOut){ + wasm.poke64f(pOut, 2440587.5 + (Date.now()/86400000)); + return 0; + }, + + xCurrentTimeInt64: function(pVfs,pOut){ + wasm.poke64(pOut, (2440587.5 * 86400000) + Date.now()); + return 0; + } //#endif + }/*.vfs*/, + + /** + kvvfs has separate sqlite3_api_methods impls for some of the + methods depending on whether it's a db or journal file. Some + of the methods use shared impls but others are specific to + either db or journal files. + */ + ioDb:{ + /* sqlite3_kvvfs_methods::pIoDb's methods */ + xClose: function(pFile){ + try{ + const h = pFileHandles.get(pFile); + //debug("xClose", pFile, h); + if( h ){ + pFileHandles.delete(pFile); + const s = storageForZClass(h.jzClass); + s.files = s.files.filter((v)=>v!==h.file); + if( --s.refc<=0 && s.deleteAtRefc0 ){ + deleteStorage(s); + } + originalIoMethods(h.file).xClose(pFile); + h.file.dispose(); + notifyListeners('close', s, s.files.length); + }else{ + /* Can happen if xOpen fails */ + } + return 0; + }catch(e){ + error("xClose",e); + return capi.SQLITE_ERROR; + } }, +//#if nope + xRead: function(pFile,pTgt,n,iOff64){}, + xWrite: function(pFile,pSrc,n,iOff64){}, + xTruncate: function(pFile,i64){}, + xSync: function(pFile,flags){}, + xFileControl: function(pFile, opId, pArg){}, + xFileSize: function(pFile,pi64Out){}, + xLock: function(pFile,iLock){}, + xUnlock: function(pFile,iLock){}, + xCheckReservedLock: function(pFile,piOut){}, + xFileControl: function(pFile,iOp,pArg){}, + xSectorSize: function(pFile){}, + xDeviceCharacteristics: function(pFile){} +//#endif + }/*.ioDb*/, - ioJrnl:{ - /* sqlite3_kvvfs_methods::pIoJrnl's methods. Those set to true - are copied as-is from the ioDb objects. Others are specific - to journal files. */ - xClose: true, + ioJrnl:{ + /* sqlite3_kvvfs_methods::pIoJrnl's methods. Those set to true + are copied as-is from the ioDb objects. Others are specific + to journal files. */ + xClose: true, //#if nope - xRead: function(pFile,pTgt,n,iOff64){}, - xWrite: function(pFile,pSrc,n,iOff64){}, - xTruncate: function(pFile,i64){}, - xSync: function(pFile,flags){}, - xFileControl: function(pFile, opId, pArg){}, - xFileSize: function(pFile,pi64Out){}, - xLock: true, - xUnlock: true, - xCheckReservedLock: true, - xFileControl: function(pFile,iOp,pArg){}, - xSectorSize: true, - xDeviceCharacteristics: true + xRead: function(pFile,pTgt,n,iOff64){}, + xWrite: function(pFile,pSrc,n,iOff64){}, + xTruncate: function(pFile,i64){}, + xSync: function(pFile,flags){}, + xFileControl: function(pFile, opId, pArg){}, + xFileSize: function(pFile,pi64Out){}, + xLock: true, + xUnlock: true, + xCheckReservedLock: true, + xFileControl: function(pFile,iOp,pArg){}, + xSectorSize: true, + xDeviceCharacteristics: true //#endif + }/*.ioJrnl*/ + }/*methodOverrides*/; + + debug("pVfs and friends", pVfs, pIoDb, pIoJrnl, + kvvfsMethods, capi.sqlite3_file.structInfo, + KVVfsFile.structInfo); + try { + cache.keybuf = Object.create(null); + cache.keybuf.n = kvvfsMethods.$nBufferSize; + util.assert( cache.keybuf.n>1024*129, "Key buffer is not large enough" + /* Native is SQLITE_KVOS_SZ is 133073 as of this writing */ ); + for(const e of Object.entries(methodOverrides.recordHandler)){ + // Overwrite kvvfsMethods's callbacks + const k = e[0], f = e[1]; + recordHandler[k] = f; + if( 0 ){ + // bug: this should work + kvvfsMethods.installMethod(k, f); + }else{ + kvvfsMethods[kvvfsMethods.memberKey(k)] = + wasm.installFunction(kvvfsMethods.memberSignature(k), f); } - }/*methodOverrides*/; - - debug("pVfs and friends", pVfs, pIoDb, pIoJrnl, - kvvfsMethods, capi.sqlite3_file.structInfo, - KVVfsFile.structInfo); - try { - cache.keybuf = Object.create(null); - cache.keybuf.n = kvvfsMethods.$nBufferSize; - util.assert( cache.keybuf.n>1024*129, "Key buffer is not large enough" - /* Native is SQLITE_KVOS_SZ is 133073 as of this writing */ ); - for(const e of Object.entries(methodOverrides.recordHandler)){ - // Overwrite kvvfsMethods's callbacks - const k = e[0], f = e[1]; - recordHandler[k] = f; - if( 0 ){ - // bug: this should work - kvvfsMethods.installMethod(k, f); - }else{ - kvvfsMethods[kvvfsMethods.memberKey(k)] = - wasm.installFunction(kvvfsMethods.memberSignature(k), f); - } - } - for(const e of Object.entries(methodOverrides.vfs)){ - // Overwrite some pVfs entries and stash the original impls - const k = e[0], f = e[1], km = pVfs.memberKey(k), - member = pVfs.structInfo.members[k] - || util.toss("Missing pVfs.structInfo[",k,"]"); - originalMethods.vfs[k] = wasm.functionEntry(pVfs[km]); - pVfs[km] = wasm.installFunction(member.signature, f); - } - for(const e of Object.entries(methodOverrides.ioDb)){ - // Similar treatment for pVfs.$pIoDb a.k.a. pIoDb... - const k = e[0], f = e[1], km = pIoDb.memberKey(k); - originalMethods.ioDb[k] = wasm.functionEntry(pIoDb[km]) - || util.toss("Missing native pIoDb[",km,"]"); - pIoDb[km] = wasm.installFunction(pIoDb.memberSignature(k), f); - } - for(const e of Object.entries(methodOverrides.ioJrnl)){ - // Similar treatment for pVfs.$pIoJrnl a.k.a. pIoJrnl... - const k = e[0], f = e[1], km = pIoJrnl.memberKey(k); - originalMethods.ioJrnl[k] = wasm.functionEntry(pIoJrnl[km]) - || util.toss("Missing native pIoJrnl[",km,"]"); - if( true===f ){ - /* use pIoDb's copy */ - pIoJrnl[km] = pIoDb[km] || util.toss("Missing copied pIoDb[",km,"]"); - }else{ - pIoJrnl[km] = wasm.installFunction(pIoJrnl.memberSignature(k), f); - } + } + for(const e of Object.entries(methodOverrides.vfs)){ + // Overwrite some pVfs entries and stash the original impls + const k = e[0], f = e[1], km = pVfs.memberKey(k), + member = pVfs.structInfo.members[k] + || util.toss("Missing pVfs.structInfo[",k,"]"); + originalMethods.vfs[k] = wasm.functionEntry(pVfs[km]); + pVfs[km] = wasm.installFunction(member.signature, f); + } + for(const e of Object.entries(methodOverrides.ioDb)){ + // Similar treatment for pVfs.$pIoDb a.k.a. pIoDb... + const k = e[0], f = e[1], km = pIoDb.memberKey(k); + originalMethods.ioDb[k] = wasm.functionEntry(pIoDb[km]) + || util.toss("Missing native pIoDb[",km,"]"); + pIoDb[km] = wasm.installFunction(pIoDb.memberSignature(k), f); + } + for(const e of Object.entries(methodOverrides.ioJrnl)){ + // Similar treatment for pVfs.$pIoJrnl a.k.a. pIoJrnl... + const k = e[0], f = e[1], km = pIoJrnl.memberKey(k); + originalMethods.ioJrnl[k] = wasm.functionEntry(pIoJrnl[km]) + || util.toss("Missing native pIoJrnl[",km,"]"); + if( true===f ){ + /* use pIoDb's copy */ + pIoJrnl[km] = pIoDb[km] || util.toss("Missing copied pIoDb[",km,"]"); + }else{ + pIoJrnl[km] = wasm.installFunction(pIoJrnl.memberSignature(k), f); } - }finally{ - kvvfsMethods.dispose(); - pVfs.dispose(); - pIoDb.dispose(); - pIoJrnl.dispose(); } - }/*native method overrides*/ + }finally{ + kvvfsMethods.dispose(); + pVfs.dispose(); + pIoDb.dispose(); + pIoJrnl.dispose(); + } /* That gets all of the low-level bits out of the way. What follows @@ -945,20 +916,17 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - It accepts an arbitrary storage name. In v1 this was a silent no-op for any names other than ('local','session',''). - - The second argument was added. Its default value reflects the - legacy behavior. - - It throws if a db currently has the storage opened. That version 1 did not throw for this case was due to an architectural limitation which has since been overcome. */ - capi.sqlite3_js_kvvfs_clear = function callee(which=""){ + const sqlite3_js_kvvfs_clear = function callee(which){ if( !which ){ return callee('local') + callee('session'); } const store = storageForZClass(which); if( !store ) return 0; - const keyPrefix = store.prefix; + const keyPrefix = store.keyPrefix; if( store.files.length ){ toss3(capi.SQLITE_ACCESS, "Cannot clear in-use database storage."); @@ -994,7 +962,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ unspecified and may include per-entry overhead invisible to clients. */ - capi.sqlite3_js_kvvfs_size = function callee(which=""){ + const sqlite3_js_kvvfs_size = function callee(which){ if( !which ){ return callee('local') + callee('session'); } @@ -1004,7 +972,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ let i, sz = 0; for(i = 0; i < s.length; ++i){ const k = s.key(i); - if(!store.prefix || k.startsWith(store.prefix)){ + if(!store.keyPrefix || k.startsWith(store.keyPrefix)){ sz += k.length; sz += s.getItem(k).length; } @@ -1045,7 +1013,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ The format may be changed in the future but kvvfs will continue to support the current form. - Added in version 3.?? (tenatively 3.52). + Added in version @kvvfs-v2-added-in@. */ const sqlite3_js_kvvfs_export = function(storageName,includeJournal=true){ const store = storageForZClass(storageName); @@ -1097,8 +1065,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/* sqlite3_js_kvvfs_export */; /** - EXPERIMENTAL. This interface is subject to change. - The counterpart of sqlite3_js_kvvfs_export(). Its argument must be the result of that function() or a compatible one. @@ -1121,6 +1087,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ reaching that step, in which case the db is not modified. If exp.name refers to a new storage name then if it throws, the name does not get installed. + + Added in version @kvvfs-v2-added-in@. */ const sqlite3_js_kvvfs_import = function(exp, overwrite=false){ if( !exp?.timestamp @@ -1147,7 +1115,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ toss3(capi.SQLITE_IOERR_ACCESS, "Cannot import db storage while it is in use."); } - capi.sqlite3_js_kvvfs_clear(exp.name, true); + sqlite3_js_kvvfs_clear(exp.name, true); }else{ store = newStorageObj(exp.name); //warn("Installing new storage:",store); @@ -1165,7 +1133,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if( isNew ) installStorageAndJournal(store); }catch(e){ if( !isNew ){ - try{capi.sqlite3_js_kvvfs_clear(exp.name, true);} + try{sqlite3_js_kvvfs_clear(exp.name, true);} catch(ee){/*ignored*/} } throw e; @@ -1174,14 +1142,14 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; /** - EXPERIMENTAL. This interface is subject to change. - If no kvvfs storage exists with the given name, one is installed. If one exists, its reference count is increased so that it won't be freed by the closing of a database or journal file. Throws if the name is not valid for a new storage object. + + Added in version @kvvfs-v2-added-in@. */ const sqlite3_js_kvvfs_reserve = function(name){ let store = storageForZClass(name); @@ -1210,6 +1178,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Returns true if it reduces the refcount, else false. A result of true does not necessarily mean that the storage unit was removed, just that its refcount was lowered. + + Added in version @kvvfs-v2-added-in@. */ const sqlite3_js_kvvfs_unlink = function(name){ const store = storageForZClass(name); @@ -1228,8 +1198,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; /** - EXPERIMENTAL. This interface is subject to change. - Adds an event listener to a kvvfs storage object. The idea is that this can be used to asynchronously back up one kvvfs storage object to another or another channel entirely. (The caveat in the @@ -1290,6 +1258,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Design note: JS has StorageEvents but only in the main thread, which is why the listeners are not based on that. + + Added in version @kvvfs-v2-added-in@. */ const sqlite3_js_kvvfs_listen = function(opt){ if( !opt || 'object'!==typeof opt ){ @@ -1313,14 +1283,14 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }; /** - EXPERIMENTAL. This interface is subject to change. - Removes the kvvfs event listeners for the given options object. It must be passed the same object instance which was passed to sqlite3_js_kvvfs_listen(). This has no side effects if opt is invalid or is not a match for any listeners. + + Added in version @kvvfs-v2-added-in@. */ const sqlite3_js_kvvfs_unlisten = function(opt){ const store = storageForZClass(opt?.storage); @@ -1343,18 +1313,33 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ listen: sqlite3_js_kvvfs_listen, unlisten: sqlite3_js_kvvfs_unlisten, exists: (name)=>!!storageForZClass(name), - size: capi.sqlite3_js_kvvfs_size, - clear: capi.sqlite3_js_kvvfs_clear + size: sqlite3_js_kvvfs_size, + clear: sqlite3_js_kvvfs_clear }); if( sqlite3.__isUnderTest ){ /* For inspection via the dev tools console. */ sqlite3.kvvfs.test = { pFileHandles, - cache + cache, + KVVfsStorage }; } + if( globalThis.localStorage || globalThis.sessionStorage ){ + /** + Prior to version 2, kvvfs was only available in the main + thread. We retain that for the v1 APIs, exposing them only in + the main UI thread. As of version 2, kvvfs is available in all + threads but only via its v2 interface (sqlite3.kvvfs). + + These versions have a default argument value of "" which the v2 + versions lack. + */ + capi.sqlite3_js_kvvfs_size = (which="")=>sqlite3_js_kvvfs_size(which); + capi.sqlite3_js_kvvfs_clear = (which="")=>sqlite3_js_kvvfs_clear(which); + } + if(sqlite3.oo1?.DB){ /** Functionally equivalent to DB(storageName,'c','kvvfs') except @@ -1389,10 +1374,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ validateStorageName( m ? m[3] : opt.filename); DB.dbCtorHelper.call(this, opt); }; - sqlite3.oo1.JsStorageDb.defaultStorageName = 'session'; + sqlite3.oo1.JsStorageDb.defaultStorageName + = cache.storagePool.session ? 'session' : 'localThread'; const jdb = sqlite3.oo1.JsStorageDb; jdb.prototype = Object.create(DB.prototype); - jdb.clearStorage = capi.sqlite3_js_kvvfs_clear; + jdb.clearStorage = sqlite3_js_kvvfs_clear; /** Clears this database instance's storage or throws if this instance has been closed. Returns the number of @@ -1402,7 +1388,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return jdb.clearStorage(this.affirmOpen().filename, true); }; /** Equivalent to sqlite3_js_kvvfs_size(). */ - jdb.storageSize = capi.sqlite3_js_kvvfs_size; + jdb.storageSize = sqlite3_js_kvvfs_size; /** Returns the _approximate_ number of bytes this database takes up in its storage or throws if this instance has been closed. @@ -1410,14 +1396,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ jdb.prototype.storageSize = function(){ return jdb.storageSize(this.affirmOpen().filename, true); }; - - if( sqlite3.__isUnderTest ){ - jdb.test = { - storageForZClass, - cache - } - }/* __isUnderTest */ }/*sqlite3.oo1.JsStorageDb*/ })/*globalThis.sqlite3ApiBootstrap.initializers*/; +//#savepoint rollback //#endif not omit-kvvfs diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index b8870f46e1..278632e8da 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -2881,6 +2881,31 @@ globalThis.sqlite3InitModule = sqlite3InitModule; //////////////////////////////////////////////////////////////////////// T.g('kvvfs') + .t({ + name: 'kvvfs v1 API availability', + test: function(sqlite3){ + const capi = sqlite3.capi; + if( isUIThread() ){ + T.assert( capi.sqlite3_js_kvvfs_clear ) + .assert( capi.sqlite3_js_kvvfs_size ); + }else{ + /* Historical behaviour retained not for compatibility but + to help avoid some confusion between the v1 and v2 kvvfs + APIs (namely in how the v1 variants handle empty + strings). */ + T.assert( !capi.sqlite3_js_kvvfs_clear ) + .assert( !capi.sqlite3_js_kvvfs_size ); + } + const k = sqlite3.kvvfs; + T.assert( k && 'object'===typeof k ); + for(const n of ['reserve', 'import', 'export', + 'unlink', 'listen', 'unlisten', + //'exists', + 'size', 'clear'] ){ + T.assert( k[n] instanceof Function ); + } + } + }/*kvvfs API availability*/) .t({ name: 'kvvfs sessionStorage', predicate: ()=>(globalThis.sessionStorage || "sessionStorage is unavailable"), @@ -2888,7 +2913,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; const JDb = sqlite3.oo1.JsStorageDb; const pVfs = capi.sqlite3_vfs_find('kvvfs'); T.assert(looksLikePtr(pVfs)); - let x = JDb.test.storageForZClass('session'); + let x = sqlite3.kvvfs.test.storageForZClass('session'); T.assert( 0 === x.files.length ) .assert( globalThis.sessionStorage===x.storage ) .assert( 'kvvfs-session-' === x.keyPrefix ); diff --git a/manifest b/manifest index 75aae11297..7c8ee774ad 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Wasm\sspeedtest1:\sreplace\ssome\s'self'\swith\s'globalThis'\sand\sexpose\sthe\ssqlite3\sobject\sto\sthe\sglobal\sscope\sfor\sinspection\svia\sthe\sdev\sconsole\s(for\sinspecting\sresulting\skvvfs\sstorage\sobjects). -D 2025-11-26T16:26:27.148 +C Remove\ssome\sdead\scode.\sResolve\sthe\sremaining\skvvfs\sv1/v2\sAPI\scompatibility\sconfusion\sby\seliding\sthe\sv1\sAPI\sfrom\sWorkers\slike\swe've\salways\sdone.\sAdd\ssome\stests. +D 2025-11-26T16:27:11.737 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -600,7 +600,7 @@ F ext/wasm/api/sqlite3-api-worker1.c-pp.js 1041dd645e8e821c082b628cd8d9acf70c667 F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89 F ext/wasm/api/sqlite3-opfs-async-proxy.js 9654b565b346dc609b75d15337f20acfa7af7d9d558da1afeb9b6d8eaa404966 F ext/wasm/api/sqlite3-vfs-helper.c-pp.js 3f828cc66758acb40e9c5b4dcfd87fd478a14c8fb7f0630264e6c7fa0e57515d -F ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js 3c16758871b5733c2481f67bb0ce1b9ebc84ac2aec9b5429c47484c4d48476ea +F ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js 2129d852692514b87168c676daa9611ff470631fd9dd1850380bb339447ea6b8 F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js a2eea6442556867b589e04107796c6e1d04a472219529eeb45b7cd221d7d048b F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 88ce2078267a2d1af57525a32d896295f4a8db7664de0e17e82dc9ff006ed8d3 F ext/wasm/api/sqlite3-vtab-helper.c-pp.js 9097074724172e31e56ce20ccd7482259cf72a76124213cbc9469d757676da86 @@ -647,7 +647,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555 F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c F ext/wasm/tester1-worker.c-pp.html 0e432ec2c0d99cd470484337066e8d27e7aee4641d97115338f7d962bf7b081a F ext/wasm/tester1.c-pp.html 52d88fe2c6f21a046030a36410b4839b632f4424028197a45a3d5669ea724ddb -F ext/wasm/tester1.c-pp.js e224b2c827a40a915c8b8a68d12bfed1c8c8fce59f9722b28fc8993e1cdcc5e8 +F ext/wasm/tester1.c-pp.js 637365c25cee9fc598b0692abcfb74ff4eb96741fdbb37c0a3a4cd6c7865b669 F ext/wasm/tests/opfs/concurrency/index.html 657578a6e9ce1e9b8be951549ed93a6a471f4520a99e5b545928668f4285fb5e F ext/wasm/tests/opfs/concurrency/test.js d08889a5bb6e61937d0b8cbb78c9efbefbf65ad09f510589c779b7cc6a803a88 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 @@ -2178,8 +2178,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P fd85b96b12d164f7700624dba478d35e886b23f5c31c853715f783639ea95f23 -R b940057b3e5baaba9e06e05c5ba016ac +P c18154bc9a7cf07d3902d74d27b4ca696929aae9e1abe031679bd5a42d759046 +R 0b5f71befded837e8498adc93b1e36b5 U stephan -Z aef30e10e6a9b3f34615b924cf02cd1b +Z 6151d9a6ceb918c4eb7ea5d986b2dc3c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index bb710af7c5..a74e468c5e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c18154bc9a7cf07d3902d74d27b4ca696929aae9e1abe031679bd5a42d759046 +450b4bcca6038be58a73ff15ff18b9d70df661fe0bd9777273e0db6fbce5f296