| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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){} |
| }; |
| |
|
|
| }); |
| |
| |
|
|