VFS API refactor.

This commit is contained in:
Nuno Cruces
2024-10-21 14:51:30 +01:00
parent 9eec439d35
commit 21de004779
5 changed files with 224 additions and 157 deletions

View File

@@ -6,12 +6,58 @@ import (
"github.com/ncruces/go-sqlite3/vfs"
)
// WrapSharedMemory helps wrap [vfs.FileSharedMemory].
func WrapSharedMemory(f vfs.File) vfs.SharedMemory {
if f, ok := f.(vfs.FileSharedMemory); ok {
return f.SharedMemory()
// UnwrapFile unwraps a [vfs.File],
// possibly implementing [vfs.FileUnwrap],
// to a concrete type.
func UnwrapFile[T vfs.File](f vfs.File) (_ T, _ bool) {
for {
switch t := f.(type) {
default:
return
case T:
return t, true
case vfs.FileUnwrap:
f = t.Unwrap()
}
}
}
// WrapLockState helps wrap [vfs.FileLockState].
func WrapLockState(f vfs.File) vfs.LockLevel {
if f, ok := f.(vfs.FileLockState); ok {
return f.LockState()
}
return vfs.LOCK_EXCLUSIVE + 1 // UNKNOWN_LOCK
}
// WrapPersistentWAL helps wrap [vfs.FilePersistentWAL].
func WrapPersistentWAL(f vfs.File) bool {
if f, ok := f.(vfs.FilePersistentWAL); ok {
return f.PersistentWAL()
}
return false
}
// WrapSetPersistentWAL helps wrap [vfs.FilePersistentWAL].
func WrapSetPersistentWAL(f vfs.File, keepWAL bool) {
if f, ok := f.(vfs.FilePersistentWAL); ok {
f.SetPersistentWAL(keepWAL)
}
}
// WrapPowersafeOverwrite helps wrap [vfs.FilePowersafeOverwrite].
func WrapPowersafeOverwrite(f vfs.File) bool {
if f, ok := f.(vfs.FilePowersafeOverwrite); ok {
return f.PowersafeOverwrite()
}
return false
}
// WrapSetPowersafeOverwrite helps wrap [vfs.FilePowersafeOverwrite].
func WrapSetPowersafeOverwrite(f vfs.File, psow bool) {
if f, ok := f.(vfs.FilePowersafeOverwrite); ok {
f.SetPowersafeOverwrite(psow)
}
return nil
}
// WrapChunkSize helps wrap [vfs.FileChunkSize].
@@ -45,36 +91,6 @@ func WrapOverwrite(f vfs.File) error {
return sqlite3.NOTFOUND
}
// WrapPersistentWAL helps wrap [vfs.FilePersistentWAL].
func WrapPersistentWAL(f vfs.File) bool {
if f, ok := f.(vfs.FilePersistentWAL); ok {
return f.PersistentWAL()
}
return false
}
// WrapSetPersistentWAL helps wrap [vfs.FilePersistentWAL].
func WrapSetPersistentWAL(f vfs.File, keepWAL bool) {
if f, ok := f.(vfs.FilePersistentWAL); ok {
f.SetPersistentWAL(keepWAL)
}
}
// WrapPowersafeOverwrite helps wrap [vfs.FilePowersafeOverwrite].
func WrapPowersafeOverwrite(f vfs.File) bool {
if f, ok := f.(vfs.FilePowersafeOverwrite); ok {
return f.PowersafeOverwrite()
}
return false
}
// WrapSetPowersafeOverwrite helps wrap [vfs.FilePowersafeOverwrite].
func WrapSetPowersafeOverwrite(f vfs.File, psow bool) {
if f, ok := f.(vfs.FilePowersafeOverwrite); ok {
f.SetPowersafeOverwrite(psow)
}
}
// WrapCommitPhaseTwo helps wrap [vfs.FileCommitPhaseTwo].
func WrapCommitPhaseTwo(f vfs.File) error {
if f, ok := f.(vfs.FileCommitPhaseTwo); ok {
@@ -107,20 +123,18 @@ func WrapRollbackAtomicWrite(f vfs.File) error {
return sqlite3.NOTFOUND
}
// WrapCheckpointDone helps wrap [vfs.FileCheckpoint].
func WrapCheckpointDone(f vfs.File) error {
// WrapCheckpointStart helps wrap [vfs.FileCheckpoint].
func WrapCheckpointStart(f vfs.File) {
if f, ok := f.(vfs.FileCheckpoint); ok {
return f.CheckpointDone()
f.CheckpointStart()
}
return sqlite3.NOTFOUND
}
// WrapCheckpointStart helps wrap [vfs.FileCheckpoint].
func WrapCheckpointStart(f vfs.File) error {
// WrapCheckpointDone helps wrap [vfs.FileCheckpoint].
func WrapCheckpointDone(f vfs.File) {
if f, ok := f.(vfs.FileCheckpoint); ok {
return f.CheckpointStart()
f.CheckpointDone()
}
return sqlite3.NOTFOUND
}
// WrapPragma helps wrap [vfs.FilePragma].
@@ -130,3 +144,11 @@ func WrapPragma(f vfs.File, name, value string) (string, error) {
}
return "", sqlite3.NOTFOUND
}
// WrapSharedMemory helps wrap [vfs.FileSharedMemory].
func WrapSharedMemory(f vfs.File) vfs.SharedMemory {
if f, ok := f.(vfs.FileSharedMemory); ok {
return f.SharedMemory()
}
return nil
}

View File

@@ -36,7 +36,7 @@ func (h *hbshVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs
}
var hbsh *hbsh.HBSH
if f, ok := name.DatabaseFile().(*hbshFile); ok {
if f, ok := vfsutil.UnwrapFile[*hbshFile](name.DatabaseFile()); ok {
hbsh = f.hbsh
} else {
var key []byte
@@ -71,6 +71,17 @@ const (
blockSize = 4096
)
// Ensure blockSize is a power of two.
var _ [0]struct{} = [blockSize & (blockSize - 1)]struct{}{}
func roundDown(i int64) int64 {
return i &^ (blockSize - 1)
}
func roundUp[T int | int64](i T) T {
return (i + (blockSize - 1)) &^ (blockSize - 1)
}
type hbshFile struct {
vfs.File
init HBSHCreator
@@ -111,8 +122,8 @@ func (h *hbshFile) ReadAt(p []byte, off int64) (n int, err error) {
return 0, sqlite3.CANTOPEN
}
min := (off) &^ (blockSize - 1) // round down
max := (off + int64(len(p)) + (blockSize - 1)) &^ (blockSize - 1) // round up
min := roundDown(off)
max := roundUp(off + int64(len(p)))
// Read one block at a time.
for ; min < max; min += blockSize {
@@ -141,8 +152,8 @@ func (h *hbshFile) WriteAt(p []byte, off int64) (n int, err error) {
return 0, sqlite3.READONLY
}
min := (off) &^ (blockSize - 1) // round down
max := (off + int64(len(p)) + (blockSize - 1)) &^ (blockSize - 1) // round up
min := roundDown(off)
max := roundUp(off + int64(len(p)))
// Write one block at a time.
for ; min < max; min += blockSize {
@@ -187,45 +198,44 @@ func (h *hbshFile) WriteAt(p []byte, off int64) (n int, err error) {
return n, nil
}
func (h *hbshFile) Truncate(size int64) error {
size = (size + (blockSize - 1)) &^ (blockSize - 1) // round up
return h.File.Truncate(size)
func (h *hbshFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
var _ [0]struct{} = [blockSize - 4096]struct{}{} // Ensure blockSize is 4K.
return h.File.DeviceCharacteristics() & (0 |
// The only safe flags are these:
vfs.IOCAP_ATOMIC4K |
vfs.IOCAP_UNDELETABLE_WHEN_OPEN |
vfs.IOCAP_IMMUTABLE |
vfs.IOCAP_BATCH_ATOMIC)
}
func (h *hbshFile) SectorSize() int {
return util.LCM(h.File.SectorSize(), blockSize)
}
func (h *hbshFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
return h.File.DeviceCharacteristics() & (0 |
// The only safe flags are these:
vfs.IOCAP_UNDELETABLE_WHEN_OPEN |
vfs.IOCAP_IMMUTABLE |
vfs.IOCAP_BATCH_ATOMIC)
func (h *hbshFile) Truncate(size int64) error {
return h.File.Truncate(roundUp(size))
}
// Wrap optional methods.
func (h *hbshFile) ChunkSize(size int) {
vfsutil.WrapChunkSize(h.File, roundUp(size))
}
func (h *hbshFile) SizeHint(size int64) error {
return vfsutil.WrapSizeHint(h.File, roundUp(size))
}
func (h *hbshFile) Unwrap() vfs.File {
return h.File
}
func (h *hbshFile) SharedMemory() vfs.SharedMemory {
return vfsutil.WrapSharedMemory(h.File)
}
func (h *hbshFile) ChunkSize(size int) {
size = (size + (blockSize - 1)) &^ (blockSize - 1) // round up
vfsutil.WrapChunkSize(h.File, size)
}
// Wrap optional methods.
func (h *hbshFile) SizeHint(size int64) error {
size = (size + (blockSize - 1)) &^ (blockSize - 1) // round up
return vfsutil.WrapSizeHint(h.File, size)
}
func (h *hbshFile) HasMoved() (bool, error) {
return vfsutil.WrapHasMoved(h.File) // notest
}
func (h *hbshFile) Overwrite() error {
return vfsutil.WrapOverwrite(h.File) // notest
func (h *hbshFile) LockState() vfs.LockLevel {
return vfsutil.WrapLockState(h.File) // notest
}
func (h *hbshFile) PersistentWAL() bool {
@@ -236,6 +246,14 @@ func (h *hbshFile) SetPersistentWAL(keepWAL bool) {
vfsutil.WrapSetPersistentWAL(h.File, keepWAL) // notest
}
func (h *hbshFile) HasMoved() (bool, error) {
return vfsutil.WrapHasMoved(h.File) // notest
}
func (h *hbshFile) Overwrite() error {
return vfsutil.WrapOverwrite(h.File) // notest
}
func (h *hbshFile) CommitPhaseTwo() error {
return vfsutil.WrapCommitPhaseTwo(h.File) // notest
}
@@ -252,10 +270,10 @@ func (h *hbshFile) RollbackAtomicWrite() error {
return vfsutil.WrapRollbackAtomicWrite(h.File) // notest
}
func (h *hbshFile) CheckpointDone() error {
return vfsutil.WrapCheckpointDone(h.File) // notest
func (h *hbshFile) CheckpointStart() {
vfsutil.WrapCheckpointStart(h.File) // notest
}
func (h *hbshFile) CheckpointStart() error {
return vfsutil.WrapCheckpointStart(h.File) // notest
func (h *hbshFile) CheckpointDone() {
vfsutil.WrapCheckpointDone(h.File) // notest
}

View File

@@ -49,6 +49,13 @@ type File interface {
DeviceCharacteristics() DeviceCharacteristic
}
// FileUnwrap should be implemented by a File
// that wraps another File implementation.
type FileUnwrap interface {
File
Unwrap() File
}
// FileLockState extends File to implement the
// SQLITE_FCNTL_LOCKSTATE file control opcode.
//
@@ -58,6 +65,26 @@ type FileLockState interface {
LockState() LockLevel
}
// FilePersistentWAL extends File to implement the
// SQLITE_FCNTL_PERSIST_WAL file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpersistwal
type FilePersistentWAL interface {
File
PersistentWAL() bool
SetPersistentWAL(bool)
}
// FilePowersafeOverwrite extends File to implement the
// SQLITE_FCNTL_POWERSAFE_OVERWRITE file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpowersafeoverwrite
type FilePowersafeOverwrite interface {
File
PowersafeOverwrite() bool
SetPowersafeOverwrite(bool)
}
// FileChunkSize extends File to implement the
// SQLITE_FCNTL_CHUNK_SIZE file control opcode.
//
@@ -94,26 +121,6 @@ type FileOverwrite interface {
Overwrite() error
}
// FilePersistentWAL extends File to implement the
// SQLITE_FCNTL_PERSIST_WAL file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpersistwal
type FilePersistentWAL interface {
File
PersistentWAL() bool
SetPersistentWAL(bool)
}
// FilePowersafeOverwrite extends File to implement the
// SQLITE_FCNTL_POWERSAFE_OVERWRITE file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpowersafeoverwrite
type FilePowersafeOverwrite interface {
File
PowersafeOverwrite() bool
SetPowersafeOverwrite(bool)
}
// FileCommitPhaseTwo extends File to implement the
// SQLITE_FCNTL_COMMIT_PHASETWO file control opcode.
//
@@ -135,15 +142,6 @@ type FileBatchAtomicWrite interface {
RollbackAtomicWrite() error
}
// FilePragma extends File to implement the
// SQLITE_FCNTL_PRAGMA file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpragma
type FilePragma interface {
File
Pragma(name, value string) (string, error)
}
// FileCheckpoint extends File to implement the
// SQLITE_FCNTL_CKPT_START and SQLITE_FCNTL_CKPT_DONE
// file control opcodes.
@@ -151,8 +149,17 @@ type FilePragma interface {
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlckptstart
type FileCheckpoint interface {
File
CheckpointDone() error
CheckpointStart() error
CheckpointStart()
CheckpointDone()
}
// FilePragma extends File to implement the
// SQLITE_FCNTL_PRAGMA file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlpragma
type FilePragma interface {
File
Pragma(name, value string) (string, error)
}
// FileSharedMemory extends File to possibly implement

View File

@@ -239,14 +239,8 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl
switch op {
case _FCNTL_LOCKSTATE:
if file, ok := file.(FileLockState); ok {
util.WriteUint32(mod, pArg, uint32(file.LockState()))
return _OK
}
case _FCNTL_LOCK_TIMEOUT:
if file, ok := file.(FileSharedMemory); ok {
if shm, ok := file.SharedMemory().(blockingSharedMemory); ok {
shm.shmEnableBlocking(util.ReadUint32(mod, pArg) != 0)
if lk := file.LockState(); lk <= LOCK_EXCLUSIVE {
util.WriteUint32(mod, pArg, uint32(lk))
return _OK
}
}
@@ -328,15 +322,15 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl
return vfsErrorCode(err, _IOERR_ROLLBACK_ATOMIC)
}
case _FCNTL_CKPT_DONE:
if file, ok := file.(FileCheckpoint); ok {
err := file.CheckpointDone()
return vfsErrorCode(err, _IOERR)
}
case _FCNTL_CKPT_START:
if file, ok := file.(FileCheckpoint); ok {
err := file.CheckpointStart()
return vfsErrorCode(err, _IOERR)
file.CheckpointStart()
return _OK
}
case _FCNTL_CKPT_DONE:
if file, ok := file.(FileCheckpoint); ok {
file.CheckpointDone()
return _OK
}
case _FCNTL_PRAGMA:
@@ -365,6 +359,14 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl
}
return ret
}
case _FCNTL_LOCK_TIMEOUT:
if file, ok := file.(FileSharedMemory); ok {
if shm, ok := file.SharedMemory().(blockingSharedMemory); ok {
shm.shmEnableBlocking(util.ReadUint32(mod, pArg) != 0)
return _OK
}
}
}
// Consider also implementing these opcodes (in use by SQLite):

View File

@@ -35,7 +35,7 @@ func (x *xtsVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs.
}
var cipher *xts.Cipher
if f, ok := name.DatabaseFile().(*xtsFile); ok {
if f, ok := vfsutil.UnwrapFile[*xtsFile](name.DatabaseFile()); ok {
cipher = f.cipher
} else {
var key []byte
@@ -67,6 +67,17 @@ func (x *xtsVFS) OpenFilename(name *vfs.Filename, flags vfs.OpenFlag) (file vfs.
// https://sqlite.org/fileformat.html#pages
const sectorSize = 512
// Ensure sectorSize is a power of two.
var _ [0]struct{} = [sectorSize & (sectorSize - 1)]struct{}{}
func roundDown(i int64) int64 {
return i &^ (sectorSize - 1)
}
func roundUp[T int | int64](i T) T {
return (i + (sectorSize - 1)) &^ (sectorSize - 1)
}
type xtsFile struct {
vfs.File
init XTSCreator
@@ -106,8 +117,8 @@ func (x *xtsFile) ReadAt(p []byte, off int64) (n int, err error) {
return 0, sqlite3.CANTOPEN
}
min := (off) &^ (sectorSize - 1) // round down
max := (off + int64(len(p)) + (sectorSize - 1)) &^ (sectorSize - 1) // round up
min := roundDown(off)
max := roundUp(off + int64(len(p)))
// Read one block at a time.
for ; min < max; min += sectorSize {
@@ -137,8 +148,8 @@ func (x *xtsFile) WriteAt(p []byte, off int64) (n int, err error) {
return 0, sqlite3.READONLY
}
min := (off) &^ (sectorSize - 1) // round down
max := (off + int64(len(p)) + (sectorSize - 1)) &^ (sectorSize - 1) // round up
min := roundDown(off)
max := roundUp(off + int64(len(p)))
// Write one block at a time.
for ; min < max; min += sectorSize {
@@ -183,45 +194,44 @@ func (x *xtsFile) WriteAt(p []byte, off int64) (n int, err error) {
return n, nil
}
func (x *xtsFile) Truncate(size int64) error {
size = (size + (sectorSize - 1)) &^ (sectorSize - 1) // round up
return x.File.Truncate(size)
func (x *xtsFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
var _ [0]struct{} = [sectorSize - 512]struct{}{} // Ensure sectorSize is 512.
return x.File.DeviceCharacteristics() & (0 |
// The only safe flags are these:
vfs.IOCAP_ATOMIC512 |
vfs.IOCAP_UNDELETABLE_WHEN_OPEN |
vfs.IOCAP_IMMUTABLE |
vfs.IOCAP_BATCH_ATOMIC)
}
func (x *xtsFile) SectorSize() int {
return util.LCM(x.File.SectorSize(), sectorSize)
}
func (x *xtsFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
return x.File.DeviceCharacteristics() & (0 |
// The only safe flags are these:
vfs.IOCAP_UNDELETABLE_WHEN_OPEN |
vfs.IOCAP_IMMUTABLE |
vfs.IOCAP_BATCH_ATOMIC)
func (x *xtsFile) Truncate(size int64) error {
return x.File.Truncate(roundUp(size))
}
// Wrap optional methods.
func (x *xtsFile) ChunkSize(size int) {
vfsutil.WrapChunkSize(x.File, roundUp(size))
}
func (x *xtsFile) SizeHint(size int64) error {
return vfsutil.WrapSizeHint(x.File, roundUp(size))
}
func (x *xtsFile) Unwrap() vfs.File {
return x.File
}
func (x *xtsFile) SharedMemory() vfs.SharedMemory {
return vfsutil.WrapSharedMemory(x.File)
}
func (x *xtsFile) ChunkSize(size int) {
size = (size + (sectorSize - 1)) &^ (sectorSize - 1) // round up
vfsutil.WrapChunkSize(x.File, size)
}
// Wrap optional methods.
func (x *xtsFile) SizeHint(size int64) error {
size = (size + (sectorSize - 1)) &^ (sectorSize - 1) // round up
return vfsutil.WrapSizeHint(x.File, size)
}
func (x *xtsFile) HasMoved() (bool, error) {
return vfsutil.WrapHasMoved(x.File) // notest
}
func (x *xtsFile) Overwrite() error {
return vfsutil.WrapOverwrite(x.File) // notest
func (x *xtsFile) LockState() vfs.LockLevel {
return vfsutil.WrapLockState(x.File) // notest
}
func (x *xtsFile) PersistentWAL() bool {
@@ -232,6 +242,14 @@ func (x *xtsFile) SetPersistentWAL(keepWAL bool) {
vfsutil.WrapSetPersistentWAL(x.File, keepWAL) // notest
}
func (x *xtsFile) HasMoved() (bool, error) {
return vfsutil.WrapHasMoved(x.File) // notest
}
func (x *xtsFile) Overwrite() error {
return vfsutil.WrapOverwrite(x.File) // notest
}
func (x *xtsFile) CommitPhaseTwo() error {
return vfsutil.WrapCommitPhaseTwo(x.File) // notest
}
@@ -248,10 +266,10 @@ func (x *xtsFile) RollbackAtomicWrite() error {
return vfsutil.WrapRollbackAtomicWrite(x.File) // notest
}
func (x *xtsFile) CheckpointDone() error {
return vfsutil.WrapCheckpointDone(x.File) // notest
func (x *xtsFile) CheckpointStart() {
vfsutil.WrapCheckpointStart(x.File) // notest
}
func (x *xtsFile) CheckpointStart() error {
return vfsutil.WrapCheckpointStart(x.File) // notest
func (x *xtsFile) CheckpointDone() {
vfsutil.WrapCheckpointDone(x.File) // notest
}