const optionDefaults = Object.assign(Object.create(null),{
name: 'opfs-sahpool',
- directory: undefined,
+ directory: undefined /* derived from .name */,
initialCapacity: 6,
clearOnInit: false,
verbosity: 2 /*3+ == everything, 2 == warnings+errors, 1 == errors only*/
return rc;
}
+ getFileForPtr(ptr){
+ return this.mapIdToFile.get(ptr);
+ }
+ setFileForPtr(ptr,file){
+ if(file) this.mapIdToFile.set(ptr, file);
+ else this.mapIdToFile.delete(ptr);
+ }
+
+ hasFilename(name){
+ return this.mapFilenameToSAH.has(name)
+ }
+
+ getSAHForPath(path){
+ return this.mapFilenameToSAH.get(path);
+ }
}/*class OpfsSAHPool*/;
the default directory. If no directory is explicitly provided
then a directory name is synthesized from the `name` option.
+ Peculiarities of this VFS:
+
+ - Paths given to it _must_ be absolute. Relative paths will not
+ be properly recognized. This is arguably a bug but correcting it
+ requires some hoop-jumping and memory allocation in routines
+ which should not be allocating.
+
The API for the utility object passed on by this function's
Promise, in alphabetical order...
throw new Error("Just testing rejection.");
}
if(initPromises[vfsName]){
- console.warn("Returning same OpfsSAHPool result",options,vfsName,initPromises[vfsName]);
+ //console.warn("Returning same OpfsSAHPool result",options,vfsName,initPromises[vfsName]);
return initPromises[vfsName];
}
if(!globalThis.FileSystemHandle ||
const opfsVfs = new capi.sqlite3_vfs()
.addOnDispose(()=>opfsIoMethods.dispose());
- const promiseReject = (err)=>{
- error("rejecting promise:",err);
- opfsVfs.dispose();
- initPromises[vfsName] = Promise.reject(err);
- throw err;
- };
-
/* We fetch the default VFS so that we can inherit some
methods from it. */
const pDVfs = capi.sqlite3_vfs_find(null);
},
xClose: function(pFile){
thePool.storeErr();
- const file = thePool.mapIdToFile.get(pFile);
+ const file = thePool.getFileForPtr(pFile);
if(file) {
try{
log(`xClose ${file.path}`);
if(file.sq3File) file.sq3File.dispose();
file.sah.flush();
- thePool.mapIdToFile.delete(pFile);
+ thePool.setFileForPtr(pFile,0);
if(file.flags & capi.SQLITE_OPEN_DELETEONCLOSE){
thePool.deletePath(file.path);
}
},
xFileSize: function(pFile,pSz64){
log(`xFileSize`);
- const file = thePool.mapIdToFile.get(pFile);
+ const file = thePool.getFileForPtr(pFile);
const size = file.sah.getSize() - HEADER_OFFSET_DATA;
//log(`xFileSize ${file.path} ${size}`);
wasm.poke64(pSz64, BigInt(size));
xLock: function(pFile,lockType){
log(`xLock ${lockType}`);
thePool.storeErr();
- const file = thePool.mapIdToFile.get(pFile);
+ const file = thePool.getFileForPtr(pFile);
file.lockType = lockType;
return 0;
},
xRead: function(pFile,pDest,n,offset64){
log(`xRead ${n}@${offset64}`);
thePool.storeErr();
- const file = thePool.mapIdToFile.get(pFile);
+ const file = thePool.getFileForPtr(pFile);
log(`xRead ${file.path} ${n} ${offset64}`);
try {
const nRead = file.sah.read(
xSync: function(pFile,flags){
log(`xSync ${flags}`);
thePool.storeErr();
- const file = thePool.mapIdToFile.get(pFile);
+ const file = thePool.getFileForPtr(pFile);
//log(`xSync ${file.path} ${flags}`);
try{
file.sah.flush();
xTruncate: function(pFile,sz64){
log(`xTruncate ${sz64}`);
thePool.storeErr();
- const file = thePool.mapIdToFile.get(pFile);
+ const file = thePool.getFileForPtr(pFile);
//log(`xTruncate ${file.path} ${iSize}`);
try{
file.sah.truncate(HEADER_OFFSET_DATA + Number(sz64));
},
xUnlock: function(pFile,lockType){
log('xUnlock');
- const file = thePool.mapIdToFile.get(pFile);
+ const file = thePool.getFileForPtr(pFile);
file.lockType = lockType;
return 0;
},
xWrite: function(pFile,pSrc,n,offset64){
thePool.storeErr();
- const file = thePool.mapIdToFile.get(pFile);
+ const file = thePool.getFileForPtr(pFile);
log(`xWrite ${file.path} ${n} ${offset64}`);
try{
const nBytes = file.sah.write(
*/
const vfsMethods = {
xAccess: function(pVfs,zName,flags,pOut){
- log(`xAccess ${wasm.cstrToJs(zName)}`);
+ //log(`xAccess ${wasm.cstrToJs(zName)}`);
thePool.storeErr();
try{
- const name = this.getPath(zName);
- wasm.poke32(pOut, thePool.mapFilenameToSAH.has(name) ? 1 : 0);
+ const name = thePool.getPath(zName);
+ wasm.poke32(pOut, thePool.hasFilename(name) ? 1 : 0);
}catch(e){
- /*ignored*/;
+ /*ignored*/
+ wasm.poke32(pOut, 0);
}
return 0;
},
const path = (zName && wasm.peek8(zName))
? thePool.getPath(zName)
: getRandomName();
- let sah = thePool.mapFilenameToSAH.get(path);
+ let sah = thePool.getSAHForPath(path);
if(!sah && (flags & capi.SQLITE_OPEN_CREATE)) {
// File not found so try to create it.
if(thePool.getFileCount() < thePool.getCapacity()) {
// Subsequent methods are only passed the file pointer, so
// map the relevant info we need to that pointer.
const file = {path, flags, sah};
- thePool.mapIdToFile.set(pFile, file);
+ thePool.setFileForPtr(pFile, file);
wasm.poke32(pOutFlags, flags);
file.sq3File = new capi.sqlite3_file(pFile);
file.sq3File.$pMethods = opfsIoMethods.pointer;
log("VFS initialized.");
return poolUtil;
});
- }).catch(promiseReject);
+ }).catch((err)=>{
+ error("rejecting promise:",err);
+ opfsVfs.dispose();
+ return initPromises[vfsName] = Promise.reject(err);
+ });
}/*installOpfsSAHPoolVfs()*/;
}/*sqlite3ApiBootstrap.initializers*/);
}/*kvvfs sqlite3_js_vfs_create_file()*/)
;/* end kvvfs tests */
- ////////////////////////////////////////////////////////////////////////
- T.g('OPFS: Origin-Private File System',
- (sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs")
- || 'requires "opfs" VFS'))
- .t({
- name: 'OPFS db sanity checks',
- test: async function(sqlite3){
- const filename = this.opfsDbFile = 'sqlite3-tester1.db';
- const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs');
- T.assert(pVfs);
- const unlink = this.opfsUnlink =
- (fn=filename)=>{wasm.sqlite3_wasm_vfs_unlink(pVfs,fn)};
- unlink();
- let db = new sqlite3.oo1.OpfsDb(filename);
- try {
- db.exec([
- 'create table p(a);',
- 'insert into p(a) values(1),(2),(3)'
- ]);
- T.assert(3 === db.selectValue('select count(*) from p'));
- db.close();
- db = new sqlite3.oo1.OpfsDb(filename);
- db.exec('insert into p(a) values(4),(5),(6)');
- T.assert(6 === db.selectValue('select count(*) from p'));
- this.opfsDbExport = capi.sqlite3_js_db_export(db);
- T.assert(this.opfsDbExport instanceof Uint8Array)
- .assert(this.opfsDbExport.byteLength>0
- && 0===this.opfsDbExport.byteLength % 512);
- }finally{
- db.close();
- unlink();
- }
- }
- }/*OPFS db sanity checks*/)
- .t({
- name: 'OPFS export/import',
- test: async function(sqlite3){
- let db;
- try {
- const exp = this.opfsDbExport;
- delete this.opfsDbExport;
- capi.sqlite3_js_vfs_create_file("opfs", this.opfsDbFile, exp);
- const db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
- T.assert(6 === db.selectValue('select count(*) from p'));
- }finally{
- if(db) db.close();
- }
- }
- }/*OPFS export/import*/)
- .t({
- name: 'OPFS utility APIs and sqlite3_js_vfs_create_file()',
- test: async function(sqlite3){
- const filename = this.opfsDbFile;
- const pVfs = this.opfsVfs;
- const unlink = this.opfsUnlink;
- T.assert(filename && pVfs && !!unlink);
- delete this.opfsDbFile;
- delete this.opfsVfs;
- delete this.opfsUnlink;
- unlink();
- // Sanity-test sqlite3_js_vfs_create_file()...
- /**************************************************************
- ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended
- for client-side use. It is only for this project's own
- internal use. Its APIs are subject to change or removal at
- any time.
- ***************************************************************/
- const opfs = sqlite3.opfs;
- const fSize = 1379;
- let sh;
- try{
- T.assert(!(await opfs.entryExists(filename)));
- capi.sqlite3_js_vfs_create_file(
- pVfs, filename, null, fSize
- );
- T.assert(await opfs.entryExists(filename));
- let fh = await opfs.rootDirectory.getFileHandle(filename);
- sh = await fh.createSyncAccessHandle();
- T.assert(fSize === await sh.getSize());
- await sh.close();
- sh = undefined;
- unlink();
- T.assert(!(await opfs.entryExists(filename)));
-
- const ba = new Uint8Array([1,2,3,4,5]);
- capi.sqlite3_js_vfs_create_file(
- "opfs", filename, ba
- );
- T.assert(await opfs.entryExists(filename));
- fh = await opfs.rootDirectory.getFileHandle(filename);
- sh = await fh.createSyncAccessHandle();
- T.assert(ba.byteLength === await sh.getSize());
- await sh.close();
- sh = undefined;
- unlink();
-
- T.mustThrowMatching(()=>{
- capi.sqlite3_js_vfs_create_file(
- "no-such-vfs", filename, ba
- );
- }, "SQLITE_NOTFOUND: Unknown sqlite3_vfs name: no-such-vfs");
- }finally{
- if(sh) await sh.close();
- unlink();
- }
-
- // Some sanity checks of the opfs utility functions...
- const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
- const aDir = testDir+'/test/dir';
- T.assert(await opfs.mkdir(aDir), "mkdir failed")
- .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
- .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
- .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
- .assert(!(await opfs.unlink(testDir+'/test/dir')),
- "delete 2b should have failed (dir already deleted)")
- .assert((await opfs.unlink(testDir, true)), "delete 3 failed")
- .assert(!(await opfs.entryExists(testDir)),
- "entryExists(",testDir,") should have failed");
- }
- }/*OPFS util sanity checks*/)
- ;/* end OPFS tests */
-
- ////////////////////////////////////////////////////////////////////////
- T.g('OPFS SyncAccessHandle Pool VFS',
- (sqlite3)=>(hasOpfs() || "requires OPFS APIs"))
- .t({
- name: 'SAH sanity checks',
- test: async function(sqlite3){
- T.assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
- .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) < 0)
- const inst = sqlite3.installOpfsSAHPoolVfs,
- catcher = (e)=>{
- error("Cannot load SAH pool VFS.",
- "This might not be a problem,",
- "depending on the environment.");
- return false;
- };
- let u1, u2;
- const P1 = inst(sahPoolConfig).then(u=>u1 = u).catch(catcher),
- P2 = inst(sahPoolConfig).then(u=>u2 = u).catch(catcher);
- await Promise.all([P1, P2]);
- if(!P1) return;
- T.assert(u1 === u2)
- .assert(sahPoolConfig.name === u1.vfsName)
- .assert(sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
- .assert(u1.getCapacity() >= sahPoolConfig.initialCapacity
- /* If a test fails before we get to nuke the VFS, we
- can have more than the initial capacity on the next
- run. */)
- .assert(u1.getCapacity() + 2 === (await u2.addCapacity(2)))
- .assert(2 === (await u2.reduceCapacity(2)))
- .assert(sqlite3.oo1.OpfsSAHPool.default instanceof Function)
- .assert(sqlite3.oo1.OpfsSAHPool.default ===
- sqlite3.oo1.OpfsSAHPool[sahPoolConfig.name])
- .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) >= 0);
-
- T.assert(0 === u1.getFileCount());
- const DbCtor = sqlite3.oo1.OpfsSAHPool.default;
- const dbName = '/foo.db';
- let db = new DbCtor(dbName);
- T.assert(1 === u1.getFileCount());
- db.exec([
- 'create table t(a);',
- 'insert into t(a) values(1),(2),(3)'
- ]);
- T.assert(1 === u1.getFileCount());
- T.assert(3 === db.selectValue('select count(*) from t'));
- db.close();
- T.assert(1 === u1.getFileCount());
- db = new DbCtor(dbName);
- T.assert(1 === u1.getFileCount());
- db.close();
- T.assert(1 === u1.getFileCount())
- .assert(true === u1.unlink(dbName))
- .assert(false === u1.unlink(dbName))
- .assert(0 === u1.getFileCount());
-
- T.assert(true === await u2.removeVfs())
- .assert(false === await u1.removeVfs())
- .assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name));
-
- let cErr, u3;
- const conf2 = JSON.parse(JSON.stringify(sahPoolConfig));
- conf2.$testThrowInInit = new Error("Testing throwing during init.");
- conf2.name = sahPoolConfig.name+'-err';
- const P3 = await inst(conf2).then(u=>u3 = u).catch((e)=>cErr=e);
- T.assert(P3 === conf2.$testThrowInInit)
- .assert(cErr === P3)
- .assert(undefined === u3)
- .assert(!sqlite3.capi.sqlite3_vfs_find(conf2.name));
- }
- }/*OPFS SAH Pool sanity checks*/)
-
////////////////////////////////////////////////////////////////////////
T.g('Hook APIs')
.t({
}
})/*session API sanity tests*/
;/*end of session API group*/;
+
+ ////////////////////////////////////////////////////////////////////////
+ T.g('OPFS: Origin-Private File System',
+ (sqlite3)=>(sqlite3.capi.sqlite3_vfs_find("opfs")
+ || 'requires "opfs" VFS'))
+ .t({
+ name: 'OPFS db sanity checks',
+ test: async function(sqlite3){
+ const filename = this.opfsDbFile = 'sqlite3-tester1.db';
+ const pVfs = this.opfsVfs = capi.sqlite3_vfs_find('opfs');
+ T.assert(pVfs);
+ const unlink = this.opfsUnlink =
+ (fn=filename)=>{wasm.sqlite3_wasm_vfs_unlink(pVfs,fn)};
+ unlink();
+ let db = new sqlite3.oo1.OpfsDb(filename);
+ try {
+ db.exec([
+ 'create table p(a);',
+ 'insert into p(a) values(1),(2),(3)'
+ ]);
+ T.assert(3 === db.selectValue('select count(*) from p'));
+ db.close();
+ db = new sqlite3.oo1.OpfsDb(filename);
+ db.exec('insert into p(a) values(4),(5),(6)');
+ T.assert(6 === db.selectValue('select count(*) from p'));
+ this.opfsDbExport = capi.sqlite3_js_db_export(db);
+ T.assert(this.opfsDbExport instanceof Uint8Array)
+ .assert(this.opfsDbExport.byteLength>0
+ && 0===this.opfsDbExport.byteLength % 512);
+ }finally{
+ db.close();
+ unlink();
+ }
+ }
+ }/*OPFS db sanity checks*/)
+ .t({
+ name: 'OPFS export/import',
+ test: async function(sqlite3){
+ let db;
+ try {
+ const exp = this.opfsDbExport;
+ delete this.opfsDbExport;
+ capi.sqlite3_js_vfs_create_file("opfs", this.opfsDbFile, exp);
+ const db = new sqlite3.oo1.OpfsDb(this.opfsDbFile);
+ T.assert(6 === db.selectValue('select count(*) from p'));
+ }finally{
+ if(db) db.close();
+ }
+ }
+ }/*OPFS export/import*/)
+ .t({
+ name: 'OPFS utility APIs and sqlite3_js_vfs_create_file()',
+ test: async function(sqlite3){
+ const filename = this.opfsDbFile;
+ const pVfs = this.opfsVfs;
+ const unlink = this.opfsUnlink;
+ T.assert(filename && pVfs && !!unlink);
+ delete this.opfsDbFile;
+ delete this.opfsVfs;
+ delete this.opfsUnlink;
+ unlink();
+ // Sanity-test sqlite3_js_vfs_create_file()...
+ /**************************************************************
+ ATTENTION CLIENT-SIDE USERS: sqlite3.opfs is NOT intended
+ for client-side use. It is only for this project's own
+ internal use. Its APIs are subject to change or removal at
+ any time.
+ ***************************************************************/
+ const opfs = sqlite3.opfs;
+ const fSize = 1379;
+ let sh;
+ try{
+ T.assert(!(await opfs.entryExists(filename)));
+ capi.sqlite3_js_vfs_create_file(
+ pVfs, filename, null, fSize
+ );
+ T.assert(await opfs.entryExists(filename));
+ let fh = await opfs.rootDirectory.getFileHandle(filename);
+ sh = await fh.createSyncAccessHandle();
+ T.assert(fSize === await sh.getSize());
+ await sh.close();
+ sh = undefined;
+ unlink();
+ T.assert(!(await opfs.entryExists(filename)));
+
+ const ba = new Uint8Array([1,2,3,4,5]);
+ capi.sqlite3_js_vfs_create_file(
+ "opfs", filename, ba
+ );
+ T.assert(await opfs.entryExists(filename));
+ fh = await opfs.rootDirectory.getFileHandle(filename);
+ sh = await fh.createSyncAccessHandle();
+ T.assert(ba.byteLength === await sh.getSize());
+ await sh.close();
+ sh = undefined;
+ unlink();
+
+ T.mustThrowMatching(()=>{
+ capi.sqlite3_js_vfs_create_file(
+ "no-such-vfs", filename, ba
+ );
+ }, "SQLITE_NOTFOUND: Unknown sqlite3_vfs name: no-such-vfs");
+ }finally{
+ if(sh) await sh.close();
+ unlink();
+ }
+
+ // Some sanity checks of the opfs utility functions...
+ const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
+ const aDir = testDir+'/test/dir';
+ T.assert(await opfs.mkdir(aDir), "mkdir failed")
+ .assert(await opfs.mkdir(aDir), "mkdir must pass if the dir exists")
+ .assert(!(await opfs.unlink(testDir+'/test')), "delete 1 should have failed (dir not empty)")
+ .assert((await opfs.unlink(testDir+'/test/dir')), "delete 2 failed")
+ .assert(!(await opfs.unlink(testDir+'/test/dir')),
+ "delete 2b should have failed (dir already deleted)")
+ .assert((await opfs.unlink(testDir, true)), "delete 3 failed")
+ .assert(!(await opfs.entryExists(testDir)),
+ "entryExists(",testDir,") should have failed");
+ }
+ }/*OPFS util sanity checks*/)
+ ;/* end OPFS tests */
+
+ ////////////////////////////////////////////////////////////////////////
+ T.g('OPFS SyncAccessHandle Pool VFS',
+ (sqlite3)=>(hasOpfs() || "requires OPFS APIs"))
+ .t({
+ name: 'SAH sanity checks',
+ test: async function(sqlite3){
+ T.assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
+ .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) < 0)
+ const inst = sqlite3.installOpfsSAHPoolVfs,
+ catcher = (e)=>{
+ error("Cannot load SAH pool VFS.",
+ "This might not be a problem,",
+ "depending on the environment.");
+ return false;
+ };
+ let u1, u2;
+ const P1 = inst(sahPoolConfig).then(u=>u1 = u).catch(catcher),
+ P2 = inst(sahPoolConfig).then(u=>u2 = u).catch(catcher);
+ await Promise.all([P1, P2]);
+ if(!P1) return;
+ T.assert(u1 === u2)
+ .assert(sahPoolConfig.name === u1.vfsName)
+ .assert(sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name))
+ .assert(u1.getCapacity() >= sahPoolConfig.initialCapacity
+ /* If a test fails before we get to nuke the VFS, we
+ can have more than the initial capacity on the next
+ run. */)
+ .assert(u1.getCapacity() + 2 === (await u2.addCapacity(2)))
+ .assert(2 === (await u2.reduceCapacity(2)))
+ .assert(sqlite3.oo1.OpfsSAHPool.default instanceof Function)
+ .assert(sqlite3.oo1.OpfsSAHPool.default ===
+ sqlite3.oo1.OpfsSAHPool[sahPoolConfig.name])
+ .assert(sqlite3.capi.sqlite3_js_vfs_list().indexOf(sahPoolConfig.name) >= 0);
+
+ T.assert(0 === u1.getFileCount());
+ const DbCtor = sqlite3.oo1.OpfsSAHPool.default;
+ const dbName = '/foo.db';
+ let db = new DbCtor(dbName);
+ T.assert(1 === u1.getFileCount());
+ db.exec([
+ 'create table t(a);',
+ 'insert into t(a) values(1),(2),(3)'
+ ]);
+ T.assert(1 === u1.getFileCount());
+ T.assert(3 === db.selectValue('select count(*) from t'));
+ db.close();
+ T.assert(1 === u1.getFileCount());
+ db = new DbCtor(dbName);
+ T.assert(1 === u1.getFileCount());
+ db.close();
+ T.assert(1 === u1.getFileCount())
+ .assert(true === u1.unlink(dbName))
+ .assert(false === u1.unlink(dbName))
+ .assert(0 === u1.getFileCount());
+
+ T.assert(true === await u2.removeVfs())
+ .assert(false === await u1.removeVfs())
+ .assert(!sqlite3.capi.sqlite3_vfs_find(sahPoolConfig.name));
+
+ let cErr, u3;
+ const conf2 = JSON.parse(JSON.stringify(sahPoolConfig));
+ conf2.$testThrowInInit = new Error("Testing throwing during init.");
+ conf2.name = sahPoolConfig.name+'-err';
+ const P3 = await inst(conf2).then(u=>u3 = u).catch((e)=>cErr=e);
+ T.assert(P3 === conf2.$testThrowInInit)
+ .assert(cErr === P3)
+ .assert(undefined === u3)
+ .assert(!sqlite3.capi.sqlite3_vfs_find(conf2.name));
+ }
+ }/*OPFS SAH Pool sanity checks*/)
+
////////////////////////////////////////////////////////////////////////
T.g('Bug Reports')
.t({