mirror of
https://github.com/sqlite/sqlite.git
synced 2025-12-20 01:22:32 +03:00
334 lines
12 KiB
JavaScript
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
|