From 663b23ff3bc286a64acbd1a2e08c61a41d41087e Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Fri, 19 May 2023 13:47:12 +0100 Subject: [PATCH] Documentation. --- README.md | 4 +- backup.go | 6 +- conn.go | 6 +- driver/driver.go | 2 +- internal/util/error.go | 1 + internal/util/func.go | 14 ++--- sqlite3vfs/const.go | 10 ++-- sqlite3vfs/vfs.go | 106 ++++++++++++++++++++---------------- sqlite3vfs/vfs_api.go | 38 ++++++++++++- sqlite3vfs/vfs_file.go | 9 ++- sqlite3vfs/vfs_os_darwin.go | 2 +- 11 files changed, 128 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 0eabecd..d9dcccf 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ provides a [`database/sql`](https://pkg.go.dev/database/sql) driver ([example usage](https://pkg.go.dev/github.com/ncruces/go-sqlite3/driver#example-package)). - Package [`github.com/ncruces/go-sqlite3/embed`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/embed) embeds a build of SQLite into your application. +- Package [`github.com/ncruces/go-sqlite3/sqlite3vfs`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/sqlite3vfs) +wraps the [C SQLite VFS API](https://www.sqlite.org/vfs.html) and provides a pure Go implementation. ### Caveats @@ -73,7 +75,7 @@ Performance is tested by running - [ ] in-memory VFS - [ ] read-only VFS, wrapping an [`io.ReaderAt`](https://pkg.go.dev/io#ReaderAt) - [ ] cloud-based VFS, based on [Cloud Backed SQLite](https://sqlite.org/cloudsqlite/doc/trunk/www/index.wiki) - - [ ] custom VFS API + - [x] custom VFS API ### Alternatives diff --git a/backup.go b/backup.go index d4a6719..62b7c00 100644 --- a/backup.go +++ b/backup.go @@ -11,7 +11,7 @@ type Backup struct { // Backup backs up srcDB on the src connection to the "main" database in dstURI. // -// Backup calls [Open] to open the SQLite database file dstURI, +// Backup opens the SQLite database file dstURI, // and blocks until the entire backup is complete. // Use [Conn.BackupInit] for incremental backup. // @@ -28,7 +28,7 @@ func (src *Conn) Backup(srcDB, dstURI string) error { // Restore restores dstDB on the dst connection from the "main" database in srcURI. // -// Restore calls [Open] to open the SQLite database file srcURI, +// Restore opens the SQLite database file srcURI, // and blocks until the entire restore is complete. // // https://www.sqlite.org/backup.html @@ -48,7 +48,7 @@ func (dst *Conn) Restore(dstDB, srcURI string) error { // BackupInit initializes a backup operation to copy the content of one database into another. // -// BackupInit calls [Open] to open the SQLite database file dstURI, +// BackupInit opens the SQLite database file dstURI, // then initializes a backup that copies the contents of srcDB on the src connection // to the "main" database in dstURI. // diff --git a/conn.go b/conn.go index 9aeb84f..2590b30 100644 --- a/conn.go +++ b/conn.go @@ -278,7 +278,8 @@ func (c *Conn) SetInterrupt(ctx context.Context) (old context.Context) { break case <-ctx.Done(): // Done was closed. - buf := util.View(c.mod, c.handle+280, 4) + const isInterruptedOffset = 280 + buf := util.View(c.mod, c.handle+isInterruptedOffset, 4) (*atomic.Uint32)(unsafe.Pointer(&buf[0])).Store(1) // Wait for the next call to SetInterrupt. <-waiter @@ -294,7 +295,8 @@ func (c *Conn) checkInterrupt() bool { if c.interrupt == nil || c.interrupt.Err() == nil { return false } - buf := util.View(c.mod, c.handle+280, 4) + const isInterruptedOffset = 280 + buf := util.View(c.mod, c.handle+isInterruptedOffset, 4) (*atomic.Uint32)(unsafe.Pointer(&buf[0])).Store(1) return true } diff --git a/driver/driver.go b/driver/driver.go index 61aeee6..8775d0f 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -16,7 +16,7 @@ // // sql.Open("sqlite3", "file:demo.db?_pragma=busy_timeout(10000)&_pragma=locking_mode(normal)") // -// If no PRAGMAs are specifed, a busy timeout of 1 minute +// If no PRAGMAs are specified, a busy timeout of 1 minute // and normal locking mode are used. // // [URI]: https://www.sqlite.org/uri.html diff --git a/internal/util/error.go b/internal/util/error.go index a0682bb..7bcd45a 100644 --- a/internal/util/error.go +++ b/internal/util/error.go @@ -23,6 +23,7 @@ const ( OffsetErr = ErrorString("sqlite3: invalid offset") TailErr = ErrorString("sqlite3: multiple statements") IsolationErr = ErrorString("sqlite3: unsupported isolation level") + NoVFSErr = ErrorString("sqlite3: no such vfs: ") ) func AssertErr() ErrorString { diff --git a/internal/util/func.go b/internal/util/func.go index 0a9abad..65efe3b 100644 --- a/internal/util/func.go +++ b/internal/util/func.go @@ -16,7 +16,7 @@ func (fn funcII[TR, T0]) Call(ctx context.Context, mod api.Module, stack []uint6 stack[0] = uint64(fn(ctx, mod, T0(stack[0]))) } -func RegisterFuncII[TR, T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0) TR) { +func ExportFuncII[TR, T0 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0) TR) { mod.NewFunctionBuilder(). WithGoModuleFunction(funcII[TR, T0](fn), []api.ValueType{api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). @@ -29,7 +29,7 @@ func (fn funcIII[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack [] stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]))) } -func RegisterFuncIII[TR, T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) { +func ExportFuncIII[TR, T0, T1 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) { mod.NewFunctionBuilder(). WithGoModuleFunction(funcIII[TR, T0, T1](fn), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). @@ -42,7 +42,7 @@ func (fn funcIIII[TR, T0, T1, T2]) Call(ctx context.Context, mod api.Module, sta stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]))) } -func RegisterFuncIIII[TR, T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2) TR) { +func ExportFuncIIII[TR, T0, T1, T2 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2) TR) { mod.NewFunctionBuilder(). WithGoModuleFunction(funcIIII[TR, T0, T1, T2](fn), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). @@ -55,7 +55,7 @@ func (fn funcIIIII[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))) } -func RegisterFuncIIIII[TR, T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) { +func ExportFuncIIIII[TR, T0, T1, T2, T3 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) { mod.NewFunctionBuilder(). WithGoModuleFunction(funcIIIII[TR, T0, T1, T2, T3](fn), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). @@ -68,7 +68,7 @@ func (fn funcIIIIII[TR, T0, T1, T2, T3, T4]) Call(ctx context.Context, mod api.M stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]), T4(stack[4]))) } -func RegisterFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4) TR) { +func ExportFuncIIIIII[TR, T0, T1, T2, T3, T4 i32](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3, T4) TR) { mod.NewFunctionBuilder(). WithGoModuleFunction(funcIIIIII[TR, T0, T1, T2, T3, T4](fn), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32}, []api.ValueType{api.ValueTypeI32}). @@ -81,7 +81,7 @@ func (fn funcIIIIJ[TR, T0, T1, T2, T3]) Call(ctx context.Context, mod api.Module stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]), T2(stack[2]), T3(stack[3]))) } -func RegisterFuncIIIIJ[TR, T0, T1, T2 i32, T3 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) { +func ExportFuncIIIIJ[TR, T0, T1, T2 i32, T3 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1, T2, T3) TR) { mod.NewFunctionBuilder(). WithGoModuleFunction(funcIIIIJ[TR, T0, T1, T2, T3](fn), []api.ValueType{api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}). @@ -94,7 +94,7 @@ func (fn funcIIJ[TR, T0, T1]) Call(ctx context.Context, mod api.Module, stack [] stack[0] = uint64(fn(ctx, mod, T0(stack[0]), T1(stack[1]))) } -func RegisterFuncIIJ[TR, T0 i32, T1 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) { +func ExportFuncIIJ[TR, T0 i32, T1 i64](mod wazero.HostModuleBuilder, name string, fn func(context.Context, api.Module, T0, T1) TR) { mod.NewFunctionBuilder(). WithGoModuleFunction(funcIIJ[TR, T0, T1](fn), []api.ValueType{api.ValueTypeI32, api.ValueTypeI64}, []api.ValueType{api.ValueTypeI32}). diff --git a/sqlite3vfs/const.go b/sqlite3vfs/const.go index 6ae98b7..866a97e 100644 --- a/sqlite3vfs/const.go +++ b/sqlite3vfs/const.go @@ -42,7 +42,7 @@ const ( _OK_SYMLINK _ErrorCode = util.OK_SYMLINK ) -// OpenFlag is a flag for the [VFS.Open] method. +// OpenFlag is a flag for the [VFS] Open method. // // https://www.sqlite.org/c3ref/c_open_autoproxy.html type OpenFlag uint32 @@ -71,7 +71,7 @@ const ( OPEN_NOFOLLOW OpenFlag = 0x01000000 /* Ok for sqlite3_open_v2() */ ) -// AccessFlag is a flag for the [VFS.Access] method. +// AccessFlag is a flag for the [VFS] Access method. // // https://www.sqlite.org/c3ref/c_access_exists.html type AccessFlag uint32 @@ -82,7 +82,7 @@ const ( ACCESS_READ AccessFlag = 2 /* Unused */ ) -// SyncFlag is a flag for the [File.Sync] method. +// SyncFlag is a flag for the [File] Sync method. // // https://www.sqlite.org/c3ref/c_sync_dataonly.html type SyncFlag uint32 @@ -93,7 +93,7 @@ const ( SYNC_DATAONLY SyncFlag = 0x00010 ) -// LockLevel is a value used with [File.Lock] and [File.Unlock] methods. +// LockLevel is a value used with [File] Lock and Unlock methods. // // https://www.sqlite.org/c3ref/c_lock_exclusive.html type LockLevel uint32 @@ -139,7 +139,7 @@ const ( LOCK_EXCLUSIVE LockLevel = 4 /* xLock() only */ ) -// DeviceCharacteristic is a flag retuned by the [File.DeviceCharacteristic] method. +// DeviceCharacteristic is a flag retuned by the [File] DeviceCharacteristics method. // // https://www.sqlite.org/c3ref/c_iocap_atomic.html type DeviceCharacteristic uint32 diff --git a/sqlite3vfs/vfs.go b/sqlite3vfs/vfs.go index a4d51be..ef46850 100644 --- a/sqlite3vfs/vfs.go +++ b/sqlite3vfs/vfs.go @@ -13,29 +13,31 @@ import ( "github.com/tetratelabs/wazero/api" ) +// ExportHostFunctions registers the required VFS host functions +// with the provided env module. func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder { - util.RegisterFuncII(env, "go_vfs_find", vfsFind) - util.RegisterFuncIIJ(env, "go_localtime", vfsLocaltime) - util.RegisterFuncIIII(env, "go_randomness", vfsRandomness) - util.RegisterFuncIII(env, "go_sleep", vfsSleep) - util.RegisterFuncIII(env, "go_current_time", vfsCurrentTime) - util.RegisterFuncIII(env, "go_current_time_64", vfsCurrentTime64) - util.RegisterFuncIIIII(env, "go_full_pathname", vfsFullPathname) - util.RegisterFuncIIII(env, "go_delete", vfsDelete) - util.RegisterFuncIIIII(env, "go_access", vfsAccess) - util.RegisterFuncIIIIII(env, "go_open", vfsOpen) - util.RegisterFuncII(env, "go_close", vfsClose) - util.RegisterFuncIIIIJ(env, "go_read", vfsRead) - util.RegisterFuncIIIIJ(env, "go_write", vfsWrite) - util.RegisterFuncIIJ(env, "go_truncate", vfsTruncate) - util.RegisterFuncIII(env, "go_sync", vfsSync) - util.RegisterFuncIII(env, "go_file_size", vfsFileSize) - util.RegisterFuncIIII(env, "go_file_control", vfsFileControl) - util.RegisterFuncII(env, "go_sector_size", vfsSectorSize) - util.RegisterFuncII(env, "go_device_characteristics", vfsDeviceCharacteristics) - util.RegisterFuncIII(env, "go_lock", vfsLock) - util.RegisterFuncIII(env, "go_unlock", vfsUnlock) - util.RegisterFuncIII(env, "go_check_reserved_lock", vfsCheckReservedLock) + util.ExportFuncII(env, "go_vfs_find", vfsFind) + util.ExportFuncIIJ(env, "go_localtime", vfsLocaltime) + util.ExportFuncIIII(env, "go_randomness", vfsRandomness) + util.ExportFuncIII(env, "go_sleep", vfsSleep) + util.ExportFuncIII(env, "go_current_time", vfsCurrentTime) + util.ExportFuncIII(env, "go_current_time_64", vfsCurrentTime64) + util.ExportFuncIIIII(env, "go_full_pathname", vfsFullPathname) + util.ExportFuncIIII(env, "go_delete", vfsDelete) + util.ExportFuncIIIII(env, "go_access", vfsAccess) + util.ExportFuncIIIIII(env, "go_open", vfsOpen) + util.ExportFuncII(env, "go_close", vfsClose) + util.ExportFuncIIIIJ(env, "go_read", vfsRead) + util.ExportFuncIIIIJ(env, "go_write", vfsWrite) + util.ExportFuncIIJ(env, "go_truncate", vfsTruncate) + util.ExportFuncIII(env, "go_sync", vfsSync) + util.ExportFuncIII(env, "go_file_size", vfsFileSize) + util.ExportFuncIIII(env, "go_file_control", vfsFileControl) + util.ExportFuncII(env, "go_sector_size", vfsSectorSize) + util.ExportFuncII(env, "go_device_characteristics", vfsDeviceCharacteristics) + util.ExportFuncIII(env, "go_lock", vfsLock) + util.ExportFuncIII(env, "go_unlock", vfsUnlock) + util.ExportFuncIII(env, "go_check_reserved_lock", vfsCheckReservedLock) return env } @@ -44,6 +46,13 @@ type vfsState struct { files []File } +// NewContext creates a new context to hold [api.Module] specific VFS data. +// +// This context should be passed to any [api.Function] calls that might +// generate VFS host callbacks. +// +// The returned [io.Closer] should be closed after the [api.Module] is closed, +// to release any associated resources. func NewContext(ctx context.Context) (context.Context, io.Closer) { vfs := &vfsState{} return context.WithValue(ctx, vfsKey{}, vfs), vfs @@ -113,7 +122,7 @@ 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 := vfsAPIGet(mod, pVfs) + vfs := vfsGet(mod, pVfs) path := util.ReadString(mod, zRelative, _MAX_PATHNAME) path, err := vfs.FullPathname(path) @@ -126,19 +135,19 @@ func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative, nFull mem[len(path)] = 0 copy(mem, path) - return vfsAPIErrorCode(err, _CANTOPEN_FULLPATH) + return vfsErrorCode(err, _CANTOPEN_FULLPATH) } func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) _ErrorCode { - vfs := vfsAPIGet(mod, pVfs) + vfs := vfsGet(mod, pVfs) path := util.ReadString(mod, zPath, _MAX_PATHNAME) err := vfs.Delete(path, syncDir != 0) - return vfsAPIErrorCode(err, _IOERR_DELETE) + return vfsErrorCode(err, _IOERR_DELETE) } func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags AccessFlag, pResOut uint32) _ErrorCode { - vfs := vfsAPIGet(mod, pVfs) + vfs := vfsGet(mod, pVfs) path := util.ReadString(mod, zPath, _MAX_PATHNAME) ok, err := vfs.Access(path, flags) @@ -148,11 +157,11 @@ func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags Ac } util.WriteUint32(mod, pResOut, res) - return vfsAPIErrorCode(err, _IOERR_ACCESS) + return vfsErrorCode(err, _IOERR_ACCESS) } func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, flags OpenFlag, pOutFlags uint32) _ErrorCode { - vfs := vfsAPIGet(mod, pVfs) + vfs := vfsGet(mod, pVfs) var path string if zPath != 0 { @@ -161,7 +170,7 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, fla file, flags, err := vfs.Open(path, flags) if err != nil { - return vfsAPIErrorCode(err, _CANTOPEN) + return vfsErrorCode(err, _CANTOPEN) } vfsFileRegister(ctx, mod, pFile, file) @@ -210,32 +219,32 @@ func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf, iAmt uint32, iOf func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode { file := vfsFileGet(ctx, mod, pFile) err := file.Truncate(nByte) - return vfsAPIErrorCode(err, _IOERR_TRUNCATE) + return vfsErrorCode(err, _IOERR_TRUNCATE) } func vfsSync(ctx context.Context, mod api.Module, pFile uint32, flags SyncFlag) _ErrorCode { file := vfsFileGet(ctx, mod, pFile) err := file.Sync(flags) - return vfsAPIErrorCode(err, _IOERR_FSYNC) + return vfsErrorCode(err, _IOERR_FSYNC) } func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) _ErrorCode { file := vfsFileGet(ctx, mod, pFile) size, err := file.FileSize() util.WriteUint64(mod, pSize, uint64(size)) - return vfsAPIErrorCode(err, _IOERR_SEEK) + return vfsErrorCode(err, _IOERR_SEEK) } func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock LockLevel) _ErrorCode { file := vfsFileGet(ctx, mod, pFile) err := file.Lock(eLock) - return vfsAPIErrorCode(err, _IOERR_LOCK) + return vfsErrorCode(err, _IOERR_LOCK) } func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock LockLevel) _ErrorCode { file := vfsFileGet(ctx, mod, pFile) err := file.Unlock(eLock) - return vfsAPIErrorCode(err, _IOERR_UNLOCK) + return vfsErrorCode(err, _IOERR_UNLOCK) } func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode { @@ -248,7 +257,7 @@ func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ui } util.WriteUint32(mod, pResOut, res) - return vfsAPIErrorCode(err, _IOERR_CHECKRESERVEDLOCK) + return vfsErrorCode(err, _IOERR_CHECKRESERVEDLOCK) } func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode { @@ -290,7 +299,7 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl if file, ok := file.(FileSizeHint); ok { size := util.ReadUint64(mod, pArg) err := file.SizeHint(int64(size)) - return vfsAPIErrorCode(err, _IOERR_TRUNCATE) + return vfsErrorCode(err, _IOERR_TRUNCATE) } case _FCNTL_HAS_MOVED: @@ -303,7 +312,7 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl } util.WriteUint32(mod, pArg, res) - return vfsAPIErrorCode(err, _IOERR_FSTAT) + return vfsErrorCode(err, _IOERR_FSTAT) } } @@ -326,17 +335,22 @@ func vfsDeviceCharacteristics(ctx context.Context, mod api.Module, pFile uint32) return file.DeviceCharacteristics() } -func vfsAPIGet(mod api.Module, pVfs uint32) VFS { - if pVfs != 0 { - name := util.ReadString(mod, util.ReadUint32(mod, pVfs+16), _MAX_STRING) - if vfs := Find(name); vfs != nil { - return vfs - } +func vfsGet(mod api.Module, pVfs uint32) VFS { + if pVfs == 0 { + return vfsOS{} } - return vfsOS{} + const zNameOffset = 16 + name := util.ReadString(mod, util.ReadUint32(mod, pVfs+zNameOffset), _MAX_STRING) + if name == "os" { + return vfsOS{} + } + if vfs := Find(name); vfs != nil { + return vfs + } + panic(util.NoVFSErr + util.ErrorString(name)) } -func vfsAPIErrorCode(err error, def _ErrorCode) _ErrorCode { +func vfsErrorCode(err error, def _ErrorCode) _ErrorCode { if err == nil { return _OK } diff --git a/sqlite3vfs/vfs_api.go b/sqlite3vfs/vfs_api.go index cb28b6c..7849b58 100644 --- a/sqlite3vfs/vfs_api.go +++ b/sqlite3vfs/vfs_api.go @@ -1,7 +1,13 @@ +// Package sqlite3vfs wraps the C SQLite VFS API. package sqlite3vfs -import "sync" +import ( + "sync" +) +// A VFS defines the interface between the SQLite core and the underlying operating system. +// +// https://www.sqlite.org/c3ref/vfs.html type VFS interface { Open(name string, flags OpenFlag) (File, OpenFlag, error) Delete(name string, syncDir bool) error @@ -9,6 +15,10 @@ type VFS interface { FullPathname(name string) (string, error) } +// A File represents an open file in the OS interface layer. +// +// https://www.sqlite.org/c3ref/file.html +// https://www.sqlite.org/c3ref/io_methods.html type File interface { Close() error ReadAt(p []byte, off int64) (n int, err error) @@ -23,21 +33,37 @@ type File interface { DeviceCharacteristics() DeviceCharacteristic } +// FileLockState extends [File] to implement the +// SQLITE_FCNTL_LOCKSTATE file control opcode. +// +// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html type FileLockState interface { File LockState() LockLevel } +// FileLockState extends [File] to implement the +// SQLITE_FCNTL_SIZE_HINT file control opcode. +// +// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html type FileSizeHint interface { File SizeHint(size int64) error } +// FileLockState extends [File] to implement the +// SQLITE_FCNTL_HAS_MOVED file control opcode. +// +// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html type FileHasMoved interface { File HasMoved() (bool, error) } +// FileLockState extends [File] to implement the +// SQLITE_FCNTL_POWERSAFE_OVERWRITE file control opcode. +// +// https://www.sqlite.org/c3ref/c_fcntl_begin_atomic_write.html type FilePowersafeOverwrite interface { File PowersafeOverwrite() bool @@ -49,12 +75,19 @@ var ( vfsRegistryMtx sync.Mutex ) +// Find returns a VFS given its name. +// If there is no match, nil is returned. +// +// https://www.sqlite.org/c3ref/vfs_find.html func Find(name string) VFS { vfsRegistryMtx.Lock() defer vfsRegistryMtx.Unlock() return vfsRegistry[name] } +// Register registers a VFS. +// +// https://www.sqlite.org/c3ref/vfs_find.html func Register(name string, vfs VFS) { vfsRegistryMtx.Lock() defer vfsRegistryMtx.Unlock() @@ -64,6 +97,9 @@ func Register(name string, vfs VFS) { vfsRegistry[name] = vfs } +// Unregister unregisters a VFS. +// +// https://www.sqlite.org/c3ref/vfs_find.html func Unregister(name string) { vfsRegistryMtx.Lock() defer vfsRegistryMtx.Unlock() diff --git a/sqlite3vfs/vfs_file.go b/sqlite3vfs/vfs_file.go index f20ab0e..d9ee18b 100644 --- a/sqlite3vfs/vfs_file.go +++ b/sqlite3vfs/vfs_file.go @@ -143,19 +143,22 @@ func vfsFileNew(vfs *vfsState, file File) uint32 { } func vfsFileRegister(ctx context.Context, mod api.Module, pFile uint32, file File) { + const fileHandleOffset = 4 id := vfsFileNew(ctx.Value(vfsKey{}).(*vfsState), file) - util.WriteUint32(mod, pFile+4, id) + util.WriteUint32(mod, pFile+fileHandleOffset, id) } func vfsFileGet(ctx context.Context, mod api.Module, pFile uint32) File { + const fileHandleOffset = 4 vfs := ctx.Value(vfsKey{}).(*vfsState) - id := util.ReadUint32(mod, pFile+4) + id := util.ReadUint32(mod, pFile+fileHandleOffset) return vfs.files[id] } func vfsFileClose(ctx context.Context, mod api.Module, pFile uint32) error { + const fileHandleOffset = 4 vfs := ctx.Value(vfsKey{}).(*vfsState) - id := util.ReadUint32(mod, pFile+4) + id := util.ReadUint32(mod, pFile+fileHandleOffset) file := vfs.files[id] vfs.files[id] = nil return file.Close() diff --git a/sqlite3vfs/vfs_os_darwin.go b/sqlite3vfs/vfs_os_darwin.go index 39e0b58..4f908dd 100644 --- a/sqlite3vfs/vfs_os_darwin.go +++ b/sqlite3vfs/vfs_os_darwin.go @@ -47,7 +47,7 @@ func osAllocate(file *os.File, size int64) error { Length: size, } - // Try to get a continous chunk of disk space. + // Try to get a continuous chunk of disk space. err = unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store) if err != nil { // OK, perhaps we are too fragmented, allocate non-continuous.