More VFS API.

This commit is contained in:
Nuno Cruces
2023-05-17 14:04:00 +01:00
parent f6a887dd1c
commit c94cdaf720
6 changed files with 292 additions and 106 deletions

View File

@@ -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].

View File

@@ -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

View File

@@ -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)
}

98
sqlite3vfs/const.go Normal file
View File

@@ -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 */
)

49
sqlite3vfs/vfs.go Normal file
View File

@@ -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)
}

View File

@@ -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()
}