From: stephan Date: Tue, 25 Nov 2025 16:41:56 +0000 (+0000) Subject: Add the sqlite3.kvvfs namespace for the new kvvfs APIs instead of adding more sqlite3... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e62055f27498c24d321896ce4bbc64e45928e4cf;p=thirdparty%2Fsqlite.git Add the sqlite3.kvvfs namespace for the new kvvfs APIs instead of adding more sqlite3_js_kvvfs_...() methods. Reinstate that clearing kvvfs storage is illegal when db handles are opened, solely for sanity's sake (they can actually recover from that but supporting such use feels ill-advised). FossilOrigin-Name: 02793c5905e6b99379cd5ad6bfe1eb6cccf839da081fc174dce7b06245e212fb --- diff --git a/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js b/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js index c161a3a4ba..6bf7371cb1 100644 --- a/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js @@ -876,22 +876,17 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - 'session' - 'local' - - An empty string means both of 'local' and 'session' storage. + - '' - see below. - A transient kvvfs storage object name. In the first two cases, only sessionStorage resp. localStorage is cleared. If passed an an empty string (the default) then its - behavior depends on: + behavior depends on the second argument: - - If called in the main thread an empty stream means to act on - both localStorage and sessionStorage. Only storage keys which - match the pattern used by kvvfs are cleared: any other - client-side data are retained. Version 1 of this API always - behaves this way. (This is backwards-compatibility behavior to - account for an admitted misuse of "".) - - - If called from a thread where globalThis.Storage is not available - then an empty string is considered to be a legal storage unit name. + If emptyIsAName is false (the default) then an empty string means + both of 'local' and 'session' storage. (For backwards + compatibility.) if emptyIsAName is true then the empty string is + treated as a storage name instead. Returns the number of entries cleared. @@ -906,25 +901,23 @@ 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',''). - - In threads where local/sessionStorage are not available, an - empty string argument is treated as a storage unit name. + - The second argument was added. - 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=""){ - if( !which && cache.storagePool.local ){ + capi.sqlite3_js_kvvfs_clear = function callee(which="", emptyIsAName=false){ + if( !which && !emptyIsAName && cache.storagePool.local ){ return callee('local') + callee('session'); } const store = storageForZClass(which); if( !store ) return 0; const keyPrefix = store.prefix; - /*undecided: if( store.files.length ){ toss3(capi.SQLITE_ACCESS, "Cannot clear in-use database storage."); - }*/ + } const s = store.storage; const toRm = [] /* keys to remove */; let i, n = s.length; @@ -943,9 +936,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ This routine guesses the approximate amount of storage used by the given kvvfs back-end. - The 'which' argument is as documented for - sqlite3_js_kvvfs_clear(), only the operation this performs is - different: + Its arguments are as documented for sqlite3_js_kvvfs_clear(), + only the operation this performs is different. The returned value is twice the "length" value of every matching key and value, noting that JavaScript stores each character in 2 @@ -957,8 +949,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ unspecified and may include per-entry overhead invisible to clients. */ - capi.sqlite3_js_kvvfs_size = function callee(which=""){ - if( !which && cache.storagePool.local ){ + capi.sqlite3_js_kvvfs_size = function callee(which="", emptyIsAName=false){ + if( !which && !emptyIsAName ){ return callee('local') + callee('session'); } const store = storageForZClass(which); @@ -1024,7 +1016,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ Added in version 3.?? (tenatively 3.52). */ - capi.sqlite3_js_kvvfs_export = function(storageName,includeJournal=true){ + const sqlite3_js_kvvfs_export = function(storageName,includeJournal=true){ const store = storageForZClass(storageName); if( !store ){ toss3(capi.SQLITE_NOTFOUND, @@ -1071,7 +1063,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ (v)=>rc.pages.push(pages[v]) ); return rc; - }/* capi.sqlite3_js_kvvfs_export */; + }/* sqlite3_js_kvvfs_export */; /** INCOMPLETE. This interface is subject to change. @@ -1099,7 +1091,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ exp.name refers to a new storage name then if it throws, the name does not get installed. */ - capi.sqlite3_js_kvvfs_import = function(exp, overwrite=false){ + const sqlite3_js_kvvfs_import = function(exp, overwrite=false){ if( !exp?.timestamp || !exp.name || undefined===exp.size @@ -1124,7 +1116,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); + capi.sqlite3_js_kvvfs_clear(exp.name, true); }else{ store = newStorageObj(exp.name); //warn("Installing new storage:",store); @@ -1142,7 +1134,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ if( isNew ) installStorageAndJournal(store); }catch(e){ if( !isNew ){ - try{capi.sqlite3_js_kvvfs_clear(exp.name);} + try{capi.sqlite3_js_kvvfs_clear(exp.name, true);} catch(ee){/*ignored*/} } throw e; @@ -1150,6 +1142,24 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ return this; }; + /** + 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. + */ + const sqlite3_js_kvvfs_reserve = function(name){ + let store = storageForZClass(name); + if( store ){ + ++store.refc; + return; + } + validateStorageName(name); + installStorageAndJournal(newStorageObj(name)); + }; + /** Adds an event listener to a kvvfs storage object. The idea is that this can be used to asynchronously back up one kvvfs storage @@ -1189,14 +1199,14 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ - 'delete' gets the string-type key of the deleted record. */ - capi.sqlite3_js_kvvfs_listen = function(opt){ + const sqlite3_js_kvvfs_listen = function(opt){ if( !opt || 'object'!==typeof opt ){ toss3(capi.SQLITE_MISUSE, "Expecting a listener object."); } let store = storageForZClass(opt.storage); if( !store ){ if( opt.storage && opt.reserve ){ - capi.sqlite3_js_kvvfs_reserve(opt.storage); + sqlite3_js_kvvfs_reserve(opt.storage); store = storageForZClass(opt.storage); util.assert(store, "Unexpectedly cannot fetch reserved storage " @@ -1218,31 +1228,13 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ This has no side effects if opt is invalid or is not a match for any listeners. */ - capi.sqlite3_js_kvvfs_unlisten = function(opt){ + const sqlite3_js_kvvfs_unlisten = function(opt){ const store = storageForZClass(opt?.storage); if( store && opt.events ){ store.listeners = store.listeners.filter((v)=>v!==opt.events); } }; - /** - 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. - */ - capi.sqlite3_js_kvvfs_reserve = function(name){ - let store = storageForZClass(name); - if( store ){ - ++store.refc; - return; - } - validateStorageName(name); - installStorageAndJournal(newStorageObj(name)); - }; - if(sqlite3?.oo1?.DB){ /** Functionally equivalent to DB(storageName,'c','kvvfs') except @@ -1285,7 +1277,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ database blocks which were cleaned up. */ jdb.prototype.clearStorage = function(){ - return jdb.clearStorage(this.affirmOpen().filename); + return jdb.clearStorage(this.affirmOpen().filename, true); }; /** Equivalent to sqlite3_js_kvvfs_size(). */ jdb.storageSize = capi.sqlite3_js_kvvfs_size; @@ -1294,7 +1286,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ up in its storage or throws if this instance has been closed. */ jdb.prototype.storageSize = function(){ - return jdb.storageSize(this.affirmOpen().filename); + return jdb.storageSize(this.affirmOpen().filename, true); }; if( sqlite3.__isUnderTest ){ @@ -1305,5 +1297,22 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ }/* __isUnderTest */ }/*sqlite3.oo1.JsStorageDb*/ + /** + Public interface for kvvfs v2. The capi.sqlite3_js_kvvfs_...() + routines remain in place for v1. Some members of this class proxy + to those functions but use different default argument values in + some cases. + */ + sqlite3.kvvfs = Object.assign(Object.create(null),{ + reserve: sqlite3_js_kvvfs_reserve, + import: sqlite3_js_kvvfs_import, + export: sqlite3_js_kvvfs_export, + listen: sqlite3_js_kvvfs_listen, + unlisten: sqlite3_js_kvvfs_unlisten, + // DIFFERENT DEFAULTS for the second arguments: + size: (which,emptyIsAName=true)=>capi.sqlite3_js_kvvfs_size(which,emptyIsAName), + clear: (which,emptyIsAName=true)=>capi.sqlite3_js_kvvfs_clear(which,emptyIsAName), + }); + })/*globalThis.sqlite3ApiBootstrap.initializers*/; //#endif not omit-kvvfs diff --git a/ext/wasm/tester1.c-pp.js b/ext/wasm/tester1.c-pp.js index 6374755903..9f9ed7e2e3 100644 --- a/ext/wasm/tester1.c-pp.js +++ b/ext/wasm/tester1.c-pp.js @@ -2893,7 +2893,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .assert( globalThis.sessionStorage===x.storage ) .assert( 'kvvfs-session-' === x.keyPrefix ); const filename = this.kvvfsDbFile = 'session'; - const unlink = this.kvvfsUnlink = ()=>JDb.clearStorage(filename); + const unlink = this.kvvfsUnlink = ()=>sqlite3.kvvfs.clear(filename); unlink(); let db = new JDb(filename); try { @@ -2921,7 +2921,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; const filename = 'localThread' /* preinstalled instance */; const JDb = sqlite3.oo1.JsStorageDb; const DB = sqlite3.oo1.DB; - JDb.clearStorage(filename); + sqlite3.kvvfs.clear(filename); let db = new JDb(filename); const sqlSetup = [ 'create table kvvfs(a);', @@ -2929,15 +2929,20 @@ globalThis.sqlite3InitModule = sqlite3InitModule; ]; try { T.assert( 0===db.storageSize(), "expecting 0 storage size" ); - //T.mustThrowMatching(()=>db.clearStorage(), /in-use/); - db.clearStorage(); + T.mustThrowMatching(()=>db.clearStorage(), /in-use/); + //db.clearStorage(); T.assert( 0===db.storageSize(), "expecting 0 storage size" ); db.exec(sqlSetup); T.assert( 0db.clearStorage(), /in-use/); - db.clearStorage(/*wiping everything out from under it*/); - T.assert( 0===db.storageSize(), "expecting 0 storage size" ); - db.exec(sqlSetup/*that actually worked*/); + T.mustThrowMatching(()=>db.clearStorage(), /in-use/); + //db.clearStorage(/*wiping everything out from under it*/); + T.assert( 0{ db.close(); @@ -2946,7 +2951,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; T.assert(3 === db.selectValue('select count(*) from kvvfs')); close(); - const exportDb = capi.sqlite3_js_kvvfs_export; + const exportDb = sqlite3.kvvfs.export; db = new JDb(filename); db.exec('insert into kvvfs(a) values(4),(5),(6)'); T.assert(6 === db.selectValue('select count(*) from kvvfs')); @@ -2967,7 +2972,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; //console.warn("db.dbFilename() =",dbFilename); T.assert(3 === db.selectValue('select count(*) from kvvfs')); console.debug("kvvfs to Object:",exportDb(dbFilename)); - const n = capi.sqlite3_js_kvvfs_size( dbFilename ); + const n = sqlite3.kvvfs.size( dbFilename ); T.assert( n>0, "Db size count failed" ); if( 1 ){ @@ -3015,10 +3020,10 @@ globalThis.sqlite3InitModule = sqlite3InitModule; }, capi.SQLITE_RANGE); try { - const exportDb = capi.sqlite3_js_kvvfs_export; + const exportDb = sqlite3.kvvfs.export; const dbFileRaw = 'file:'+filename+'?vfs=kvvfs&delete-on-close=1'; + sqlite3.kvvfs.clear(filename); db = new DB(dbFileRaw); - capi.sqlite3_js_kvvfs_clear(filename); db.exec(sqlSetup); T.assert(3 === db.selectValue(sqlCount)); @@ -3036,7 +3041,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; finally{ddb.close()} }, /.*no such table: kvvfs.*/); - const importDb = capi.sqlite3_js_kvvfs_import; + const importDb = sqlite3.kvvfs.import; duo = new JDb(filename); T.mustThrowMatching(()=>importDb(exp,true), /.*in use.*/); duo.close(); @@ -3113,7 +3118,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; } } }; - capi.sqlite3_js_kvvfs_listen(listener); + sqlite3.kvvfs.listen(listener); const dbFileRaw = 'file:'+filename+'?vfs=kvvfs&delete-on-close=1'; db = new DB(dbFileRaw); db.exec(sqlSetup); @@ -3125,7 +3130,7 @@ globalThis.sqlite3InitModule = sqlite3InitModule; .assert( counts.write ) .assert( counts.open===counts.close ); const before = JSON.stringify(counts); - capi.sqlite3_js_kvvfs_unlisten(listener); + sqlite3.kvvfs.unlisten(listener); db = new DB(dbFileRaw); db.exec("delete from kvvfs"); db.close(); diff --git a/manifest b/manifest index 064faf88ac..7103ec9a3e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\san\sexplicit\srollback\stest\sfor\skvvfs. -D 2025-11-25T16:07:05.776 +C Add\sthe\ssqlite3.kvvfs\snamespace\sfor\sthe\snew\skvvfs\sAPIs\sinstead\sof\sadding\smore\ssqlite3_js_kvvfs_...()\smethods.\sReinstate\sthat\sclearing\skvvfs\sstorage\sis\sillegal\swhen\sdb\shandles\sare\sopened,\ssolely\sfor\ssanity's\ssake\s(they\scan\sactually\srecover\sfrom\sthat\sbut\ssupporting\ssuch\suse\sfeels\sill-advised). +D 2025-11-25T16:41:56.975 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 5eb75df70a029f042437a161994fa3bc15e38ad5ab1b2f3d56a48907a5563b8d +F ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js c9dcbfb0aca85c11491427c9980174294022a0982f6aaf8c6a8374fa5ca76c83 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 4f60d6b8543132556fb14195bd1cd890c0e2076981b686448bde05509a1f45df +F ext/wasm/tester1.c-pp.js 5030080f96b0f85ce78e3937fffd51e2a54b32d1fd7e6d3bce6c6bf7dcc5646a 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 d391f1ce691fca4504c21a7e3e05a029f247da23d432ee34b0dae14be51d909c -R c1d4fff97440a131249bd90e6bd13e0a +P 2bf31ef8027a3e15887d4dcd26fe09463b5f8852c5ce443f7d07c23d29c37311 +R 30d74c08232d6eedb21ecc8d5c58c72f U stephan -Z f68b904bbc8cb67b750643f6622fc5c8 +Z 2911ac5a013b42fb37b269f1bdfc6178 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b7215ce6da..c62c523ce7 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2bf31ef8027a3e15887d4dcd26fe09463b5f8852c5ce443f7d07c23d29c37311 +02793c5905e6b99379cd5ad6bfe1eb6cccf839da081fc174dce7b06245e212fb