1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-12-20 01:22:32 +03:00
Files
sqlite/ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js
stephan 794a9b48ac Add new file missing from [3c4061428544].
FossilOrigin-Name: 41f94eca01d8317364aa60ddd8e5fe3cd21a215040ef271a157d450a914139d0
2025-11-21 15:03:38 +00:00

334 lines
12 KiB
JavaScript

//#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