From 794a9b48accf73a32af1e8e3abc0703794ed849e Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 21 Nov 2025 15:03:38 +0000 Subject: [PATCH] Add new file missing from [3c4061428544]. FossilOrigin-Name: 41f94eca01d8317364aa60ddd8e5fe3cd21a215040ef271a157d450a914139d0 --- ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js | 333 +++++++++++++++++++++++++ manifest | 14 +- manifest.uuid | 2 +- 3 files changed, 341 insertions(+), 8 deletions(-) create mode 100644 ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js diff --git a/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js new file mode 100644 index 0000000000..c4f9109cb1 --- /dev/null +++ b/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js @@ -0,0 +1,333 @@ +//#if not omit-kvvfs +/* + 2025-11-21 + + 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. + + *********************************************************************** + + This file houses the "kvvfs" pieces of the JS API. + + Main project home page: https://sqlite.org + + Documentation home page: https://sqlite.org/wasm +*/ +globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ + 'use strict'; + /* We unregister the kvvfs VFS from Worker threads later on. */ + const util = sqlite3.util, + capi = sqlite3.capi, + wasm = sqlite3.wasm, + sqlite3_kvvfs_methods = capi.sqlite3_kvvfs_methods, + pKvvfs = sqlite3.capi.sqlite3_vfs_find("kvvfs"); + delete capi.sqlite3_kvvfs_methods /* this is JS plumbing, not part + of the public API */; + if( !pKvvfs ) return /* built without kvvfs */; + + if( !util.isUIThread() ){ + /* One test currently relies on this VFS not being visible + in Workers. If we add generic object storage, we can + retain this VFS in Workers. */ + capi.sqlite3_vfs_unregister(pKvvfs); + return; + } + + /** + Internal helper for sqlite3_js_kvvfs_clear() and friends. + Its argument should be one of ('local','session',""). + */ + const __kvfsWhich = function(which){ + const rc = Object.create(null); + rc.prefix = 'kvvfs-'+which; + rc.stores = []; + if('session'===which || ""===which) rc.stores.push(globalThis.sessionStorage); + if('local'===which || ""===which) rc.stores.push(globalThis.localStorage); + return rc; + }; + + /** + Clears all storage used by the kvvfs DB backend, deleting any + DB(s) stored there. Its argument must be either 'session', + 'local', or "". In the first two cases, only sessionStorage + resp. localStorage is cleared. If it's an empty string (the + default) then both are cleared. Only storage keys which match + the pattern used by kvvfs are cleared: any other client-side + data are retained. + + This function is only available in the main window thread. + + Returns the number of entries cleared. + */ + capi.sqlite3_js_kvvfs_clear = function(which=""){ + let rc = 0; + const kvWhich = __kvfsWhich(which); + kvWhich.stores.forEach((s)=>{ + const toRm = [] /* keys to remove */; + let i; + for( i = 0; i < s.length; ++i ){ + const k = s.key(i); + if(k.startsWith(kvWhich.prefix)) toRm.push(k); + } + toRm.forEach((kk)=>s.removeItem(kk)); + rc += toRm.length; + }); + return rc; + }; + + /** + This routine guesses the approximate amount of + window.localStorage and/or window.sessionStorage in use by the + kvvfs database backend. Its argument must be one of ('session', + 'local', ""). In the first two cases, only sessionStorage + resp. localStorage is counted. If it's an empty string (the + default) then both are counted. Only storage keys which match + the pattern used by kvvfs are counted. The returned value is + twice the "length" value of every matching key and value, + noting that JavaScript stores each character in 2 bytes. + + The returned size is not authoritative from the perspective of + how much data can fit into localStorage and sessionStorage, as + the precise algorithms for determining those limits are + unspecified and may include per-entry overhead invisible to + clients. + */ + capi.sqlite3_js_kvvfs_size = function(which=""){ + let sz = 0; + const kvWhich = __kvfsWhich(which); + kvWhich.stores.forEach((s)=>{ + let i; + for(i = 0; i < s.length; ++i){ + const k = s.key(i); + if(k.startsWith(kvWhich.prefix)){ + sz += k.length; + sz += s.getItem(k).length; + } + } + }); + return sz * 2 /* because JS uses 2-byte char encoding */; + }; + + const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKeyOnPstack; + const pstack = wasm.pstack; + const kvvfsStorage = (zClass)=>((115/*=='s'*/===wasm.peek(zClass)) + ? sessionStorage : localStorage); + + { /* Override native sqlite3_kvvfs_methods */ + const kvvfsMethods = new sqlite3_kvvfs_methods( + /* Wraps the static sqlite3_api_methods singleton */ + wasm.exports.sqlite3__wasm_kvvfs_methods() + ); + try{ + /** + Implementations for members of the object referred to by + sqlite3__wasm_kvvfs_methods(). We swap out the native + implementations with these, which use JS Storage for their + backing store. + + The interface docs for these methods are in + src/os_kv.c:kvstorageRead(), kvstorageWrite(), and + kvstorageDelete(). + */ + for(const e of Object.entries({ + xRead: (zClass, zKey, zBuf, nBuf)=>{ + const stack = pstack.pointer, + astack = wasm.scopedAllocPush(); + try { + const zXKey = kvvfsMakeKey(zClass,zKey); + if(!zXKey) return -3/*OOM*/; + const jKey = wasm.cstrToJs(zXKey); + const jV = kvvfsStorage(zClass).getItem(jKey); + if(!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(nBuf<=0) return nV; + else if(1===nBuf){ + wasm.poke(zBuf, 0); + return nV; + } + const zV = wasm.scopedAllocCString(jV); + if(nBuf > nV + 1) nBuf = nV + 1; + wasm.heap8u().copyWithin( + Number(zBuf), Number(zV), wasm.ptr.addn(zV, nBuf,- 1) + ); + wasm.poke(wasm.ptr.add(zBuf, nBuf, -1), 0); + return nBuf - 1; + }catch(e){ + sqlite3.config.error("kvstorageRead()",e); + return -2; + }finally{ + pstack.restore(stack); + wasm.scopedAllocPop(astack); + } + }, + xWrite: (zClass, zKey, zData)=>{ + const stack = pstack.pointer; + try { + const zXKey = kvvfsMakeKey(zClass,zKey); + if(!zXKey) return 1/*OOM*/; + const jKey = wasm.cstrToJs(zXKey); + kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData)); + return 0; + }catch(e){ + sqlite3.config.error("kvstorageWrite()",e); + return capi.SQLITE_IOERR; + }finally{ + pstack.restore(stack); + } + }, + xDelete: (zClass, zKey)=>{ + const stack = pstack.pointer; + try { + const zXKey = kvvfsMakeKey(zClass,zKey); + if(!zXKey) return 1/*OOM*/; + kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey)); + return 0; + }catch(e){ + sqlite3.config.error("kvstorageDelete()",e); + return capi.SQLITE_IOERR; + }finally{ + pstack.restore(stack); + } + } + })){ + kvvfsMethods[kvvfsMethods.memberKey(e[0])] = + wasm.installFunction(kvvfsMethods.memberSignature(e[0]), e[1]); + } + }finally{ + kvvfsMethods.dispose(); + } + }/* Override native sqlite3_api_methods */ + + /** + After initial refactoring to support the use of arbitrary Storage + objects (the interface from which localStorage and sessionStorage + dervie), we will apparently need to override some of the + associated sqlite3_vfs and sqlite3_io_methods members. + + We can call back into the native impls when needed, but we need + to override certain operations here to bypass its strict + db-naming rules (which, funnily enough, are in place because + they're relevant (only) for this browser-side + implementation). Apropos: the port to generic objects would also + make non-persistent kvvfs available in Worker threads and + non-browser builds. + */ + const eventualTodo = 1 || { + vfsMethods:{ + /** + */ + xOpen: function(pProtoVfs,zName,pProtoFile,flags,pOutFlags){}, + xDelete: function(pVfs, zName, iSyncFlag){}, + xAccess:function(pProtoVfs, zPath, flags, pResOut){}, + xFullPathname: function(pVfs, zPath, nOut, zOut){}, + xDlOpen: function(pVfs, zFilename){}, + xSleep: function(pVfs,usec){}, + xCurrentTime: function(pVfs,pOutDbl){}, + xCurrentTimeInt64: function(pVfs,pOutI64){}, + 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; + } + }, + + /** + kvvfs has separate impls for some of the I/O methods, + depending on whether it's a db or journal file. + */ + ioMethods:{ + db:{ + xClose: function(pFile){}, + 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){} + }, + jrnl:{ + xClose: todoIOMethodsDb.xClose, + 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: todoIOMethodsDb.xLock, + xUnlock: todoIOMethodsDb.xUnlock, + xCheckReservedLock: todoIOMethodsDb.xCheckReservedLock, + xFileControl: function(pFile,iOp,pArg){}, + xSectorSize: todoIOMethodsDb.xSectorSize, + xDeviceCharacteristics: todoIOMethodsDb.xDeviceCharacteristics + } + } + }/*eventualTodo*/; + + if(sqlite3?.oo1?.DB){ + /** + Functionally equivalent to DB(storageName,'c','kvvfs') except + that it throws if the given storage name is not one of 'local' + or 'session'. + + As of version 3.46, the argument may optionally be an options + object in the form: + + { + filename: 'session'|'local', + ... etc. (all options supported by the DB ctor) + } + + noting that the 'vfs' option supported by main DB + constructor is ignored here: the vfs is always 'kvvfs'. + */ + sqlite3.oo1.JsStorageDb = function( + storageName = sqlite3.oo1.JsStorageDb.defaultStorageName + ){ + const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...arguments); + storageName = opt.filename; + if('session'!==storageName && 'local'!==storageName){ + toss3("JsStorageDb db name must be one of 'session' or 'local'."); + } + opt.vfs = 'kvvfs'; + sqlite3.oo1.DB.dbCtorHelper.call(this, opt); + }; + sqlite3.oo1.JsStorageDb.defaultStorageName = 'session'; + const jdb = sqlite3.oo1.JsStorageDb; + jdb.prototype = Object.create(sqlite3.oo1.DB.prototype); + /** Equivalent to sqlite3_js_kvvfs_clear(). */ + jdb.clearStorage = capi.sqlite3_js_kvvfs_clear; + /** + Clears this database instance's storage or throws if this + instance has been closed. Returns the number of + database blocks which were cleaned up. + */ + jdb.prototype.clearStorage = function(){ + return jdb.clearStorage(this.affirmDbOpen().filename); + }; + /** Equivalent to sqlite3_js_kvvfs_size(). */ + jdb.storageSize = capi.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. + */ + jdb.prototype.storageSize = function(){ + return jdb.storageSize(this.affirmDbOpen().filename); + }; + }/*sqlite3.oo1.JsStorageDb*/ + +})/*globalThis.sqlite3ApiBootstrap.initializers*/; +//#endif not omit-kvvfs diff --git a/manifest b/manifest index b2773d549f..eaff89674f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Enhance\sALTER\sTABLE\sto\ssupport\sadding\sand\sremoving\sNOT\sNULL\sand\sCHECK\nconstraints. -D 2025-11-21T14:15:56.853 +C Add\snew\sfile\smissing\sfrom\s[3c4061428544]. +D 2025-11-21T15:03:38.538 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -600,6 +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 f44a4396b2305b27dc81c00a1629fe4f615520f7fb11ed4e606255e365956ba2 F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 26cb41d5a62f46a106b6371eb00fef02de3cdbfaa51338ba087a45f53028e0d0 F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js aa330fa0e8ef35cbd92eb0d52e05fbaa07e61540c5cb164e693c82428ce1d763 F ext/wasm/api/sqlite3-vtab-helper.c-pp.js 9097074724172e31e56ce20ccd7482259cf72a76124213cbc9469d757676da86 @@ -2177,9 +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 e1ba175124be83da4db6d5d4e583b9b3b7dcb82e983c6f185d590469dcdc3139 57555d75daa2ee8345f8329749841a322b3e57679e5c4899ef749c8bc814812d -R a08830703de06847124f417f6313a8fd -T +closed 57555d75daa2ee8345f8329749841a322b3e57679e5c4899ef749c8bc814812d -U drh -Z c52ca5465c06951a7373da17d60cecb6 +P 895498e4431e02cff65a5d96db22f0b0cb9c96aedf1e3cdcdeb3c34c6fec432b +R ce0350baf4d1de88228e71ca89be9093 +U stephan +Z 923ff40315e60b496c7ed96ee0be8e80 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 47c7899bd5..675d8849ad 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -895498e4431e02cff65a5d96db22f0b0cb9c96aedf1e3cdcdeb3c34c6fec432b +41f94eca01d8317364aa60ddd8e5fe3cd21a215040ef271a157d450a914139d0 -- 2.47.3