| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ |
| | |
| | delete sqlite3.capi.sqlite3_kvvfs_methods; |
| | delete sqlite3.capi.KVVfsFile; |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ |
| | 'use strict'; |
| | const capi = sqlite3.capi, |
| | sqlite3_kvvfs_methods = capi.sqlite3_kvvfs_methods, |
| | KVVfsFile = capi.KVVfsFile, |
| | pKvvfs = sqlite3.capi.sqlite3_vfs_find("kvvfs") |
| |
|
| | |
| | delete capi.sqlite3_kvvfs_methods; |
| | delete capi.KVVfsFile; |
| |
|
| | if( !pKvvfs ) return ; |
| | if( 0 ){ |
| | |
| | |
| | |
| | |
| | capi.sqlite3_vfs_register(pKvvfs, 1); |
| | } |
| |
|
| | const util = sqlite3.util, |
| | wasm = sqlite3.wasm, |
| | toss3 = util.toss3, |
| | hop = (o,k)=>Object.prototype.hasOwnProperty.call(o,k); |
| |
|
| | const kvvfsMethods = new sqlite3_kvvfs_methods( |
| | |
| | wasm.exports.sqlite3__wasm_kvvfs_methods() |
| | ); |
| | util.assert( 32<=kvvfsMethods.$nKeySize, "unexpected kvvfsMethods.$nKeySize: "+kvvfsMethods.$nKeySize); |
| |
|
| | |
| | |
| | |
| | const cache = Object.assign(Object.create(null),{ |
| | |
| | rxJournalSuffix: /-journal$/, |
| | |
| | zKeyJrnl: wasm.allocCString("jrnl"), |
| | |
| | zKeySz: wasm.allocCString("sz"), |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | keySize: kvvfsMethods.$nKeySize, |
| | |
| | |
| | |
| | |
| | buffer: Object.assign(Object.create(null),{ |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | n: kvvfsMethods.$nBufferSize, |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | pool: Object.create(null) |
| | }) |
| | }); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | cache.memBuffer = (id=0)=>cache.buffer.pool[id] ??= wasm.alloc(cache.buffer.n); |
| |
|
| | |
| | cache.memBufferFree = (id)=>{ |
| | const b = cache.buffer.pool[id]; |
| | if( b ){ |
| | wasm.dealloc(b); |
| | delete cache.buffer.pool[id]; |
| | } |
| | }; |
| |
|
| | const noop = ()=>{}; |
| | const debug = sqlite3.__isUnderTest |
| | ? (...args)=>sqlite3.config.debug("kvvfs:", ...args) |
| | : noop; |
| | const warn = (...args)=>sqlite3.config.warn("kvvfs:", ...args); |
| | const error = (...args)=>sqlite3.config.error("kvvfs:", ...args); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | class KVVfsStorage { |
| | #map; |
| | #keys; |
| | #getKeys(){return this.#keys ??= Object.keys(this.#map);} |
| |
|
| | constructor(){ |
| | this.clear(); |
| | } |
| |
|
| | key(n){ |
| | const k = this.#getKeys(); |
| | return n<k.length ? k[n] : null; |
| | } |
| |
|
| | getItem(k){ |
| | return this.#map[k] ?? null; |
| | } |
| |
|
| | setItem(k,v){ |
| | if( !hop(this.#map, k) ){ |
| | this.#keys = null; |
| | } |
| | this.#map[k] = ''+v; |
| | } |
| |
|
| | removeItem(k){ |
| | if( delete this.#map[k] ){ |
| | this.#keys = null; |
| | } |
| | } |
| |
|
| | clear(){ |
| | this.#map = Object.create(null); |
| | this.#keys = null; |
| | } |
| |
|
| | get length() { |
| | return this.#getKeys().length; |
| | } |
| | }; |
| |
|
| | |
| | |
| | const kvvfsIsPersistentName = (v)=>'local'===v || 'session'===v; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const kvvfsKeyPrefix = (v)=>kvvfsIsPersistentName(v) ? 'kvvfs-'+v+'-' : ''; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const validateStorageName = function(n,mayBeJournal=false){ |
| | if( kvvfsIsPersistentName(n) ) return; |
| | const len = (new Blob([n])).size; |
| | if( !len ) toss3(capi.SQLITE_MISUSE, "Empty name is not permitted."); |
| | let maxLen = cache.keySize - 1; |
| | if( cache.rxJournalSuffix.test(n) ){ |
| | if( !mayBeJournal ){ |
| | toss3(capi.SQLITE_MISUSE, |
| | "Storage names may not have a '-journal' suffix."); |
| | } |
| | }else if( ['-wal','-shm'].filter(v=>n.endsWith(v)).length ){ |
| | toss3(capi.SQLITE_MISUSE, |
| | "Storage names may not have a -wal or -shm suffix."); |
| | }else{ |
| | maxLen -= 8 ; |
| | } |
| | if( len > maxLen ){ |
| | toss3(capi.SQLITE_RANGE, "Storage name is too long. Limit =", maxLen); |
| | } |
| | let i; |
| | for( i = 0; i < len; ++i ){ |
| | const ch = n.codePointAt(i); |
| | if( ch<32 ){ |
| | toss3(capi.SQLITE_RANGE, |
| | "Illegal character ("+ch+"d) in storage name:",n); |
| | } |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | const newStorageObj = (name,storage=undefined)=>Object.assign(Object.create(null),{ |
| | |
| | |
| | |
| | |
| | jzClass: name, |
| | |
| | |
| | |
| | |
| | |
| | |
| | refc: 1, |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | deleteAtRefc0: false, |
| | |
| | |
| | |
| | storage: storage || new KVVfsStorage, |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | keyPrefix: kvvfsKeyPrefix(name), |
| | |
| | |
| | |
| | |
| | files: [], |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | listeners: undefined |
| | }); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | const kvvfs = sqlite3.kvvfs = Object.create(null); |
| | if( sqlite3.__isUnderTest ){ |
| | |
| | kvvfs.log = Object.assign(Object.create(null),{ |
| | xOpen: false, |
| | xClose: false, |
| | xWrite: false, |
| | xRead: false, |
| | xSync: false, |
| | xAccess: false, |
| | xFileControl: false, |
| | xRcrdRead: false, |
| | xRcrdWrite: false, |
| | xRcrdDelete: false, |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | const deleteStorage = function(store){ |
| | const other = cache.rxJournalSuffix.test(store.jzClass) |
| | ? store.jzClass.replace(cache.rxJournalSuffix,'') |
| | : store.jzClass+'-journal'; |
| | kvvfs?.log?.xClose |
| | && debug("cleaning up storage handles [", store.jzClass, other,"]",store); |
| | delete cache.storagePool[store.jzClass]; |
| | delete cache.storagePool[other]; |
| | if( !sqlite3.__isUnderTest ){ |
| | |
| | |
| | |
| | |
| | delete store.storage; |
| | delete store.refc; |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | const installStorageAndJournal = (store)=> |
| | cache.storagePool[store.jzClass] = |
| | cache.storagePool[store.jzClass+'-journal'] = store; |
| |
|
| | |
| | |
| | |
| | |
| | const nameOfThisThreadStorage = '.'; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | cache.storagePool = Object.assign(Object.create(null),{ |
| | |
| | [nameOfThisThreadStorage]: newStorageObj(nameOfThisThreadStorage) |
| | }); |
| |
|
| | if( globalThis.Storage ){ |
| | |
| | if( globalThis.localStorage instanceof globalThis.Storage ){ |
| | cache.storagePool.local = newStorageObj('local', globalThis.localStorage); |
| | } |
| | if( globalThis.sessionStorage instanceof globalThis.Storage ){ |
| | cache.storagePool.session = newStorageObj('session', globalThis.sessionStorage); |
| | } |
| | } |
| |
|
| | cache.builtinStorageNames = Object.keys(cache.storagePool); |
| |
|
| | const isBuiltinName = (n)=>cache.builtinStorageNames.indexOf(n)>-1; |
| |
|
| | |
| | for(const k of Object.keys(cache.storagePool)){ |
| | |
| | |
| | |
| | |
| | const orig = cache.storagePool[k]; |
| | cache.storagePool[k+'-journal'] = orig; |
| | } |
| |
|
| | cache.setError = (e=undefined, dfltErrCode=capi.SQLITE_ERROR)=>{ |
| | if( e ){ |
| | cache.lastError = e; |
| | return (e.resultCode | 0) || dfltErrCode; |
| | } |
| | delete cache.lastError; |
| | return 0; |
| | }; |
| |
|
| | cache.popError = ()=>{ |
| | const e = cache.lastError; |
| | delete cache.lastError; |
| | return e; |
| | }; |
| |
|
| | |
| | const catchForNotify = (e)=>{ |
| | warn("kvvfs.listener handler threw:",e); |
| | }; |
| |
|
| | const kvvfsDecode = wasm.exports.sqlite3__wasm_kvvfs_decode; |
| | const kvvfsEncode = wasm.exports.sqlite3__wasm_kvvfs_encode; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const notifyListeners = async function(eventName,store,...args){ |
| | try{ |
| | |
| | if( store.keyPrefix && args[0] ){ |
| | args[0] = args[0].replace(store.keyPrefix,''); |
| | } |
| | let u8enc, z0, z1, wcache; |
| | for(const ear of store.listeners){ |
| | const ev = Object.create(null); |
| | ev.storageName = store.jzClass; |
| | ev.type = eventName; |
| | const decodePages = ear.decodePages; |
| | const f = ear.events[eventName]; |
| | if( f ){ |
| | if( !ear.includeJournal && args[0]==='jrnl' ){ |
| | continue; |
| | } |
| | if( 'write'===eventName && ear.decodePages && +args[0]>0 ){ |
| | |
| | |
| | ev.data = [args[0]]; |
| | if( wcache?.[args[0]] ){ |
| | ev.data[1] = wcache[args[0]]; |
| | continue; |
| | } |
| | u8enc ??= new TextEncoder('utf-8'); |
| | z0 ??= cache.memBuffer(10); |
| | z1 ??= cache.memBuffer(11); |
| | const u = u8enc.encode(args[1]); |
| | const heap = wasm.heap8u(); |
| | heap.set(u, Number(z0)); |
| | heap[wasm.ptr.addn(z0, u.length)] = 0; |
| | const rc = kvvfsDecode(z0, z1, cache.buffer.n); |
| | if( rc>0 ){ |
| | wcache ??= Object.create(null); |
| | wcache[args[0]] |
| | = ev.data[1] |
| | = heap.slice(Number(z1), wasm.ptr.addn(z1,rc)); |
| | }else{ |
| | continue; |
| | } |
| | }else{ |
| | ev.data = args.length |
| | ? ((args.length===1) ? args[0] : args) |
| | : undefined; |
| | } |
| | try{f(ev)?.catch?.(catchForNotify)} |
| | catch(e){ |
| | warn("notifyListeners [",store.jzClass,"]",eventName,e); |
| | } |
| | } |
| | } |
| | }catch(e){ |
| | catchForNotify(e); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | const storageForZClass = (zClass)=> |
| | 'string'===typeof zClass |
| | ? cache.storagePool[zClass] |
| | : cache.storagePool[wasm.cstrToJs(zClass)]; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const fileForDb = function(pDb){ |
| | const stack = wasm.pstack.pointer; |
| | try{ |
| | const pOut = wasm.pstack.allocPtr(); |
| | return wasm.exports.sqlite3_file_control( |
| | pDb, wasm.ptr.null, capi.SQLITE_FCNTL_FILE_POINTER, pOut |
| | ) |
| | ? null |
| | : new KVVfsFile(wasm.peekPtr(pOut)); |
| | }finally{ |
| | wasm.pstack.restore(stack); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | const alertFilesToReload = (store)=>{ |
| | try{ |
| | for( const f of store.files ){ |
| | |
| | |
| | f.$szPage = -1; |
| | f.$szDb = -1n |
| | } |
| | }catch(e){ |
| | error("alertFilesToReload()",store,e); |
| | throw e; |
| | } |
| | }; |
| | |
| |
|
| | const kvvfsMakeKey = wasm.exports.sqlite3__wasm_kvvfsMakeKey; |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const zKeyForStorage = (store, zClass, zKey)=>{ |
| | |
| | return (zClass && store.keyPrefix) ? kvvfsMakeKey(zClass, zKey) : zKey; |
| | }; |
| |
|
| | const jsKeyForStorage = (store,zClass,zKey)=> |
| | wasm.cstrToJs(zKeyForStorage(store, zClass, zKey)); |
| |
|
| | const storageGetDbSize = (store)=>+store.storage.getItem(store.keyPrefix + "sz"); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const pFileHandles = new Map(); |
| |
|
| | |
| | |
| | |
| | const originalMethods = { |
| | vfs: Object.create(null), |
| | ioDb: Object.create(null), |
| | ioJrnl: Object.create(null) |
| | }; |
| |
|
| | |
| | |
| | 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) |
| | ; |
| | const kvvfsInternal = Object.assign(Object.create(null),{ |
| | pFileHandles, |
| | cache, |
| | storageForZClass, |
| | KVVfsStorage, |
| | |
| | |
| | |
| | |
| | |
| | |
| | disablePageSizeChange: true |
| | }); |
| | if( kvvfs.log ){ |
| | |
| | kvvfs.internal = kvvfsInternal; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | const methodOverrides = { |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | recordHandler: { |
| | xRcrdRead: (zClass, zKey, zBuf, nBuf)=>{ |
| | try{ |
| | const jzClass = wasm.cstrToJs(zClass); |
| | const store = storageForZClass(jzClass); |
| | if( !store ) return -1; |
| | const jXKey = jsKeyForStorage(store, zClass, zKey); |
| | kvvfs?.log?.xRcrdRead && warn("xRcrdRead", jzClass, jXKey, nBuf, store ); |
| | const jV = store.storage.getItem(jXKey); |
| | if(null===jV) return -1; |
| | const nV = jV.length |
| | |
| | ; |
| | if( 0 ){ |
| | debug("xRcrdRead", jXKey, store, jV); |
| | } |
| | if(nBuf<=0) return nV; |
| | else if(1===nBuf){ |
| | wasm.poke(zBuf, 0); |
| | return nV; |
| | } |
| | if( nBuf+1<nV ){ |
| | toss3(capi.SQLITE_RANGE, |
| | "xRcrdRead()",jzClass,jXKey, |
| | "input buffer is too small: need", |
| | nV,"but have",nBuf); |
| | } |
| | if( 0 ){ |
| | debug("xRcrdRead", nBuf, zClass, wasm.cstrToJs(zClass), |
| | wasm.cstrToJs(zKey), nV, jV, store); |
| | } |
| | const zV = cache.memBuffer(0); |
| | |
| | const heap = wasm.heap8(); |
| | let i; |
| | for(i = 0; i < nV; ++i){ |
| | heap[wasm.ptr.add(zV,i)] = jV.codePointAt(i) & 0xFF; |
| | } |
| | heap.copyWithin( |
| | Number(zBuf), Number(zV), wasm.ptr.addn(zV, i) |
| | ); |
| | heap[wasm.ptr.add(zBuf, nV)] = 0; |
| | return nBuf; |
| | }catch(e){ |
| | error("kvrecordRead()",e); |
| | cache.setError(e); |
| | return -2; |
| | } |
| | }, |
| |
|
| | xRcrdWrite: (zClass, zKey, zData)=>{ |
| | try { |
| | const store = storageForZClass(zClass); |
| | const jxKey = jsKeyForStorage(store, zClass, zKey); |
| | const jData = wasm.cstrToJs(zData); |
| | kvvfs?.log?.xRcrdWrite && warn("xRcrdWrite",jxKey, store); |
| | store.storage.setItem(jxKey, jData); |
| | store.listeners && 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 jxKey = jsKeyForStorage(store, zClass, zKey); |
| | kvvfs?.log?.xRcrdDelete && warn("xRcrdDelete",jxKey, store); |
| | store.storage.removeItem(jxKey); |
| | store.listeners && notifyListeners('delete', store, jxKey); |
| | return 0; |
| | }catch(e){ |
| | error("kvrecordDelete()",e); |
| | return cache.setError(e, capi.SQLITE_IOERR); |
| | } |
| | } |
| | }, |
| |
|
| | |
| | |
| | |
| | |
| | |
| | vfs:{ |
| | |
| | xOpen: function(pProtoVfs,zName,pProtoFile,flags,pOutFlags){ |
| | cache.popError(); |
| | let zToFree ; |
| | if( 0 ){ |
| | |
| | flags |= capi.SQLITE_OPEN_CREATE; |
| | } |
| | try{ |
| | if( !zName ){ |
| | zToFree = wasm.allocCString(""+pProtoFile+"." |
| | +(Math.random() * 100000 | 0)); |
| | zName = zToFree; |
| | } |
| | const jzClass = wasm.cstrToJs(zName); |
| | kvvfs?.log?.xOpen && debug("xOpen",jzClass,"flags =",flags); |
| | validateStorageName(jzClass, true); |
| | 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."); |
| | } |
| | let s = storageForZClass(jzClass); |
| | if( !s && !(flags & capi.SQLITE_OPEN_CREATE) ){ |
| | toss3(capi.SQLITE_ERROR, "Storage not found:", jzClass); |
| | } |
| | const rc = originalMethods.vfs.xOpen(pProtoVfs, zName, pProtoFile, |
| | flags, pOutFlags); |
| | if( rc ) return rc; |
| | let deleteAt0 = !!(capi.SQLITE_OPEN_DELETEONCLOSE & flags); |
| | if(wasm.isPtr(arguments[1])){ |
| | if(capi.sqlite3_uri_boolean(zName, "delete-on-close", 0)){ |
| | deleteAt0 = true; |
| | } |
| | } |
| | const f = new KVVfsFile(pProtoFile); |
| | util.assert(f.$zClass, "Missing f.$zClass"); |
| | f.addOnDispose(zToFree); |
| | zToFree = undefined; |
| | |
| | if( s ){ |
| | ++s.refc; |
| | |
| | s.files.push(f); |
| | wasm.poke32(pOutFlags, flags); |
| | }else{ |
| | wasm.poke32(pOutFlags, flags | capi.SQLITE_OPEN_CREATE); |
| | util.assert( !f.$isJournal, "Opening a journal before its db? "+jzClass ); |
| | |
| | const nm = jzClass.replace(cache.rxJournalSuffix,''); |
| | s = newStorageObj(nm); |
| | installStorageAndJournal(s); |
| | s.files.push(f); |
| | s.deleteAtRefc0 = deleteAt0; |
| | kvvfs?.log?.xOpen |
| | && debug("xOpen installed storage handle [",nm, nm+"-journal","]", s); |
| | } |
| | pFileHandles.set(pProtoFile, {store: s, file: f, jzClass}); |
| | s.listeners && notifyListeners('open', s, s.files.length); |
| | return 0; |
| | }catch(e){ |
| | warn("xOpen:",e); |
| | return cache.setError(e); |
| | }finally{ |
| | zToFree && wasm.dealloc(zToFree); |
| | } |
| | }, |
| |
|
| | xDelete: function(pVfs, zName, iSyncFlag){ |
| | cache.popError(); |
| | try{ |
| | const jzName = wasm.cstrToJs(zName); |
| | if( cache.rxJournalSuffix.test(jzName) ){ |
| | recordHandler.xRcrdDelete(zName, cache.zKeyJrnl); |
| | } |
| | |
| | |
| | |
| | return 0; |
| | }catch(e){ |
| | warn("xDelete",e); |
| | return cache.setError(e); |
| | } |
| | }, |
| |
|
| | xAccess: function(pProtoVfs, zPath, flags, pResOut){ |
| | cache.popError(); |
| | try{ |
| | const s = storageForZClass(zPath); |
| | const jzPath = s?.jzClass || wasm.cstrToJs(zPath); |
| | if( kvvfs?.log?.xAccess ){ |
| | debug("xAccess",jzPath,"flags =", |
| | flags,"*pResOut =",wasm.peek32(pResOut), |
| | "store =",s); |
| | } |
| | if( !s ){ |
| | |
| | |
| | |
| | |
| | |
| | |
| | try{validateStorageName(jzPath)} |
| | catch(e){ |
| | |
| | wasm.poke32(pResOut, 0); |
| | return 0; |
| | } |
| | } |
| | if( s ){ |
| | const key = s.keyPrefix+ |
| | (cache.rxJournalSuffix.test(jzPath) ? "jrnl" : "1"); |
| | const res = s.storage.getItem(key) ? 0 : 1; |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | wasm.poke32(pResOut, res); |
| | }else{ |
| | wasm.poke32(pResOut, 0); |
| | } |
| | return 0; |
| | }catch(e){ |
| | error('xAccess',e); |
| | return cache.setError(e); |
| | } |
| | }, |
| |
|
| | 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 nOut; |
| | }, |
| |
|
| | 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); |
| | return (e.resultCode | 0) || capi.SQLITE_IOERR; |
| | }catch(e){ |
| | return capi.SQLITE_NOMEM; |
| | }finally{ |
| | wasm.scopedAllocPop(scope); |
| | } |
| | } |
| | return 0; |
| | } |
| |
|
| | |
| | |
| | |
| | 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; |
| | } |
| | |
| | }, |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | ioDb:{ |
| | |
| | xClose: function(pFile){ |
| | cache.popError(); |
| | try{ |
| | const h = pFileHandles.get(pFile); |
| | kvvfs?.log?.xClose && debug("xClose", pFile, h); |
| | if( h ){ |
| | pFileHandles.delete(pFile); |
| | const s = h.store; |
| | s.files = s.files.filter((v)=>v!==h.file); |
| | if( --s.refc<=0 && s.deleteAtRefc0 ){ |
| | deleteStorage(s); |
| | } |
| | originalMethods.ioDb.xClose(pFile); |
| | h.file.dispose(); |
| | s.listeners && notifyListeners('close', s, s.files.length); |
| | }else{ |
| | |
| | } |
| | return 0; |
| | }catch(e){ |
| | error("xClose",e); |
| | return cache.setError(e); |
| | } |
| | }, |
| |
|
| | xFileControl: function(pFile, opId, pArg){ |
| | cache.popError(); |
| | try{ |
| | const h = pFileHandles.get(pFile); |
| | util.assert(h, "Missing KVVfsFile handle"); |
| | kvvfs?.log?.xFileControl && debug("xFileControl",h,'op =',opId); |
| | if( opId===capi.SQLITE_FCNTL_PRAGMA |
| | && kvvfs.internal.disablePageSizeChange ){ |
| | |
| | |
| | const zName = wasm.peekPtr(wasm.ptr.add(pArg, wasm.ptr.size)); |
| | if( "page_size"===wasm.cstrToJs(zName) ){ |
| | kvvfs?.log?.xFileControl |
| | && debug("xFileControl pragma",wasm.cstrToJs(zName)); |
| | const zVal = wasm.peekPtr(wasm.ptr.add(pArg, 2*wasm.ptr.size)); |
| | if( zVal ){ |
| | |
| | |
| | |
| | |
| | kvvfs?.log?.xFileControl |
| | && warn("xFileControl pragma", h, |
| | "NOT setting page size to", wasm.cstrToJs(zVal)); |
| | h.file.$szPage = -1; |
| | return 0; |
| | }else if( h.file.$szPage>0 ){ |
| | kvvfs?.log?.xFileControl && |
| | warn("xFileControl", h, "getting page size",h.file.$szPage); |
| | wasm.pokePtr(pArg, wasm.allocCString(""+h.file.$szPage) |
| | ); |
| | return 0; |
| | } |
| | } |
| | } |
| | const rc = originalMethods.ioDb.xFileControl(pFile, opId, pArg); |
| | if( 0==rc && capi.SQLITE_FCNTL_SYNC===opId ){ |
| | h.store.listeners && notifyListeners('sync', h.store, false); |
| | } |
| | return rc; |
| | }catch(e){ |
| | error("xFileControl",e); |
| | return cache.setError(e); |
| | } |
| | }, |
| |
|
| | xSync: function(pFile,flags){ |
| | cache.popError(); |
| | try{ |
| | const h = pFileHandles.get(pFile); |
| | kvvfs?.log?.xSync && debug("xSync", h); |
| | util.assert(h, "Missing KVVfsFile handle"); |
| | const rc = originalMethods.ioDb.xSync(pFile, flags); |
| | if( 0==rc && h.store.listeners ) notifyListeners('sync', h.store, true); |
| | return rc; |
| | }catch(e){ |
| | error("xSync",e); |
| | return cache.setError(e); |
| | } |
| | }, |
| |
|
| | |
| | |
| | |
| | xRead: function(pFile,pTgt,n,iOff64){ |
| | cache.popError(); |
| | try{ |
| | if( kvvfs?.log?.xRead ){ |
| | const h = pFileHandles.get(pFile); |
| | util.assert(h, "Missing KVVfsFile handle"); |
| | debug("xRead", n, iOff64, h); |
| | } |
| | return originalMethods.ioDb.xRead(pFile, pTgt, n, iOff64); |
| | }catch(e){ |
| | error("xRead",e); |
| | return cache.setError(e); |
| | } |
| | }, |
| | xWrite: function(pFile,pSrc,n,iOff64){ |
| | cache.popError(); |
| | try{ |
| | if( kvvfs?.log?.xWrite ){ |
| | const h = pFileHandles.get(pFile); |
| | util.assert(h, "Missing KVVfsFile handle"); |
| | debug("xWrite", n, iOff64, h); |
| | } |
| | return originalMethods.ioDb.xWrite(pFile, pSrc, n, iOff64); |
| | }catch(e){ |
| | error("xWrite",e); |
| | return cache.setError(e); |
| | } |
| | }, |
| | |
| |
|
| | |
| | xTruncate: function(pFile,i64){}, |
| | xFileSize: function(pFile,pi64Out){}, |
| | xLock: function(pFile,iLock){}, |
| | xUnlock: function(pFile,iLock){}, |
| | xCheckReservedLock: function(pFile,piOut){}, |
| | xSectorSize: function(pFile){}, |
| | xDeviceCharacteristics: function(pFile){} |
| | |
| | }, |
| |
|
| | ioJrnl:{ |
| | |
| | |
| | |
| | xClose: 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, |
| | xSectorSize: true, |
| | xDeviceCharacteristics: true |
| | |
| | } |
| | }; |
| |
|
| | debug("pVfs and friends", pVfs, pIoDb, pIoJrnl, |
| | kvvfsMethods, capi.sqlite3_file.structInfo, |
| | KVVfsFile.structInfo); |
| | try { |
| | util.assert( cache.buffer.n>1024*129, "Heap buffer is not large enough" |
| | ); |
| | for(const e of Object.entries(methodOverrides.recordHandler)){ |
| | |
| | const k = e[0], f = e[1]; |
| | recordHandler[k] = f; |
| | if( 0 ){ |
| | |
| | kvvfsMethods.installMethod(k, f); |
| | }else{ |
| | kvvfsMethods[kvvfsMethods.memberKey(k)] = |
| | wasm.installFunction(kvvfsMethods.memberSignature(k), f); |
| | } |
| | } |
| | for(const e of Object.entries(methodOverrides.vfs)){ |
| | |
| | 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)){ |
| | |
| | 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)){ |
| | |
| | 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 ){ |
| | |
| | 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(); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const sqlite3_js_kvvfs_clear = function callee(which){ |
| | if( ''===which ){ |
| | return callee('local') + callee('session'); |
| | } |
| | const store = storageForZClass(which); |
| | if( !store ) return 0; |
| | if( store.files.length ){ |
| | if( globalThis.localStorage===store.storage |
| | || globalThis.sessionStorage===store.storage ){ |
| | |
| | |
| | }else{ |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | toss3(capi.SQLITE_ACCESS, |
| | "Cannot clear in-use database storage."); |
| | } |
| | } |
| | const s = store.storage; |
| | const toRm = [] ; |
| | let i, n = s.length; |
| | |
| | for( i = 0; i < n; ++i ){ |
| | const k = s.key(i); |
| | |
| | if(!store.keyPrefix || k.startsWith(store.keyPrefix)) toRm.push(k); |
| | } |
| | toRm.forEach((kk)=>s.removeItem(kk)); |
| | |
| | return toRm.length; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const sqlite3_js_kvvfs_size = function callee(which){ |
| | if( ''===which ){ |
| | return callee('local') + callee('session'); |
| | } |
| | const store = storageForZClass(which); |
| | if( !store ) return 0; |
| | const s = store.storage; |
| | let i, sz = 0; |
| | for(i = 0; i < s.length; ++i){ |
| | const k = s.key(i); |
| | if(!store.keyPrefix || k.startsWith(store.keyPrefix)){ |
| | sz += k.length; |
| | sz += s.getItem(k).length; |
| | } |
| | } |
| | return sz * 2 ; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const sqlite3_js_kvvfs_export = function callee(...args){ |
| | let opt; |
| | if( 1===args.length && 'object'===typeof args[0] ){ |
| | opt = args[0]; |
| | }else if(args.length){ |
| | opt = Object.assign(Object.create(null),{ |
| | name: args[0], |
| | |
| | }); |
| | } |
| | const store = opt ? storageForZClass(opt.name) : null; |
| | if( !store ){ |
| | toss3(capi.SQLITE_NOTFOUND, |
| | "There is no kvvfs storage named",opt?.name); |
| | } |
| | |
| | const s = store.storage; |
| | const rc = Object.assign(Object.create(null),{ |
| | name: store.jzClass, |
| | timestamp: Date.now(), |
| | pages: [] |
| | }); |
| | const pages = Object.create(null); |
| | let xpages; |
| | const keyPrefix = store.keyPrefix; |
| | const rxTail = keyPrefix |
| | ? /^kvvfs-[^-]+-(\w+)/ |
| | : undefined; |
| | let i = 0, n = s.length; |
| | for( ; i < n; ++i ){ |
| | const k = s.key(i); |
| | if( !keyPrefix || k.startsWith(keyPrefix) ){ |
| | let kk = (keyPrefix ? rxTail.exec(k) : undefined)?.[1] ?? k; |
| | switch( kk ){ |
| | case 'jrnl': |
| | if( opt.includeJournal ) rc.journal = s.getItem(k); |
| | break; |
| | case 'sz': |
| | rc.size = +s.getItem(k); |
| | break; |
| | default: |
| | kk = +kk ; |
| | if( !util.isInt32(kk) || kk<=0 ){ |
| | toss3(capi.SQLITE_RANGE, "Malformed kvvfs key: "+k); |
| | } |
| | if( opt.decodePages ){ |
| | const spg = s.getItem(k), |
| | n = spg.length, |
| | z = cache.memBuffer(0), |
| | zDec = cache.memBuffer(1), |
| | heap = wasm.heap8u(); |
| | let i = 0; |
| | for( ; i < n; ++i ){ |
| | heap[wasm.ptr.add(z, i)] = spg.codePointAt(i) & 0xff; |
| | } |
| | heap[wasm.ptr.add(z, i)] = 0; |
| | |
| | const nDec = kvvfsDecode( |
| | z, zDec, cache.buffer.n |
| | ); |
| | |
| | pages[kk] = heap.slice(Number(zDec), wasm.ptr.addn(zDec, nDec)); |
| | }else{ |
| | pages[kk] = s.getItem(k); |
| | } |
| | break; |
| | } |
| | } |
| | } |
| | if( opt.decodePages ) cache.memBufferFree(1); |
| | |
| | |
| | |
| | Object.keys(pages).map((v)=>+v).sort().forEach( |
| | (v)=>rc.pages.push(pages[v]) |
| | ); |
| | return rc; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const sqlite3_js_kvvfs_import = function(exp, overwrite=false){ |
| | if( !exp?.timestamp |
| | || !exp.name |
| | || undefined===exp.size |
| | || !Array.isArray(exp.pages) ){ |
| | toss3(capi.SQLITE_MISUSE, "Malformed export object."); |
| | }else if( !exp.size |
| | || (exp.size !== (exp.size | 0)) |
| | |
| | || exp.size>=0x7fffffff ){ |
| | toss3(capi.SQLITE_RANGE, "Invalid db size: "+exp.size); |
| | } |
| |
|
| | validateStorageName(exp.name); |
| | let store = storageForZClass(exp.name); |
| | const isNew = !store; |
| | if( store ){ |
| | if( !overwrite ){ |
| | |
| | toss3(capi.SQLITE_ACCESS, |
| | "Storage '"+exp.name+"' already exists and", |
| | "overwrite was not specified."); |
| | }else if( !store.files || !store.jzClass ){ |
| | toss3(capi.SQLITE_ERROR, |
| | "Internal storage object", exp.name,"seems to be malformed."); |
| | }else if( store.files.length ){ |
| | toss3(capi.SQLITE_IOERR_ACCESS, |
| | "Cannot import db storage while it is in use."); |
| | } |
| | sqlite3_js_kvvfs_clear(exp.name); |
| | }else{ |
| | store = newStorageObj(exp.name); |
| | |
| | } |
| | |
| | |
| | const keyPrefix = kvvfsKeyPrefix(exp.name); |
| | let zEnc; |
| | try{ |
| | |
| | ; |
| | const s = store.storage; |
| | s.setItem(keyPrefix+'sz', exp.size); |
| | if( exp.journal ) s.setItem(keyPrefix+'jrnl', exp.journal); |
| | if( exp.pages[0] instanceof Uint8Array ){ |
| | |
| | |
| | exp.pages.forEach((u,ndx)=>{ |
| | const n = u.length; |
| | if( 0 && cache.fixedPageSize !== n ){ |
| | util.toss3(capi.SQLITE_RANGE,"Unexpected page size:", n); |
| | } |
| | zEnc ??= cache.memBuffer(1); |
| | const zBin = cache.memBuffer(0), |
| | heap = wasm.heap8u(); |
| | |
| | |
| | |
| | heap.set(u, Number(zBin)); |
| | heap[wasm.ptr.addn(zBin,n)] = 0; |
| | const rc = kvvfsEncode(zBin, n, zEnc); |
| | util.assert( rc < cache.buffer.n, |
| | "Impossibly long output - possibly smashed the heap" ); |
| | util.assert( 0===wasm.peek8(wasm.ptr.add(zEnc,rc)), |
| | "Expecting NUL-terminated encoded output" ); |
| | const jenc = wasm.cstrToJs(zEnc); |
| | |
| | s.setItem(keyPrefix+(ndx+1), jenc); |
| | }); |
| | }else if( exp.pages[0] ){ |
| | |
| | exp.pages.forEach((v,ndx)=>s.setItem(keyPrefix+(ndx+1), v)); |
| | } |
| | if( isNew ) installStorageAndJournal(store); |
| | }catch{ |
| | if( !isNew ){ |
| | try{sqlite3_js_kvvfs_clear(exp.name);}catch(ee){} |
| | } |
| | }finally{ |
| | if( zEnc ) cache.memBufferFree(1); |
| | } |
| | return this; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const sqlite3_js_kvvfs_reserve = function(name){ |
| | let store = storageForZClass(name); |
| | if( store ){ |
| | ++store.refc; |
| | return; |
| | } |
| | validateStorageName(name); |
| | installStorageAndJournal(newStorageObj(name)); |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const sqlite3_js_kvvfs_unlink = function(name){ |
| | const store = storageForZClass(name); |
| | if( !store |
| | || kvvfsIsPersistentName(store.jzClass) |
| | || isBuiltinName(store.jzClass) |
| | || cache.rxJournalSuffix.test(name) ) return false; |
| | if( store.refc > store.files.length || 0===store.files.length ){ |
| | if( --store.refc<=0 ){ |
| | |
| | deleteStorage(store); |
| | } |
| | return true; |
| | } |
| | return false; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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 ){ |
| | sqlite3_js_kvvfs_reserve(opt.storage); |
| | store = storageForZClass(opt.storage); |
| | util.assert(store, |
| | "Unexpectedly cannot fetch reserved storage " |
| | +opt.storage); |
| | }else{ |
| | toss3(capi.SQLITE_NOTFOUND,"No such storage:",opt.storage); |
| | } |
| | } |
| | if( opt.events ){ |
| | (store.listeners ??= []).push(opt); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const sqlite3_js_kvvfs_unlisten = function(opt){ |
| | const store = storageForZClass(opt?.storage); |
| | if( store?.listeners && opt.events ){ |
| | const n = store.listeners.length; |
| | store.listeners = store.listeners.filter((v)=>v!==opt); |
| | const rc = n>store.listeners.length; |
| | if( !store.listeners.length ){ |
| | |
| | store.listeners = undefined; |
| | } |
| | return rc; |
| | } |
| | return false; |
| | }; |
| |
|
| | sqlite3.kvvfs.reserve = sqlite3_js_kvvfs_reserve; |
| | sqlite3.kvvfs.import = sqlite3_js_kvvfs_import; |
| | sqlite3.kvvfs.export = sqlite3_js_kvvfs_export; |
| | sqlite3.kvvfs.unlink = sqlite3_js_kvvfs_unlink; |
| | sqlite3.kvvfs.listen = sqlite3_js_kvvfs_listen; |
| | sqlite3.kvvfs.unlisten = sqlite3_js_kvvfs_unlisten; |
| | sqlite3.kvvfs.exists = (name)=>!!storageForZClass(name); |
| | sqlite3.kvvfs.estimateSize = sqlite3_js_kvvfs_size; |
| | sqlite3.kvvfs.clear = sqlite3_js_kvvfs_clear; |
| |
|
| |
|
| | if( globalThis.Storage ){ |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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){ |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const DB = sqlite3.oo1.DB; |
| | sqlite3.oo1.JsStorageDb = function( |
| | storageName = sqlite3.oo1.JsStorageDb.defaultStorageName |
| | ){ |
| | const opt = DB.dbCtorHelper.normalizeArgs(...arguments); |
| | opt.vfs = 'kvvfs'; |
| | if( 0 ){ |
| | |
| | if( opt.flags ) opt.flags = 'cw'+opt.flags; |
| | else opt.flags = 'cw'; |
| | } |
| | switch( opt.filename ){ |
| | |
| | |
| | |
| | case ":sessionStorage:": opt.filename = 'session'; break; |
| | case ":localStorage:": opt.filename = 'local'; break; |
| | } |
| | const m = /(file:(\/\/)?)([^?]+)/.exec(opt.filename); |
| | validateStorageName( m ? m[3] : opt.filename); |
| | DB.dbCtorHelper.call(this, opt); |
| | }; |
| | sqlite3.oo1.JsStorageDb.defaultStorageName |
| | = cache.storagePool.session ? 'session' : nameOfThisThreadStorage; |
| | const jdb = sqlite3.oo1.JsStorageDb; |
| | jdb.prototype = Object.create(DB.prototype); |
| | jdb.clearStorage = sqlite3_js_kvvfs_clear; |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | jdb.prototype.clearStorage = function(){ |
| | return jdb.clearStorage(this.affirmOpen().dbFilename(), true); |
| | }; |
| | |
| | jdb.storageSize = sqlite3_js_kvvfs_size; |
| | |
| | |
| | |
| | |
| | jdb.prototype.storageSize = function(){ |
| | return jdb.storageSize(this.affirmOpen().dbFilename(), true); |
| | }; |
| | } |
| | |
| |
|
| | if( sqlite3.__isUnderTest && sqlite3.vtab ){ |
| | |
| | |
| | |
| | |
| | |
| | const cols = Object.assign(Object.create(null),{ |
| | rowid: {type: 'INTEGER'}, |
| | name: {type: 'TEXT'}, |
| | nRef: {type: 'INTEGER'}, |
| | nOpen: {type: 'INTEGER'}, |
| | isTransient: {type: 'INTEGER'}, |
| | dbSize: {type: 'INTEGER'} |
| | }); |
| | Object.keys(cols).forEach((v,i)=>cols[v].colId = i); |
| |
|
| | const VT = sqlite3.vtab; |
| | const ProtoCursor = Object.assign(Object.create(null),{ |
| | row: function(){ |
| | return cache.storagePool[this.names[this.rowid]]; |
| | } |
| | }); |
| | Object.assign(Object.create(ProtoCursor),{ |
| | rowid: 0, |
| | names: Object.keys(cache.storagePool) |
| | .filter(v=>!cache.rxJournalSuffix.test(v)) |
| | }); |
| | const cursorState = function(cursor, reset){ |
| | const o = (cursor instanceof capi.sqlite3_vtab_cursor) |
| | ? cursor |
| | : VT.xCursor.get(cursor); |
| | if( reset || !o.vTabState ){ |
| | o.vTabState = Object.assign(Object.create(ProtoCursor),{ |
| | rowid: 0, |
| | names: Object.keys(cache.storagePool) |
| | .filter(v=>!cache.rxJournalSuffix.test(v)) |
| | }); |
| | } |
| | return o.vTabState; |
| | }; |
| |
|
| | const dbg = 1 ? ()=>{} : (...args)=>debug("vtab",...args); |
| |
|
| | const theModule = function f(){ |
| | return f.mod ??= new sqlite3.capi.sqlite3_module().setupModule({ |
| | catchExceptions: true, |
| | methods: { |
| | xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){ |
| | dbg("xConnect"); |
| | try{ |
| | const xcol = []; |
| | Object.keys(cols).forEach((k)=>{ |
| | xcol.push(k+" "+cols[k].type); |
| | }); |
| | const rc = capi.sqlite3_declare_vtab( |
| | pDb, "CREATE TABLE ignored("+xcol.join(',')+")" |
| | ); |
| | if(0===rc){ |
| | const t = VT.xVtab.create(ppVtab); |
| | util.assert( |
| | (t === VT.xVtab.get(wasm.peekPtr(ppVtab))), |
| | "output pointer check failed" |
| | ); |
| | } |
| | return rc; |
| | }catch(e){ |
| | return VT.xErrror('xConnect', e, capi.SQLITE_ERROR); |
| | } |
| | }, |
| | xCreate: wasm.ptr.null, |
| | |
| | xDisconnect: function(pVtab){ |
| | dbg("xDisconnect",...arguments); |
| | VT.xVtab.dispose(pVtab); |
| | return 0; |
| | }, |
| | xOpen: function(pVtab, ppCursor){ |
| | dbg("xOpen",...arguments); |
| | VT.xCursor.create(ppCursor); |
| | return 0; |
| | }, |
| | xClose: function(pCursor){ |
| | dbg("xClose",...arguments); |
| | const c = VT.xCursor.unget(pCursor); |
| | delete c.vTabState; |
| | c.dispose(); |
| | return 0; |
| | }, |
| | xNext: function(pCursor){ |
| | dbg("xNext",...arguments); |
| | const c = VT.xCursor.get(pCursor); |
| | ++cursorState(c).rowid; |
| | return 0; |
| | }, |
| | xColumn: function(pCursor, pCtx, iCol){ |
| | dbg("xColumn",...arguments); |
| | |
| | const st = cursorState(pCursor); |
| | const store = st.row(); |
| | util.assert(store, "Unexpected xColumn call"); |
| | switch(iCol){ |
| | case cols.rowid.colId: |
| | capi.sqlite3_result_int(pCtx, st.rowid); |
| | break; |
| | case cols.name.colId: |
| | capi.sqlite3_result_text(pCtx, store.jzClass, -1, capi.SQLITE_TRANSIENT); |
| | break; |
| | case cols.nRef.colId: |
| | capi.sqlite3_result_int(pCtx, store.refc); |
| | break; |
| | case cols.nOpen.colId: |
| | capi.sqlite3_result_int(pCtx, store.files.length); |
| | break; |
| | case cols.isTransient.colId: |
| | capi.sqlite3_result_int(pCtx, !!store.deleteAtRefc0); |
| | break; |
| | case cols.dbSize.colId: |
| | capi.sqlite3_result_int(pCtx, storageGetDbSize(store)); |
| | break; |
| | default: |
| | capi.sqlite3_result_error(pCtx, "Invalid column id: "+iCol); |
| | return capi.SQLITE_RANGE; |
| | } |
| | return 0; |
| | }, |
| | xRowid: function(pCursor, ppRowid64){ |
| | dbg("xRowid",...arguments); |
| | const st = cursorState(pCursor); |
| | VT.xRowid(ppRowid64, st.rowid); |
| | return 0; |
| | }, |
| | xEof: function(pCursor){ |
| | const st = cursorState(pCursor); |
| | dbg("xEof?="+(!st.row()),...arguments); |
| | return !st.row(); |
| | }, |
| | xFilter: function(pCursor, idxNum, idxCStr, |
| | argc, argv){ |
| | dbg("xFilter",...arguments); |
| | const st = cursorState(pCursor, true); |
| | return 0; |
| | }, |
| | xBestIndex: function(pVtab, pIdxInfo){ |
| | dbg("xBestIndex",...arguments); |
| | |
| | const pii = new capi.sqlite3_index_info(pIdxInfo); |
| | pii.$estimatedRows = cache.storagePool.size; |
| | pii.$estimatedCost = 1.0; |
| | pii.dispose(); |
| | return 0; |
| | } |
| | } |
| | }); |
| | }; |
| |
|
| | sqlite3.kvvfs.create_module = function(pDb, name="sqlite_kvvfs"){ |
| | return capi.sqlite3_create_module(pDb, name, theModule(), |
| | wasm.ptr.null); |
| | }; |
| |
|
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | kvvfs.Listener = class KvvfsListener { |
| | #store; |
| | #listener; |
| |
|
| | constructor(opt){ |
| | this.#listenTo(opt); |
| | } |
| |
|
| | #event(ev){ |
| | switch(ev.type){ |
| | case 'open': this.onOpen(ev.data); break; |
| | case 'close': this.onClose(ev.data); break; |
| | case 'sync': this.onSync(ev.data); break; |
| | case 'delete': |
| | switch(ev.data){ |
| | case 'jrnl': break; |
| | default:{ |
| | const n = +ev.data; |
| | util.assert( n>0, "Expecting positive db page number" ); |
| | this.onPageChange(n, null); |
| | break; |
| | } |
| | } |
| | break; |
| | case 'write':{ |
| | const key = ev.data[0], val = ev.data[1]; |
| | switch( key ){ |
| | case 'jrnl': break; |
| | case 'sz':{ |
| | const sz = +val; |
| | util.assert( sz>0, "Expecting a db page number" ); |
| | this.onSizeChange(sz); |
| | break; |
| | } |
| | default: |
| | T.assert( +key>0, "Expecting a positive db page number" ); |
| | this.onPageChange(+key, val); |
| | break; |
| | } |
| | break; |
| | } |
| | } |
| | } |
| |
|
| | #listenTo(opt){ |
| | if(this.#listener){ |
| | sqlite3_js_kvvfs_unlisten(this.#listener); |
| | this.#listener = undefined; |
| | } |
| | const eventHandler = async function(ev){this.event(ev)}.bind(this); |
| | const li = Object.assign( |
| | { |
| | reserve: false, |
| | includeJournal: false, |
| | decodePages: false, |
| | storage: null |
| | }, |
| | (opt||{}), |
| | { |
| | events: Object.assign(Object.create(null),{ |
| | 'open': eventHandler, |
| | 'close': eventHandler, |
| | 'write': eventHandler, |
| | 'delete': eventHandler, |
| | 'sync': eventHandler |
| | }) |
| | } |
| | ); |
| | sqlite3_js_kvvfs_listen(li); |
| | this.#listener = li; |
| | } |
| |
|
| | async onSizeChange(sz){} |
| | async onPageChange(pgNo,content){} |
| | async onSync(mode){} |
| | async onOpen(count){} |
| | async onClose(count){} |
| | }; |
| | |
| |
|
| | }); |
| | |
| | |
| |
|