From c94cdaf720b7d82b9278a95c4f49e6c81679daa5 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 17 May 2023 14:04:00 +0100 Subject: [PATCH] More VFS API. --- const.go | 33 ++++------- internal/vfs/const.go | 109 +++++++++++++------------------------ internal/vfs/vfs.go | 63 ++++++++++++++++----- sqlite3vfs/const.go | 98 +++++++++++++++++++++++++++++++++ sqlite3vfs/vfs.go | 49 +++++++++++++++++ sqlite3vfs/vfs_api_test.go | 46 ++++++++++++++++ 6 files changed, 292 insertions(+), 106 deletions(-) create mode 100644 sqlite3vfs/const.go create mode 100644 sqlite3vfs/vfs.go create mode 100644 sqlite3vfs/vfs_api_test.go diff --git a/const.go b/const.go index 46d729c..6e51fe7 100644 --- a/const.go +++ b/const.go @@ -143,28 +143,17 @@ const ( type OpenFlag uint32 const ( - OPEN_READONLY OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */ - OPEN_READWRITE OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */ - OPEN_CREATE OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */ - OPEN_DELETEONCLOSE OpenFlag = 0x00000008 /* VFS only */ - OPEN_EXCLUSIVE OpenFlag = 0x00000010 /* VFS only */ - OPEN_AUTOPROXY OpenFlag = 0x00000020 /* VFS only */ - OPEN_URI OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */ - OPEN_MEMORY OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */ - OPEN_MAIN_DB OpenFlag = 0x00000100 /* VFS only */ - OPEN_TEMP_DB OpenFlag = 0x00000200 /* VFS only */ - OPEN_TRANSIENT_DB OpenFlag = 0x00000400 /* VFS only */ - OPEN_MAIN_JOURNAL OpenFlag = 0x00000800 /* VFS only */ - OPEN_TEMP_JOURNAL OpenFlag = 0x00001000 /* VFS only */ - OPEN_SUBJOURNAL OpenFlag = 0x00002000 /* VFS only */ - OPEN_SUPER_JOURNAL OpenFlag = 0x00004000 /* VFS only */ - OPEN_NOMUTEX OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */ - OPEN_FULLMUTEX OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */ - OPEN_SHAREDCACHE OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */ - OPEN_PRIVATECACHE OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */ - OPEN_WAL OpenFlag = 0x00080000 /* VFS only */ - OPEN_NOFOLLOW OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */ - OPEN_EXRESCODE OpenFlag = 0x02000000 /* Extended result codes */ + OPEN_READONLY OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */ + OPEN_READWRITE OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */ + OPEN_CREATE OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */ + OPEN_URI OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */ + OPEN_MEMORY OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */ + OPEN_NOMUTEX OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */ + OPEN_FULLMUTEX OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */ + OPEN_SHAREDCACHE OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */ + OPEN_PRIVATECACHE OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */ + OPEN_NOFOLLOW OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */ + OPEN_EXRESCODE OpenFlag = 0x02000000 /* Extended result codes */ ) // PrepareFlag is a flag that can be passed to [Conn.PrepareFlags]. diff --git a/internal/vfs/const.go b/internal/vfs/const.go index 0366458..1d7684d 100644 --- a/internal/vfs/const.go +++ b/internal/vfs/const.go @@ -1,6 +1,9 @@ package vfs +import "github.com/ncruces/go-sqlite3/sqlite3vfs" + const ( + _MAX_STRING = 512 // Used for short strings: names, error messages… _MAX_PATHNAME = 512 _DEFAULT_SECTOR_SIZE = 4096 ) @@ -59,93 +62,59 @@ const ( ) // https://www.sqlite.org/c3ref/c_open_autoproxy.html -type _OpenFlag uint32 +type _OpenFlag = sqlite3vfs.OpenFlag const ( - _OPEN_READONLY _OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */ - _OPEN_READWRITE _OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */ - _OPEN_CREATE _OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */ - _OPEN_DELETEONCLOSE _OpenFlag = 0x00000008 /* VFS only */ - _OPEN_EXCLUSIVE _OpenFlag = 0x00000010 /* VFS only */ - _OPEN_AUTOPROXY _OpenFlag = 0x00000020 /* VFS only */ - _OPEN_URI _OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */ - _OPEN_MEMORY _OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */ - _OPEN_MAIN_DB _OpenFlag = 0x00000100 /* VFS only */ - _OPEN_TEMP_DB _OpenFlag = 0x00000200 /* VFS only */ - _OPEN_TRANSIENT_DB _OpenFlag = 0x00000400 /* VFS only */ - _OPEN_MAIN_JOURNAL _OpenFlag = 0x00000800 /* VFS only */ - _OPEN_TEMP_JOURNAL _OpenFlag = 0x00001000 /* VFS only */ - _OPEN_SUBJOURNAL _OpenFlag = 0x00002000 /* VFS only */ - _OPEN_SUPER_JOURNAL _OpenFlag = 0x00004000 /* VFS only */ - _OPEN_NOMUTEX _OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */ - _OPEN_FULLMUTEX _OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */ - _OPEN_SHAREDCACHE _OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */ - _OPEN_PRIVATECACHE _OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */ - _OPEN_WAL _OpenFlag = 0x00080000 /* VFS only */ - _OPEN_NOFOLLOW _OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */ - _OPEN_EXRESCODE _OpenFlag = 0x02000000 /* Extended result codes */ + _OPEN_READONLY = sqlite3vfs.OPEN_READONLY + _OPEN_READWRITE = sqlite3vfs.OPEN_READWRITE + _OPEN_CREATE = sqlite3vfs.OPEN_CREATE + _OPEN_DELETEONCLOSE = sqlite3vfs.OPEN_DELETEONCLOSE + _OPEN_EXCLUSIVE = sqlite3vfs.OPEN_EXCLUSIVE + _OPEN_AUTOPROXY = sqlite3vfs.OPEN_AUTOPROXY + _OPEN_URI = sqlite3vfs.OPEN_URI + _OPEN_MEMORY = sqlite3vfs.OPEN_MEMORY + _OPEN_MAIN_DB = sqlite3vfs.OPEN_MAIN_DB + _OPEN_TEMP_DB = sqlite3vfs.OPEN_TEMP_DB + _OPEN_TRANSIENT_DB = sqlite3vfs.OPEN_TRANSIENT_DB + _OPEN_MAIN_JOURNAL = sqlite3vfs.OPEN_MAIN_JOURNAL + _OPEN_TEMP_JOURNAL = sqlite3vfs.OPEN_TEMP_JOURNAL + _OPEN_SUBJOURNAL = sqlite3vfs.OPEN_SUBJOURNAL + _OPEN_SUPER_JOURNAL = sqlite3vfs.OPEN_SUPER_JOURNAL + _OPEN_NOMUTEX = sqlite3vfs.OPEN_NOMUTEX + _OPEN_FULLMUTEX = sqlite3vfs.OPEN_FULLMUTEX + _OPEN_SHAREDCACHE = sqlite3vfs.OPEN_SHAREDCACHE + _OPEN_PRIVATECACHE = sqlite3vfs.OPEN_PRIVATECACHE + _OPEN_WAL = sqlite3vfs.OPEN_WAL + _OPEN_NOFOLLOW = sqlite3vfs.OPEN_NOFOLLOW ) // https://www.sqlite.org/c3ref/c_access_exists.html -type _AccessFlag uint32 +type _AccessFlag = sqlite3vfs.AccessFlag const ( - _ACCESS_EXISTS _AccessFlag = 0 - _ACCESS_READWRITE _AccessFlag = 1 /* Used by PRAGMA temp_store_directory */ - _ACCESS_READ _AccessFlag = 2 /* Unused */ + _ACCESS_EXISTS = sqlite3vfs.ACCESS_EXISTS + _ACCESS_READWRITE = sqlite3vfs.ACCESS_READWRITE + _ACCESS_READ = sqlite3vfs.ACCESS_READ ) // https://www.sqlite.org/c3ref/c_sync_dataonly.html -type _SyncFlag uint32 +type _SyncFlag = sqlite3vfs.SyncFlag const ( - _SYNC_NORMAL _SyncFlag = 0x00002 - _SYNC_FULL _SyncFlag = 0x00003 - _SYNC_DATAONLY _SyncFlag = 0x00010 + _SYNC_NORMAL = sqlite3vfs.SYNC_NORMAL + _SYNC_FULL = sqlite3vfs.SYNC_FULL + _SYNC_DATAONLY = sqlite3vfs.SYNC_DATAONLY ) // https://www.sqlite.org/c3ref/c_lock_exclusive.html -type _LockLevel uint32 +type _LockLevel = sqlite3vfs.LockLevel const ( - // No locks are held on the database. - // The database may be neither read nor written. - // Any internally cached data is considered suspect and subject to - // verification against the database file before being used. - // Other processes can read or write the database as their own locking - // states permit. - // This is the default state. - _LOCK_NONE _LockLevel = 0 /* xUnlock() only */ - - // The database may be read but not written. - // Any number of processes can hold SHARED locks at the same time, - // hence there can be many simultaneous readers. - // But no other thread or process is allowed to write to the database file - // while one or more SHARED locks are active. - _LOCK_SHARED _LockLevel = 1 /* xLock() or xUnlock() */ - - // A RESERVED lock means that the process is planning on writing to the - // database file at some point in the future but that it is currently just - // reading from the file. - // Only a single RESERVED lock may be active at one time, - // though multiple SHARED locks can coexist with a single RESERVED lock. - // RESERVED differs from PENDING in that new SHARED locks can be acquired - // while there is a RESERVED lock. - _LOCK_RESERVED _LockLevel = 2 /* xLock() only */ - - // A PENDING lock means that the process holding the lock wants to write to - // the database as soon as possible and is just waiting on all current - // SHARED locks to clear so that it can get an EXCLUSIVE lock. - // No new SHARED locks are permitted against the database if a PENDING lock - // is active, though existing SHARED locks are allowed to continue. - _LOCK_PENDING _LockLevel = 3 /* internal use only */ - - // An EXCLUSIVE lock is needed in order to write to the database file. - // Only one EXCLUSIVE lock is allowed on the file and no other locks of any - // kind are allowed to coexist with an EXCLUSIVE lock. - // In order to maximize concurrency, SQLite works to minimize the amount of - // time that EXCLUSIVE locks are held. - _LOCK_EXCLUSIVE _LockLevel = 4 /* xLock() only */ + _LOCK_NONE = sqlite3vfs.LOCK_NONE + _LOCK_SHARED = sqlite3vfs.LOCK_SHARED + _LOCK_RESERVED = sqlite3vfs.LOCK_RESERVED + _LOCK_PENDING = sqlite3vfs.LOCK_PENDING + _LOCK_EXCLUSIVE = sqlite3vfs.LOCK_EXCLUSIVE ) // https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html diff --git a/internal/vfs/vfs.go b/internal/vfs/vfs.go index 04c5946..5263df9 100644 --- a/internal/vfs/vfs.go +++ b/internal/vfs/vfs.go @@ -12,6 +12,7 @@ import ( "time" "github.com/ncruces/go-sqlite3/internal/util" + "github.com/ncruces/go-sqlite3/sqlite3vfs" "github.com/ncruces/julianday" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" @@ -64,6 +65,10 @@ func (vfs *vfsState) Close() error { } func vfsFind(ctx context.Context, mod api.Module, zVfsName uint32) uint32 { + name := util.ReadString(mod, zVfsName, _MAX_STRING) + if sqlite3vfs.Find(name) != nil { + return 1 + } return 0 } @@ -113,10 +118,29 @@ func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow uint32) _ } func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative, nFull, zFull uint32) _ErrorCode { + vfs := getVFS(mod, pVfs) rel := util.ReadString(mod, zRelative, _MAX_PATHNAME) - abs, err := filepath.Abs(rel) - if err != nil { - return _CANTOPEN_FULLPATH + + var abs string + var symlink bool + if vfs != nil { + p, err := vfs.FullPathname(rel) + if err != nil { + return _CANTOPEN_FULLPATH + } + abs = p + } else { + p, err := filepath.Abs(rel) + if err != nil { + return _CANTOPEN_FULLPATH + } + s, err := os.Lstat(p) + if err == nil { + symlink = s.Mode()&fs.ModeSymlink != 0 + } else if !errors.Is(err, fs.ErrNotExist) { + return _CANTOPEN_FULLPATH + } + abs = p } size := uint64(len(abs) + 1) @@ -127,15 +151,10 @@ func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative, nFull mem[len(abs)] = 0 copy(mem, abs) - if fi, err := os.Lstat(abs); err == nil { - if fi.Mode()&fs.ModeSymlink != 0 { - return _OK_SYMLINK - } - return _OK - } else if errors.Is(err, fs.ErrNotExist) { - return _OK + if symlink { + return _OK_SYMLINK } - return _CANTOPEN_FULLPATH + return _OK } func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) _ErrorCode { @@ -192,6 +211,8 @@ func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags _A } func vfsOpen(ctx context.Context, mod api.Module, pVfs, zName, pFile uint32, flags _OpenFlag, pOutFlags uint32) _ErrorCode { + vfs := getVFS(mod, pVfs) + var oflags int if flags&_OPEN_EXCLUSIVE != 0 { oflags |= os.O_EXCL @@ -207,11 +228,17 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zName, pFile uint32, fla } var err error + var name string var f *os.File - if zName == 0 { + if zName != 0 { + name = util.ReadString(mod, zName, _MAX_PATHNAME) + } + switch { + case vfs != nil: + _, flags, err = vfs.Open(name, flags) + case name == "": f, err = os.CreateTemp("", "*.db") - } else { - name := util.ReadString(mod, zName, _MAX_PATHNAME) + default: f, err = osOpenFile(name, oflags, 0666) } if err != nil { @@ -393,3 +420,11 @@ func vfsFileMoved(ctx context.Context, mod api.Module, pFile, pResOut uint32) _E util.WriteUint32(mod, pResOut, res) return _OK } + +func getVFS(mod api.Module, pVfs uint32) sqlite3vfs.VFS { + if pVfs == 0 { + return nil + } + name := util.ReadString(mod, util.ReadUint32(mod, pVfs+16), _MAX_STRING) + return sqlite3vfs.Find(name) +} diff --git a/sqlite3vfs/const.go b/sqlite3vfs/const.go new file mode 100644 index 0000000..e92f10c --- /dev/null +++ b/sqlite3vfs/const.go @@ -0,0 +1,98 @@ +package sqlite3vfs + +// OpenFlag is a flag for the [VFS.Open] method. +// +// https://www.sqlite.org/c3ref/c_open_autoproxy.html +type OpenFlag uint32 + +const ( + OPEN_READONLY OpenFlag = 0x00000001 /* Ok for sqlite3_open_v2() */ + OPEN_READWRITE OpenFlag = 0x00000002 /* Ok for sqlite3_open_v2() */ + OPEN_CREATE OpenFlag = 0x00000004 /* Ok for sqlite3_open_v2() */ + OPEN_DELETEONCLOSE OpenFlag = 0x00000008 /* VFS only */ + OPEN_EXCLUSIVE OpenFlag = 0x00000010 /* VFS only */ + OPEN_AUTOPROXY OpenFlag = 0x00000020 /* VFS only */ + OPEN_URI OpenFlag = 0x00000040 /* Ok for sqlite3_open_v2() */ + OPEN_MEMORY OpenFlag = 0x00000080 /* Ok for sqlite3_open_v2() */ + OPEN_MAIN_DB OpenFlag = 0x00000100 /* VFS only */ + OPEN_TEMP_DB OpenFlag = 0x00000200 /* VFS only */ + OPEN_TRANSIENT_DB OpenFlag = 0x00000400 /* VFS only */ + OPEN_MAIN_JOURNAL OpenFlag = 0x00000800 /* VFS only */ + OPEN_TEMP_JOURNAL OpenFlag = 0x00001000 /* VFS only */ + OPEN_SUBJOURNAL OpenFlag = 0x00002000 /* VFS only */ + OPEN_SUPER_JOURNAL OpenFlag = 0x00004000 /* VFS only */ + OPEN_NOMUTEX OpenFlag = 0x00008000 /* Ok for sqlite3_open_v2() */ + OPEN_FULLMUTEX OpenFlag = 0x00010000 /* Ok for sqlite3_open_v2() */ + OPEN_SHAREDCACHE OpenFlag = 0x00020000 /* Ok for sqlite3_open_v2() */ + OPEN_PRIVATECACHE OpenFlag = 0x00040000 /* Ok for sqlite3_open_v2() */ + OPEN_WAL OpenFlag = 0x00080000 /* VFS only */ + OPEN_NOFOLLOW OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */ +) + +// AccessFlag is a flag for the [VFS.Access] method. +// +// https://www.sqlite.org/c3ref/c_access_exists.html +type AccessFlag uint32 + +const ( + ACCESS_EXISTS AccessFlag = 0 + ACCESS_READWRITE AccessFlag = 1 /* Used by PRAGMA temp_store_directory */ + ACCESS_READ AccessFlag = 2 /* Unused */ +) + +// SyncFlag is a flag for the [File.Sync] method. +// +// https://www.sqlite.org/c3ref/c_sync_dataonly.html +type SyncFlag uint32 + +const ( + SYNC_NORMAL SyncFlag = 0x00002 + SYNC_FULL SyncFlag = 0x00003 + SYNC_DATAONLY SyncFlag = 0x00010 +) + +// LockLevel is a value used with [File.Lock] and [File.Unlock] methods. +// +// https://www.sqlite.org/c3ref/c_lock_exclusive.html +type LockLevel uint32 + +const ( + // No locks are held on the database. + // The database may be neither read nor written. + // Any internally cached data is considered suspect and subject to + // verification against the database file before being used. + // Other processes can read or write the database as their own locking + // states permit. + // This is the default state. + LOCK_NONE LockLevel = 0 /* xUnlock() only */ + + // The database may be read but not written. + // Any number of processes can hold SHARED locks at the same time, + // hence there can be many simultaneous readers. + // But no other thread or process is allowed to write to the database file + // while one or more SHARED locks are active. + LOCK_SHARED LockLevel = 1 /* xLock() or xUnlock() */ + + // A RESERVED lock means that the process is planning on writing to the + // database file at some point in the future but that it is currently just + // reading from the file. + // Only a single RESERVED lock may be active at one time, + // though multiple SHARED locks can coexist with a single RESERVED lock. + // RESERVED differs from PENDING in that new SHARED locks can be acquired + // while there is a RESERVED lock. + LOCK_RESERVED LockLevel = 2 /* xLock() only */ + + // A PENDING lock means that the process holding the lock wants to write to + // the database as soon as possible and is just waiting on all current + // SHARED locks to clear so that it can get an EXCLUSIVE lock. + // No new SHARED locks are permitted against the database if a PENDING lock + // is active, though existing SHARED locks are allowed to continue. + LOCK_PENDING LockLevel = 3 /* internal use only */ + + // An EXCLUSIVE lock is needed in order to write to the database file. + // Only one EXCLUSIVE lock is allowed on the file and no other locks of any + // kind are allowed to coexist with an EXCLUSIVE lock. + // In order to maximize concurrency, SQLite works to minimize the amount of + // time that EXCLUSIVE locks are held. + LOCK_EXCLUSIVE LockLevel = 4 /* xLock() only */ +) diff --git a/sqlite3vfs/vfs.go b/sqlite3vfs/vfs.go new file mode 100644 index 0000000..dea9e3f --- /dev/null +++ b/sqlite3vfs/vfs.go @@ -0,0 +1,49 @@ +package sqlite3vfs + +import "sync" + +type VFS interface { + Open(name string, flags OpenFlag) (File, OpenFlag, error) + Delete(name string, dirSync bool) error + Access(name string, flags AccessFlag) (bool, error) + FullPathname(name string) (string, error) +} + +type File interface { + Close() error + ReadAt(p []byte, off int64) (n int, err error) + WriteAt(p []byte, off int64) (n int, err error) + Truncate(size int64) error + Sync(flag SyncFlag) error + FileSize() (int64, error) + Lock(elock LockLevel) error + Unlock(elock LockLevel) error + CheckReservedLock() (bool, error) + SectorSize() int64 +} + +var ( + vfsRegistry map[string]VFS + vfsRegistryMtx sync.Mutex +) + +func Find(name string) VFS { + vfsRegistryMtx.Lock() + defer vfsRegistryMtx.Unlock() + return vfsRegistry[name] +} + +func Register(name string, vfs VFS) { + vfsRegistryMtx.Lock() + defer vfsRegistryMtx.Unlock() + if vfsRegistry == nil { + vfsRegistry = map[string]VFS{} + } + vfsRegistry[name] = vfs +} + +func Unregister(name string) { + vfsRegistryMtx.Lock() + defer vfsRegistryMtx.Unlock() + delete(vfsRegistry, name) +} diff --git a/sqlite3vfs/vfs_api_test.go b/sqlite3vfs/vfs_api_test.go new file mode 100644 index 0000000..df65606 --- /dev/null +++ b/sqlite3vfs/vfs_api_test.go @@ -0,0 +1,46 @@ +package sqlite3vfs_test + +import ( + "testing" + + "github.com/ncruces/go-sqlite3" + _ "github.com/ncruces/go-sqlite3/embed" + "github.com/ncruces/go-sqlite3/sqlite3vfs" +) + +type testVFS struct { + *testing.T +} + +func (t testVFS) Open(name string, flags sqlite3vfs.OpenFlag) (sqlite3vfs.File, sqlite3vfs.OpenFlag, error) { + t.Log("Open", name, flags) + t.SkipNow() + return nil, flags, nil +} + +func (testVFS) Delete(name string, dirSync bool) error { + panic("unimplemented") +} + +func (testVFS) Access(name string, flags sqlite3vfs.AccessFlag) (bool, error) { + panic("unimplemented") +} + +func (t testVFS) FullPathname(name string) (string, error) { + t.Log("FullPathname", name) + return name, nil +} + +func TestRegister(t *testing.T) { + vfs := testVFS{t} + sqlite3vfs.Register("foo", vfs) + defer sqlite3vfs.Unregister("foo") + + defer func() { _ = recover() }() + + conn, err := sqlite3.Open("file:file.db?vfs=foo") + if err != nil { + t.Fatal(err) + } + defer conn.Close() +}