[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)

This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
This commit is contained in:
kim
2024-05-27 15:46:15 +00:00
committed by GitHub
parent cce21c11cb
commit 1e7b32490d
398 changed files with 86174 additions and 684 deletions

86
vendor/github.com/ncruces/go-sqlite3/vfs/README.md generated vendored Normal file
View File

@ -0,0 +1,86 @@
# Go SQLite VFS API
This package implements the SQLite [OS Interface](https://sqlite.org/vfs.html) (aka VFS).
It replaces the default SQLite VFS with a **pure Go** implementation,
and exposes [interfaces](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#VFS)
that should allow you to implement your own custom VFSes.
Since it is a from scratch reimplementation,
there are naturally some ways it deviates from the original.
The main differences are [file locking](#file-locking) and [WAL mode](#write-ahead-logging) support.
### File Locking
POSIX advisory locks, which SQLite uses on Unix, are
[broken by design](https://github.com/sqlite/sqlite/blob/b74eb0/src/os_unix.c#L1073-L1161).
On Linux and macOS, this module uses
[OFD locks](https://www.gnu.org/software/libc/manual/html_node/Open-File-Description-Locks.html)
to synchronize access to database files.
OFD locks are fully compatible with POSIX advisory locks.
This module can also use
[BSD locks](https://man.freebsd.org/cgi/man.cgi?query=flock&sektion=2),
albeit with reduced concurrency (`BEGIN IMMEDIATE` behaves like `BEGIN EXCLUSIVE`).
On BSD, macOS, and illumos, BSD locks are fully compatible with POSIX advisory locks;
on Linux and z/OS, they are fully functional, but incompatible;
elsewhere, they are very likely broken.
BSD locks are the default on BSD and illumos,
but you can opt into them with the `sqlite3_flock` build tag.
On Windows, this module uses `LockFileEx` and `UnlockFileEx`,
like SQLite.
Otherwise, file locking is not supported, and you must use
[`nolock=1`](https://sqlite.org/uri.html#urinolock)
(or [`immutable=1`](https://sqlite.org/uri.html#uriimmutable))
to open database files.
To use the [`database/sql`](https://pkg.go.dev/database/sql) driver
with `nolock=1` you must disable connection pooling by calling
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
You can use [`vfs.SupportsFileLocking`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsFileLocking)
to check if your build supports file locking.
### Write-Ahead Logging
On 64-bit Linux and macOS, this module uses `mmap` to implement
[shared-memory for the WAL-index](https://sqlite.org/wal.html#implementation_of_shared_memory_for_the_wal_index),
like SQLite.
To allow `mmap` to work, each connection needs to reserve up to 4GB of address space.
To limit the address space each connection reserves,
use [`WithMemoryLimitPages`](../tests/testcfg/testcfg.go).
Otherwise, [WAL support is limited](https://sqlite.org/wal.html#noshm),
and `EXCLUSIVE` locking mode must be set to create, read, and write WAL databases.
To use `EXCLUSIVE` locking mode with the
[`database/sql`](https://pkg.go.dev/database/sql) driver
you must disable connection pooling by calling
[`db.SetMaxOpenConns(1)`](https://pkg.go.dev/database/sql#DB.SetMaxOpenConns).
You can use [`vfs.SupportsSharedMemory`](https://pkg.go.dev/github.com/ncruces/go-sqlite3/vfs#SupportsSharedMemory)
to check if your build supports shared memory.
### Batch-Atomic Write
On 64-bit Linux, this module supports [batch-atomic writes](https://sqlite.org/cgi/src/technote/714)
on the F2FS filesystem.
### Build Tags
The VFS can be customized with a few build tags:
- `sqlite3_flock` forces the use of BSD locks; it can be used on z/OS to enable locking,
and elsewhere to test BSD locks.
- `sqlite3_nosys` prevents importing [`x/sys`](https://pkg.go.dev/golang.org/x/sys);
disables locking _and_ shared memory on all platforms.
- `sqlite3_noshm` disables shared memory on all platforms.
> [!IMPORTANT]
> The default configuration of this package is compatible with
> the standard [Unix and Windows SQLite VFSes](https://sqlite.org/vfs.html#multiple_vfses);
> `sqlite3_flock` is compatible with the [`unix-flock` VFS](https://sqlite.org/compile.html#enable_locking_style).
> If incompatible file locking is used, accessing databases concurrently with _other_ SQLite libraries
> will eventually corrupt data.

175
vendor/github.com/ncruces/go-sqlite3/vfs/api.go generated vendored Normal file
View File

@ -0,0 +1,175 @@
// Package vfs wraps the C SQLite VFS API.
package vfs
import (
"context"
"io"
"github.com/tetratelabs/wazero/api"
)
// A VFS defines the interface between the SQLite core and the underlying operating system.
//
// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite.
//
// https://sqlite.org/c3ref/vfs.html
type VFS interface {
Open(name string, flags OpenFlag) (File, OpenFlag, error)
Delete(name string, syncDir bool) error
Access(name string, flags AccessFlag) (bool, error)
FullPathname(name string) (string, error)
}
// VFSFilename extends VFS with the ability to use Filename
// objects for opening files.
//
// https://sqlite.org/c3ref/filename.html
type VFSFilename interface {
VFS
OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error)
}
// A File represents an open file in the OS interface layer.
//
// Use sqlite3.ErrorCode or sqlite3.ExtendedErrorCode to return specific error codes to SQLite.
// In particular, sqlite3.BUSY is necessary to correctly implement lock methods.
//
// https://sqlite.org/c3ref/io_methods.html
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(flags SyncFlag) error
Size() (int64, error)
Lock(lock LockLevel) error
Unlock(lock LockLevel) error
CheckReservedLock() (bool, error)
SectorSize() int
DeviceCharacteristics() DeviceCharacteristic
}
// FileLockState extends File to implement the
// SQLITE_FCNTL_LOCKSTATE file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntllockstate
type FileLockState interface {
File
LockState() LockLevel
}
// FileChunkSize extends File to implement the
// SQLITE_FCNTL_CHUNK_SIZE file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlchunksize
type FileChunkSize interface {
File
ChunkSize(size int)
}
// FileSizeHint extends File to implement the
// SQLITE_FCNTL_SIZE_HINT file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlsizehint
type FileSizeHint interface {
File
SizeHint(size int64) error
}
// FileHasMoved extends File to implement the
// SQLITE_FCNTL_HAS_MOVED file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlhasmoved
type FileHasMoved interface {
File
HasMoved() (bool, error)
}
// FileOverwrite extends File to implement the
// SQLITE_FCNTL_OVERWRITE file control opcode.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntloverwrite
type FileOverwrite interface {
File
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.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlcommitphasetwo
type FileCommitPhaseTwo interface {
File
CommitPhaseTwo() error
}
// FileBatchAtomicWrite extends File to implement the
// SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE
// and SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE file control opcodes.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlbeginatomicwrite
type FileBatchAtomicWrite interface {
File
BeginAtomicWrite() error
CommitAtomicWrite() error
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.
//
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html#sqlitefcntlckptstart
type FileCheckpoint interface {
File
CheckpointDone() error
CheckpointStart() error
}
// FileSharedMemory extends File to possibly implement
// shared-memory for the WAL-index.
// The same shared-memory instance must be returned
// for the entire life of the file.
// It's OK for SharedMemory to return nil.
type FileSharedMemory interface {
File
SharedMemory() SharedMemory
}
// SharedMemory is a shared-memory WAL-index implementation.
// Use [NewSharedMemory] to create a shared-memory.
type SharedMemory interface {
shmMap(context.Context, api.Module, int32, int32, bool) (uint32, error)
shmLock(int32, int32, _ShmFlag) error
shmUnmap(bool)
io.Closer
}

234
vendor/github.com/ncruces/go-sqlite3/vfs/const.go generated vendored Normal file
View File

@ -0,0 +1,234 @@
package vfs
import "github.com/ncruces/go-sqlite3/internal/util"
const (
_MAX_NAME = 1e6 // Self-imposed limit for most NUL terminated strings.
_MAX_SQL_LENGTH = 1e9
_MAX_PATHNAME = 1024
_DEFAULT_SECTOR_SIZE = 4096
ptrlen = 4
)
// https://sqlite.org/rescode.html
type _ErrorCode uint32
func (e _ErrorCode) Error() string {
return util.ErrorCodeString(uint32(e))
}
const (
_OK _ErrorCode = util.OK
_ERROR _ErrorCode = util.ERROR
_PERM _ErrorCode = util.PERM
_BUSY _ErrorCode = util.BUSY
_READONLY _ErrorCode = util.READONLY
_IOERR _ErrorCode = util.IOERR
_NOTFOUND _ErrorCode = util.NOTFOUND
_CANTOPEN _ErrorCode = util.CANTOPEN
_IOERR_READ _ErrorCode = util.IOERR_READ
_IOERR_SHORT_READ _ErrorCode = util.IOERR_SHORT_READ
_IOERR_WRITE _ErrorCode = util.IOERR_WRITE
_IOERR_FSYNC _ErrorCode = util.IOERR_FSYNC
_IOERR_DIR_FSYNC _ErrorCode = util.IOERR_DIR_FSYNC
_IOERR_TRUNCATE _ErrorCode = util.IOERR_TRUNCATE
_IOERR_FSTAT _ErrorCode = util.IOERR_FSTAT
_IOERR_UNLOCK _ErrorCode = util.IOERR_UNLOCK
_IOERR_RDLOCK _ErrorCode = util.IOERR_RDLOCK
_IOERR_DELETE _ErrorCode = util.IOERR_DELETE
_IOERR_ACCESS _ErrorCode = util.IOERR_ACCESS
_IOERR_CHECKRESERVEDLOCK _ErrorCode = util.IOERR_CHECKRESERVEDLOCK
_IOERR_LOCK _ErrorCode = util.IOERR_LOCK
_IOERR_CLOSE _ErrorCode = util.IOERR_CLOSE
_IOERR_SHMOPEN _ErrorCode = util.IOERR_SHMOPEN
_IOERR_SHMSIZE _ErrorCode = util.IOERR_SHMSIZE
_IOERR_SHMLOCK _ErrorCode = util.IOERR_SHMLOCK
_IOERR_SHMMAP _ErrorCode = util.IOERR_SHMMAP
_IOERR_SEEK _ErrorCode = util.IOERR_SEEK
_IOERR_DELETE_NOENT _ErrorCode = util.IOERR_DELETE_NOENT
_IOERR_BEGIN_ATOMIC _ErrorCode = util.IOERR_BEGIN_ATOMIC
_IOERR_COMMIT_ATOMIC _ErrorCode = util.IOERR_COMMIT_ATOMIC
_IOERR_ROLLBACK_ATOMIC _ErrorCode = util.IOERR_ROLLBACK_ATOMIC
_CANTOPEN_FULLPATH _ErrorCode = util.CANTOPEN_FULLPATH
_CANTOPEN_ISDIR _ErrorCode = util.CANTOPEN_ISDIR
_READONLY_CANTINIT _ErrorCode = util.READONLY_CANTINIT
_OK_SYMLINK _ErrorCode = util.OK_SYMLINK
)
// OpenFlag is a flag for the [VFS] Open method.
//
// https://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://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://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 Unlock methods.
//
// https://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 */
)
// DeviceCharacteristic is a flag retuned by the [File] DeviceCharacteristics method.
//
// https://sqlite.org/c3ref/c_iocap_atomic.html
type DeviceCharacteristic uint32
const (
IOCAP_ATOMIC DeviceCharacteristic = 0x00000001
IOCAP_ATOMIC512 DeviceCharacteristic = 0x00000002
IOCAP_ATOMIC1K DeviceCharacteristic = 0x00000004
IOCAP_ATOMIC2K DeviceCharacteristic = 0x00000008
IOCAP_ATOMIC4K DeviceCharacteristic = 0x00000010
IOCAP_ATOMIC8K DeviceCharacteristic = 0x00000020
IOCAP_ATOMIC16K DeviceCharacteristic = 0x00000040
IOCAP_ATOMIC32K DeviceCharacteristic = 0x00000080
IOCAP_ATOMIC64K DeviceCharacteristic = 0x00000100
IOCAP_SAFE_APPEND DeviceCharacteristic = 0x00000200
IOCAP_SEQUENTIAL DeviceCharacteristic = 0x00000400
IOCAP_UNDELETABLE_WHEN_OPEN DeviceCharacteristic = 0x00000800
IOCAP_POWERSAFE_OVERWRITE DeviceCharacteristic = 0x00001000
IOCAP_IMMUTABLE DeviceCharacteristic = 0x00002000
IOCAP_BATCH_ATOMIC DeviceCharacteristic = 0x00004000
)
// https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
type _FcntlOpcode uint32
const (
_FCNTL_LOCKSTATE _FcntlOpcode = 1
_FCNTL_GET_LOCKPROXYFILE _FcntlOpcode = 2
_FCNTL_SET_LOCKPROXYFILE _FcntlOpcode = 3
_FCNTL_LAST_ERRNO _FcntlOpcode = 4
_FCNTL_SIZE_HINT _FcntlOpcode = 5
_FCNTL_CHUNK_SIZE _FcntlOpcode = 6
_FCNTL_FILE_POINTER _FcntlOpcode = 7
_FCNTL_SYNC_OMITTED _FcntlOpcode = 8
_FCNTL_WIN32_AV_RETRY _FcntlOpcode = 9
_FCNTL_PERSIST_WAL _FcntlOpcode = 10
_FCNTL_OVERWRITE _FcntlOpcode = 11
_FCNTL_VFSNAME _FcntlOpcode = 12
_FCNTL_POWERSAFE_OVERWRITE _FcntlOpcode = 13
_FCNTL_PRAGMA _FcntlOpcode = 14
_FCNTL_BUSYHANDLER _FcntlOpcode = 15
_FCNTL_TEMPFILENAME _FcntlOpcode = 16
_FCNTL_MMAP_SIZE _FcntlOpcode = 18
_FCNTL_TRACE _FcntlOpcode = 19
_FCNTL_HAS_MOVED _FcntlOpcode = 20
_FCNTL_SYNC _FcntlOpcode = 21
_FCNTL_COMMIT_PHASETWO _FcntlOpcode = 22
_FCNTL_WIN32_SET_HANDLE _FcntlOpcode = 23
_FCNTL_WAL_BLOCK _FcntlOpcode = 24
_FCNTL_ZIPVFS _FcntlOpcode = 25
_FCNTL_RBU _FcntlOpcode = 26
_FCNTL_VFS_POINTER _FcntlOpcode = 27
_FCNTL_JOURNAL_POINTER _FcntlOpcode = 28
_FCNTL_WIN32_GET_HANDLE _FcntlOpcode = 29
_FCNTL_PDB _FcntlOpcode = 30
_FCNTL_BEGIN_ATOMIC_WRITE _FcntlOpcode = 31
_FCNTL_COMMIT_ATOMIC_WRITE _FcntlOpcode = 32
_FCNTL_ROLLBACK_ATOMIC_WRITE _FcntlOpcode = 33
_FCNTL_LOCK_TIMEOUT _FcntlOpcode = 34
_FCNTL_DATA_VERSION _FcntlOpcode = 35
_FCNTL_SIZE_LIMIT _FcntlOpcode = 36
_FCNTL_CKPT_DONE _FcntlOpcode = 37
_FCNTL_RESERVE_BYTES _FcntlOpcode = 38
_FCNTL_CKPT_START _FcntlOpcode = 39
_FCNTL_EXTERNAL_READER _FcntlOpcode = 40
_FCNTL_CKSM_FILE _FcntlOpcode = 41
_FCNTL_RESET_CACHE _FcntlOpcode = 42
)
// https://sqlite.org/c3ref/c_shm_exclusive.html
type _ShmFlag uint32
const (
_SHM_UNLOCK _ShmFlag = 1
_SHM_LOCK _ShmFlag = 2
_SHM_SHARED _ShmFlag = 4
_SHM_EXCLUSIVE _ShmFlag = 8
)

217
vendor/github.com/ncruces/go-sqlite3/vfs/file.go generated vendored Normal file
View File

@ -0,0 +1,217 @@
package vfs
import (
"errors"
"io"
"io/fs"
"os"
"path/filepath"
"runtime"
"syscall"
"github.com/ncruces/go-sqlite3/util/osutil"
)
type vfsOS struct{}
func (vfsOS) FullPathname(path string) (string, error) {
path, err := filepath.Abs(path)
if err != nil {
return "", err
}
fi, err := os.Lstat(path)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return path, nil
}
return "", err
}
if fi.Mode()&fs.ModeSymlink != 0 {
err = _OK_SYMLINK
}
return path, err
}
func (vfsOS) Delete(path string, syncDir bool) error {
err := os.Remove(path)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return _IOERR_DELETE_NOENT
}
return err
}
if runtime.GOOS != "windows" && syncDir {
f, err := os.Open(filepath.Dir(path))
if err != nil {
return _OK
}
defer f.Close()
err = osSync(f, false, false)
if err != nil {
return _IOERR_DIR_FSYNC
}
}
return nil
}
func (vfsOS) Access(name string, flags AccessFlag) (bool, error) {
err := osAccess(name, flags)
if flags == ACCESS_EXISTS {
if errors.Is(err, fs.ErrNotExist) {
return false, nil
}
} else {
if errors.Is(err, fs.ErrPermission) {
return false, nil
}
}
return err == nil, err
}
func (vfsOS) Open(name string, flags OpenFlag) (File, OpenFlag, error) {
return nil, 0, _CANTOPEN
}
func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error) {
var oflags int
if flags&OPEN_EXCLUSIVE != 0 {
oflags |= os.O_EXCL
}
if flags&OPEN_CREATE != 0 {
oflags |= os.O_CREATE
}
if flags&OPEN_READONLY != 0 {
oflags |= os.O_RDONLY
}
if flags&OPEN_READWRITE != 0 {
oflags |= os.O_RDWR
}
var err error
var f *os.File
if name == nil {
f, err = os.CreateTemp("", "*.db")
} else {
f, err = osutil.OpenFile(name.String(), oflags, 0666)
}
if err != nil {
if errors.Is(err, syscall.EISDIR) {
return nil, flags, _CANTOPEN_ISDIR
}
return nil, flags, err
}
if modeof := name.URIParameter("modeof"); modeof != "" {
if err = osSetMode(f, modeof); err != nil {
f.Close()
return nil, flags, _IOERR_FSTAT
}
}
if flags&OPEN_DELETEONCLOSE != 0 {
os.Remove(f.Name())
}
file := vfsFile{
File: f,
psow: true,
readOnly: flags&OPEN_READONLY != 0,
syncDir: runtime.GOOS != "windows" &&
flags&(OPEN_CREATE) != 0 &&
flags&(OPEN_MAIN_JOURNAL|OPEN_SUPER_JOURNAL|OPEN_WAL) != 0,
shm: NewSharedMemory(name.String()+"-shm", flags),
}
return &file, flags, nil
}
type vfsFile struct {
*os.File
shm SharedMemory
lock LockLevel
readOnly bool
keepWAL bool
syncDir bool
psow bool
}
var (
// Ensure these interfaces are implemented:
_ FileLockState = &vfsFile{}
_ FileHasMoved = &vfsFile{}
_ FileSizeHint = &vfsFile{}
_ FilePersistentWAL = &vfsFile{}
_ FilePowersafeOverwrite = &vfsFile{}
)
func (f *vfsFile) Close() error {
if f.shm != nil {
f.shm.Close()
}
return f.File.Close()
}
func (f *vfsFile) Sync(flags SyncFlag) error {
dataonly := (flags & SYNC_DATAONLY) != 0
fullsync := (flags & 0x0f) == SYNC_FULL
err := osSync(f.File, fullsync, dataonly)
if err != nil {
return err
}
if runtime.GOOS != "windows" && f.syncDir {
f.syncDir = false
d, err := os.Open(filepath.Dir(f.File.Name()))
if err != nil {
return nil
}
defer d.Close()
err = osSync(d, false, false)
if err != nil {
return _IOERR_DIR_FSYNC
}
}
return nil
}
func (f *vfsFile) Size() (int64, error) {
return f.Seek(0, io.SeekEnd)
}
func (f *vfsFile) SectorSize() int {
return _DEFAULT_SECTOR_SIZE
}
func (f *vfsFile) DeviceCharacteristics() DeviceCharacteristic {
var res DeviceCharacteristic
if osBatchAtomic(f.File) {
res |= IOCAP_BATCH_ATOMIC
}
if f.psow {
res |= IOCAP_POWERSAFE_OVERWRITE
}
return res
}
func (f *vfsFile) SizeHint(size int64) error {
return osAllocate(f.File, size)
}
func (f *vfsFile) HasMoved() (bool, error) {
fi, err := f.Stat()
if err != nil {
return false, err
}
pi, err := os.Stat(f.Name())
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return true, nil
}
return false, err
}
return !os.SameFile(fi, pi), nil
}
func (f *vfsFile) LockState() LockLevel { return f.lock }
func (f *vfsFile) PowersafeOverwrite() bool { return f.psow }
func (f *vfsFile) PersistentWAL() bool { return f.keepWAL }
func (f *vfsFile) SetPowersafeOverwrite(psow bool) { f.psow = psow }
func (f *vfsFile) SetPersistentWAL(keepWAL bool) { f.keepWAL = keepWAL }

174
vendor/github.com/ncruces/go-sqlite3/vfs/filename.go generated vendored Normal file
View File

@ -0,0 +1,174 @@
package vfs
import (
"context"
"net/url"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/tetratelabs/wazero/api"
)
// Filename is used by SQLite to pass filenames
// to the Open method of a VFS.
//
// https://sqlite.org/c3ref/filename.html
type Filename struct {
ctx context.Context
mod api.Module
zPath uint32
flags OpenFlag
stack [2]uint64
}
// OpenFilename is an internal API users should not call directly.
func OpenFilename(ctx context.Context, mod api.Module, id uint32, flags OpenFlag) *Filename {
if id == 0 {
return nil
}
return &Filename{
ctx: ctx,
mod: mod,
zPath: id,
flags: flags,
}
}
// String returns this filename as a string.
func (n *Filename) String() string {
if n == nil || n.zPath == 0 {
return ""
}
return util.ReadString(n.mod, n.zPath, _MAX_PATHNAME)
}
// Database returns the name of the corresponding database file.
//
// https://sqlite.org/c3ref/filename_database.html
func (n *Filename) Database() string {
return n.path("sqlite3_filename_database")
}
// Journal returns the name of the corresponding rollback journal file.
//
// https://sqlite.org/c3ref/filename_database.html
func (n *Filename) Journal() string {
return n.path("sqlite3_filename_journal")
}
// Journal returns the name of the corresponding WAL file.
//
// https://sqlite.org/c3ref/filename_database.html
func (n *Filename) WAL() string {
return n.path("sqlite3_filename_wal")
}
func (n *Filename) path(method string) string {
if n == nil || n.zPath == 0 {
return ""
}
n.stack[0] = uint64(n.zPath)
fn := n.mod.ExportedFunction(method)
if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil {
panic(err)
}
return util.ReadString(n.mod, uint32(n.stack[0]), _MAX_PATHNAME)
}
// DatabaseFile returns the main database [File] corresponding to a journal.
//
// https://sqlite.org/c3ref/database_file_object.html
func (n *Filename) DatabaseFile() File {
if n == nil || n.zPath == 0 {
return nil
}
if n.flags&(OPEN_MAIN_DB|OPEN_MAIN_JOURNAL|OPEN_WAL) == 0 {
return nil
}
n.stack[0] = uint64(n.zPath)
fn := n.mod.ExportedFunction("sqlite3_database_file_object")
if err := fn.CallWithStack(n.ctx, n.stack[:]); err != nil {
panic(err)
}
file, _ := vfsFileGet(n.ctx, n.mod, uint32(n.stack[0])).(File)
return file
}
// URIParameter returns the value of a URI parameter.
//
// https://sqlite.org/c3ref/uri_boolean.html
func (n *Filename) URIParameter(key string) string {
if n == nil || n.zPath == 0 {
return ""
}
uriKey := n.mod.ExportedFunction("sqlite3_uri_key")
n.stack[0] = uint64(n.zPath)
n.stack[1] = uint64(0)
if err := uriKey.CallWithStack(n.ctx, n.stack[:]); err != nil {
panic(err)
}
ptr := uint32(n.stack[0])
if ptr == 0 {
return ""
}
// Parse the format from:
// https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840
// This avoids having to alloc/free the key just to find a value.
for {
k := util.ReadString(n.mod, ptr, _MAX_NAME)
if k == "" {
return ""
}
ptr += uint32(len(k)) + 1
v := util.ReadString(n.mod, ptr, _MAX_NAME)
if k == key {
return v
}
ptr += uint32(len(v)) + 1
}
}
// URIParameters obtains values for URI parameters.
//
// https://sqlite.org/c3ref/uri_boolean.html
func (n *Filename) URIParameters() url.Values {
if n == nil || n.zPath == 0 {
return nil
}
uriKey := n.mod.ExportedFunction("sqlite3_uri_key")
n.stack[0] = uint64(n.zPath)
n.stack[1] = uint64(0)
if err := uriKey.CallWithStack(n.ctx, n.stack[:]); err != nil {
panic(err)
}
ptr := uint32(n.stack[0])
if ptr == 0 {
return nil
}
var params url.Values
// Parse the format from:
// https://github.com/sqlite/sqlite/blob/b74eb0/src/pager.c#L4797-L4840
// This is the only way to support multiple valued keys.
for {
k := util.ReadString(n.mod, ptr, _MAX_NAME)
if k == "" {
return params
}
ptr += uint32(len(k)) + 1
v := util.ReadString(n.mod, ptr, _MAX_NAME)
if params == nil {
params = url.Values{}
}
params.Add(k, v)
ptr += uint32(len(v)) + 1
}
}

144
vendor/github.com/ncruces/go-sqlite3/vfs/lock.go generated vendored Normal file
View File

@ -0,0 +1,144 @@
//go:build (linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys
package vfs
import "github.com/ncruces/go-sqlite3/internal/util"
// SupportsFileLocking is false on platforms that do not support file locking.
// To open a database file on those platforms,
// you need to use the [nolock] or [immutable] URI parameters.
//
// [nolock]: https://sqlite.org/uri.html#urinolock
// [immutable]: https://sqlite.org/uri.html#uriimmutable
const SupportsFileLocking = true
const (
_PENDING_BYTE = 0x40000000
_RESERVED_BYTE = (_PENDING_BYTE + 1)
_SHARED_FIRST = (_PENDING_BYTE + 2)
_SHARED_SIZE = 510
)
func (f *vfsFile) Lock(lock LockLevel) error {
// Argument check. SQLite never explicitly requests a pending lock.
if lock != LOCK_SHARED && lock != LOCK_RESERVED && lock != LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
switch {
case f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE:
// Connection state check.
panic(util.AssertErr())
case f.lock == LOCK_NONE && lock > LOCK_SHARED:
// We never move from unlocked to anything higher than a shared lock.
panic(util.AssertErr())
case f.lock != LOCK_SHARED && lock == LOCK_RESERVED:
// A shared lock is always held when a reserved lock is requested.
panic(util.AssertErr())
}
// If we already have an equal or more restrictive lock, do nothing.
if f.lock >= lock {
return nil
}
// Do not allow any kind of write-lock on a read-only database.
if f.readOnly && lock >= LOCK_RESERVED {
return _IOERR_LOCK
}
switch lock {
case LOCK_SHARED:
// Must be unlocked to get SHARED.
if f.lock != LOCK_NONE {
panic(util.AssertErr())
}
if rc := osGetSharedLock(f.File); rc != _OK {
return rc
}
f.lock = LOCK_SHARED
return nil
case LOCK_RESERVED:
// Must be SHARED to get RESERVED.
if f.lock != LOCK_SHARED {
panic(util.AssertErr())
}
if rc := osGetReservedLock(f.File); rc != _OK {
return rc
}
f.lock = LOCK_RESERVED
return nil
case LOCK_EXCLUSIVE:
// Must be SHARED, RESERVED or PENDING to get EXCLUSIVE.
if f.lock <= LOCK_NONE || f.lock >= LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
reserved := f.lock == LOCK_RESERVED
// A PENDING lock is needed before acquiring an EXCLUSIVE lock.
if f.lock < LOCK_PENDING {
// If we're already RESERVED, we can block indefinitely,
// since only new readers may briefly hold the PENDING lock.
if rc := osGetPendingLock(f.File, reserved /* block */); rc != _OK {
return rc
}
f.lock = LOCK_PENDING
}
// We already have PENDING, so we're just waiting for readers to leave.
// If we were RESERVED, we can wait for a little while, before invoking
// the busy handler; we will only do this once.
if rc := osGetExclusiveLock(f.File, reserved /* wait */); rc != _OK {
return rc
}
f.lock = LOCK_EXCLUSIVE
return nil
default:
panic(util.AssertErr())
}
}
func (f *vfsFile) Unlock(lock LockLevel) error {
// Argument check.
if lock != LOCK_NONE && lock != LOCK_SHARED {
panic(util.AssertErr())
}
// Connection state check.
if f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
// If we don't have a more restrictive lock, do nothing.
if f.lock <= lock {
return nil
}
switch lock {
case LOCK_SHARED:
rc := osDowngradeLock(f.File, f.lock)
f.lock = LOCK_SHARED
return rc
case LOCK_NONE:
rc := osReleaseLock(f.File, f.lock)
f.lock = LOCK_NONE
return rc
default:
panic(util.AssertErr())
}
}
func (f *vfsFile) CheckReservedLock() (bool, error) {
// Connection state check.
if f.lock < LOCK_NONE || f.lock > LOCK_EXCLUSIVE {
panic(util.AssertErr())
}
if f.lock >= LOCK_RESERVED {
return true, nil
}
return osCheckReservedLock(f.File)
}

23
vendor/github.com/ncruces/go-sqlite3/vfs/lock_other.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
//go:build !(linux || darwin || windows || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) || sqlite3_nosys
package vfs
// SupportsFileLocking is false on platforms that do not support file locking.
// To open a database file on those platforms,
// you need to use the [nolock] or [immutable] URI parameters.
//
// [nolock]: https://sqlite.org/uri.html#urinolock
// [immutable]: https://sqlite.org/uri.html#uriimmutable
const SupportsFileLocking = false
func (f *vfsFile) Lock(LockLevel) error {
return _IOERR_LOCK
}
func (f *vfsFile) Unlock(LockLevel) error {
return _IOERR_UNLOCK
}
func (f *vfsFile) CheckReservedLock() (bool, error) {
return false, _IOERR_CHECKRESERVEDLOCK
}

View File

@ -0,0 +1,9 @@
# Go `"memdb"` SQLite VFS
This package implements the [`"memdb"`](https://sqlite.org/src/doc/tip/src/memdb.c)
SQLite VFS in pure Go.
It has some benefits over the C version:
- the memory backing the database needs not be contiguous,
- the database can grow/shrink incrementally without copying,
- reader-writer concurrency is slightly improved.

68
vendor/github.com/ncruces/go-sqlite3/vfs/memdb/api.go generated vendored Normal file
View File

@ -0,0 +1,68 @@
// Package memdb implements the "memdb" SQLite VFS.
//
// The "memdb" [vfs.VFS] allows the same in-memory database to be shared
// among multiple database connections in the same process,
// as long as the database name begins with "/".
//
// Importing package memdb registers the VFS:
//
// import _ "github.com/ncruces/go-sqlite3/vfs/memdb"
package memdb
import (
"sync"
"github.com/ncruces/go-sqlite3/vfs"
)
func init() {
vfs.Register("memdb", memVFS{})
}
var (
memoryMtx sync.Mutex
// +checklocks:memoryMtx
memoryDBs = map[string]*memDB{}
)
// Create creates a shared memory database,
// using data as its initial contents.
// The new database takes ownership of data,
// and the caller should not use data after this call.
func Create(name string, data []byte) {
memoryMtx.Lock()
defer memoryMtx.Unlock()
db := &memDB{
refs: 1,
name: name,
size: int64(len(data)),
}
// Convert data from WAL to rollback journal.
if len(data) >= 20 && data[18] == 2 && data[19] == 2 {
data[18] = 1
data[19] = 1
}
sectors := divRoundUp(db.size, sectorSize)
db.data = make([]*[sectorSize]byte, sectors)
for i := range db.data {
sector := data[i*sectorSize:]
if len(sector) >= sectorSize {
db.data[i] = (*[sectorSize]byte)(sector)
} else {
db.data[i] = new([sectorSize]byte)
copy((*db.data[i])[:], sector)
}
}
memoryDBs[name] = db
}
// Delete deletes a shared memory database.
func Delete(name string) {
memoryMtx.Lock()
defer memoryMtx.Unlock()
delete(memoryDBs, name)
}

311
vendor/github.com/ncruces/go-sqlite3/vfs/memdb/memdb.go generated vendored Normal file
View File

@ -0,0 +1,311 @@
package memdb
import (
"io"
"runtime"
"sync"
"time"
"github.com/ncruces/go-sqlite3"
"github.com/ncruces/go-sqlite3/vfs"
)
// Must be a multiple of 64K (the largest page size).
const sectorSize = 65536
type memVFS struct{}
func (memVFS) Open(name string, flags vfs.OpenFlag) (vfs.File, vfs.OpenFlag, error) {
// For simplicity, we do not support reading or writing data
// across "sector" boundaries.
//
// This is not a problem for most SQLite file types:
// - databases, which only do page aligned reads/writes;
// - temp journals, as used by the sorter, which does the same:
// https://github.com/sqlite/sqlite/blob/b74eb0/src/vdbesort.c#L409-L412
//
// We refuse to open all other file types,
// but returning OPEN_MEMORY means SQLite won't ask us to.
const types = vfs.OPEN_MAIN_DB |
vfs.OPEN_TEMP_DB |
vfs.OPEN_TEMP_JOURNAL
if flags&types == 0 {
return nil, flags, sqlite3.CANTOPEN
}
// A shared database has a name that begins with "/".
shared := len(name) > 1 && name[0] == '/'
var db *memDB
if shared {
name = name[1:]
memoryMtx.Lock()
defer memoryMtx.Unlock()
db = memoryDBs[name]
}
if db == nil {
if flags&vfs.OPEN_CREATE == 0 {
return nil, flags, sqlite3.CANTOPEN
}
db = &memDB{name: name}
}
if shared {
db.refs++ // +checklocksforce: memoryMtx is held
memoryDBs[name] = db
}
return &memFile{
memDB: db,
readOnly: flags&vfs.OPEN_READONLY != 0,
}, flags | vfs.OPEN_MEMORY, nil
}
func (memVFS) Delete(name string, dirSync bool) error {
return sqlite3.IOERR_DELETE
}
func (memVFS) Access(name string, flag vfs.AccessFlag) (bool, error) {
return false, nil
}
func (memVFS) FullPathname(name string) (string, error) {
return name, nil
}
type memDB struct {
name string
// +checklocks:lockMtx
pending *memFile
// +checklocks:lockMtx
reserved *memFile
// +checklocks:dataMtx
data []*[sectorSize]byte
// +checklocks:dataMtx
size int64
// +checklocks:lockMtx
shared int
// +checklocks:memoryMtx
refs int
lockMtx sync.Mutex
dataMtx sync.RWMutex
}
func (m *memDB) release() {
memoryMtx.Lock()
defer memoryMtx.Unlock()
if m.refs--; m.refs == 0 && m == memoryDBs[m.name] {
delete(memoryDBs, m.name)
}
}
type memFile struct {
*memDB
lock vfs.LockLevel
readOnly bool
}
var (
// Ensure these interfaces are implemented:
_ vfs.FileLockState = &memFile{}
_ vfs.FileSizeHint = &memFile{}
)
func (m *memFile) Close() error {
m.release()
return m.Unlock(vfs.LOCK_NONE)
}
func (m *memFile) ReadAt(b []byte, off int64) (n int, err error) {
m.dataMtx.RLock()
defer m.dataMtx.RUnlock()
if off >= m.size {
return 0, io.EOF
}
base := off / sectorSize
rest := off % sectorSize
have := int64(sectorSize)
if base == int64(len(m.data))-1 {
have = modRoundUp(m.size, sectorSize)
}
n = copy(b, (*m.data[base])[rest:have])
if n < len(b) {
// Assume reads are page aligned.
return 0, io.ErrNoProgress
}
return n, nil
}
func (m *memFile) WriteAt(b []byte, off int64) (n int, err error) {
m.dataMtx.Lock()
defer m.dataMtx.Unlock()
base := off / sectorSize
rest := off % sectorSize
for base >= int64(len(m.data)) {
m.data = append(m.data, new([sectorSize]byte))
}
n = copy((*m.data[base])[rest:], b)
if n < len(b) {
// Assume writes are page aligned.
return n, io.ErrShortWrite
}
if size := off + int64(len(b)); size > m.size {
m.size = size
}
return n, nil
}
func (m *memFile) Truncate(size int64) error {
m.dataMtx.Lock()
defer m.dataMtx.Unlock()
return m.truncate(size)
}
// +checklocks:m.dataMtx
func (m *memFile) truncate(size int64) error {
if size < m.size {
base := size / sectorSize
rest := size % sectorSize
if rest != 0 {
clear((*m.data[base])[rest:])
}
}
sectors := divRoundUp(size, sectorSize)
for sectors > int64(len(m.data)) {
m.data = append(m.data, new([sectorSize]byte))
}
clear(m.data[sectors:])
m.data = m.data[:sectors]
m.size = size
return nil
}
func (m *memFile) Sync(flag vfs.SyncFlag) error {
return nil
}
func (m *memFile) Size() (int64, error) {
m.dataMtx.RLock()
defer m.dataMtx.RUnlock()
return m.size, nil
}
const spinWait = 25 * time.Microsecond
func (m *memFile) Lock(lock vfs.LockLevel) error {
if m.lock >= lock {
return nil
}
if m.readOnly && lock >= vfs.LOCK_RESERVED {
return sqlite3.IOERR_LOCK
}
m.lockMtx.Lock()
defer m.lockMtx.Unlock()
switch lock {
case vfs.LOCK_SHARED:
if m.pending != nil {
return sqlite3.BUSY
}
m.shared++
case vfs.LOCK_RESERVED:
if m.reserved != nil {
return sqlite3.BUSY
}
m.reserved = m
case vfs.LOCK_EXCLUSIVE:
if m.lock < vfs.LOCK_PENDING {
if m.pending != nil {
return sqlite3.BUSY
}
m.lock = vfs.LOCK_PENDING
m.pending = m
}
for before := time.Now(); m.shared > 1; {
if time.Since(before) > spinWait {
return sqlite3.BUSY
}
m.lockMtx.Unlock()
runtime.Gosched()
m.lockMtx.Lock()
}
}
m.lock = lock
return nil
}
func (m *memFile) Unlock(lock vfs.LockLevel) error {
if m.lock <= lock {
return nil
}
m.lockMtx.Lock()
defer m.lockMtx.Unlock()
if m.pending == m {
m.pending = nil
}
if m.reserved == m {
m.reserved = nil
}
if lock < vfs.LOCK_SHARED {
m.shared--
}
m.lock = lock
return nil
}
func (m *memFile) CheckReservedLock() (bool, error) {
if m.lock >= vfs.LOCK_RESERVED {
return true, nil
}
m.lockMtx.Lock()
defer m.lockMtx.Unlock()
return m.reserved != nil, nil
}
func (m *memFile) SectorSize() int {
return sectorSize
}
func (m *memFile) DeviceCharacteristics() vfs.DeviceCharacteristic {
return vfs.IOCAP_ATOMIC |
vfs.IOCAP_SEQUENTIAL |
vfs.IOCAP_SAFE_APPEND |
vfs.IOCAP_POWERSAFE_OVERWRITE
}
func (m *memFile) SizeHint(size int64) error {
m.dataMtx.Lock()
defer m.dataMtx.Unlock()
if size > m.size {
return m.truncate(size)
}
return nil
}
func (m *memFile) LockState() vfs.LockLevel {
return m.lock
}
func divRoundUp(a, b int64) int64 {
return (a + b - 1) / b
}
func modRoundUp(a, b int64) int64 {
return b - (b-a%b)%b
}

33
vendor/github.com/ncruces/go-sqlite3/vfs/os_bsd.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
//go:build (freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys
package vfs
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func osUnlock(file *os.File, start, len int64) _ErrorCode {
if start == 0 && len == 0 {
err := unix.Flock(int(file.Fd()), unix.LOCK_UN)
if err != nil {
return _IOERR_UNLOCK
}
}
return _OK
}
func osLock(file *os.File, how int, def _ErrorCode) _ErrorCode {
err := unix.Flock(int(file.Fd()), how)
return osLockErrorCode(err, def)
}
func osReadLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode {
return osLock(file, unix.LOCK_SH|unix.LOCK_NB, _IOERR_RDLOCK)
}
func osWriteLock(file *os.File, _ /*start*/, _ /*len*/ int64, _ /*timeout*/ time.Duration) _ErrorCode {
return osLock(file, unix.LOCK_EX|unix.LOCK_NB, _IOERR_LOCK)
}

95
vendor/github.com/ncruces/go-sqlite3/vfs/os_darwin.go generated vendored Normal file
View File

@ -0,0 +1,95 @@
//go:build !(sqlite3_flock || sqlite3_nosys)
package vfs
import (
"io"
"os"
"time"
"golang.org/x/sys/unix"
)
const (
// https://github.com/apple/darwin-xnu/blob/main/bsd/sys/fcntl.h
_F_OFD_SETLK = 90
_F_OFD_SETLKW = 91
_F_OFD_SETLKWTIMEOUT = 93
)
type flocktimeout_t struct {
fl unix.Flock_t
timeout unix.Timespec
}
func osSync(file *os.File, fullsync, _ /*dataonly*/ bool) error {
if fullsync {
return file.Sync()
}
return unix.Fsync(int(file.Fd()))
}
func osAllocate(file *os.File, size int64) error {
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if size <= off {
return nil
}
store := unix.Fstore_t{
Flags: unix.F_ALLOCATEALL | unix.F_ALLOCATECONTIG,
Posmode: unix.F_PEOFPOSMODE,
Offset: 0,
Length: size - off,
}
// 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.
store.Flags = unix.F_ALLOCATEALL
unix.FcntlFstore(file.Fd(), unix.F_PREALLOCATE, &store)
}
return file.Truncate(size)
}
func osUnlock(file *os.File, start, len int64) _ErrorCode {
err := unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK,
Start: start,
Len: len,
})
if err != nil {
return _IOERR_UNLOCK
}
return _OK
}
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
lock := flocktimeout_t{fl: unix.Flock_t{
Type: typ,
Start: start,
Len: len,
}}
var err error
switch {
case timeout == 0:
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLK, &lock.fl)
case timeout < 0:
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKW, &lock.fl)
default:
lock.timeout = unix.NsecToTimespec(int64(timeout / time.Nanosecond))
err = unix.FcntlFlock(file.Fd(), _F_OFD_SETLKWTIMEOUT, &lock.fl)
}
return osLockErrorCode(err, def)
}
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
}
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
}

View File

@ -0,0 +1,34 @@
//go:build (amd64 || arm64 || riscv64) && !sqlite3_nosys
package vfs
import (
"os"
"golang.org/x/sys/unix"
)
const (
_F2FS_IOC_START_ATOMIC_WRITE = 62721
_F2FS_IOC_COMMIT_ATOMIC_WRITE = 62722
_F2FS_IOC_ABORT_ATOMIC_WRITE = 62725
_F2FS_IOC_GET_FEATURES = 2147808524
_F2FS_FEATURE_ATOMIC_WRITE = 4
)
func osBatchAtomic(file *os.File) bool {
flags, err := unix.IoctlGetInt(int(file.Fd()), _F2FS_IOC_GET_FEATURES)
return err == nil && flags&_F2FS_FEATURE_ATOMIC_WRITE != 0
}
func (f *vfsFile) BeginAtomicWrite() error {
return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_START_ATOMIC_WRITE, 0)
}
func (f *vfsFile) CommitAtomicWrite() error {
return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_COMMIT_ATOMIC_WRITE, 0)
}
func (f *vfsFile) RollbackAtomicWrite() error {
return unix.IoctlSetInt(int(f.Fd()), _F2FS_IOC_ABORT_ATOMIC_WRITE, 0)
}

71
vendor/github.com/ncruces/go-sqlite3/vfs/os_linux.go generated vendored Normal file
View File

@ -0,0 +1,71 @@
//go:build !(sqlite3_flock || sqlite3_nosys)
package vfs
import (
"math/rand"
"os"
"time"
"golang.org/x/sys/unix"
)
func osSync(file *os.File, _ /*fullsync*/, _ /*dataonly*/ bool) error {
// SQLite trusts Linux's fdatasync for all fsync's.
return unix.Fdatasync(int(file.Fd()))
}
func osAllocate(file *os.File, size int64) error {
if size == 0 {
return nil
}
return unix.Fallocate(int(file.Fd()), 0, 0, size)
}
func osUnlock(file *os.File, start, len int64) _ErrorCode {
err := unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &unix.Flock_t{
Type: unix.F_UNLCK,
Start: start,
Len: len,
})
if err != nil {
return _IOERR_UNLOCK
}
return _OK
}
func osLock(file *os.File, typ int16, start, len int64, timeout time.Duration, def _ErrorCode) _ErrorCode {
lock := unix.Flock_t{
Type: typ,
Start: start,
Len: len,
}
var err error
switch {
case timeout == 0:
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
case timeout < 0:
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLKW, &lock)
default:
before := time.Now()
for {
err = unix.FcntlFlock(file.Fd(), unix.F_OFD_SETLK, &lock)
if errno, _ := err.(unix.Errno); errno != unix.EAGAIN {
break
}
if timeout < time.Since(before) {
break
}
osSleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
}
}
return osLockErrorCode(err, def)
}
func osReadLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.F_RDLCK, start, len, timeout, _IOERR_RDLOCK)
}
func osWriteLock(file *os.File, start, len int64, timeout time.Duration) _ErrorCode {
return osLock(file, unix.F_WRLCK, start, len, timeout, _IOERR_LOCK)
}

View File

@ -0,0 +1,36 @@
//go:build !unix || sqlite3_nosys
package vfs
import (
"io/fs"
"os"
)
func osAccess(path string, flags AccessFlag) error {
fi, err := os.Stat(path)
if err != nil {
return err
}
if flags == ACCESS_EXISTS {
return nil
}
const (
S_IREAD = 0400
S_IWRITE = 0200
S_IEXEC = 0100
)
var want fs.FileMode = S_IREAD
if flags == ACCESS_READWRITE {
want |= S_IWRITE
}
if fi.IsDir() {
want |= S_IEXEC
}
if fi.Mode()&want != want {
return fs.ErrPermission
}
return nil
}

View File

@ -0,0 +1,19 @@
//go:build !(linux || darwin) || sqlite3_flock || sqlite3_nosys
package vfs
import (
"io"
"os"
)
func osAllocate(file *os.File, size int64) error {
off, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
if size <= off {
return nil
}
return file.Truncate(size)
}

View File

@ -0,0 +1,9 @@
//go:build !linux || !(amd64 || arm64 || riscv64) || sqlite3_nosys
package vfs
import "os"
func osBatchAtomic(*os.File) bool {
return false
}

View File

@ -0,0 +1,14 @@
//go:build !unix || sqlite3_nosys
package vfs
import "os"
func osSetMode(file *os.File, modeof string) error {
fi, err := os.Stat(modeof)
if err != nil {
return err
}
file.Chmod(fi.Mode())
return nil
}

View File

@ -0,0 +1,9 @@
//go:build !windows || sqlite3_nosys
package vfs
import "time"
func osSleep(d time.Duration) {
time.Sleep(d)
}

View File

@ -0,0 +1,9 @@
//go:build !(linux || darwin) || sqlite3_flock || sqlite3_nosys
package vfs
import "os"
func osSync(file *os.File, _ /*fullsync*/, _ /*dataonly*/ bool) error {
return file.Sync()
}

33
vendor/github.com/ncruces/go-sqlite3/vfs/os_unix.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
//go:build unix && !sqlite3_nosys
package vfs
import (
"os"
"syscall"
"golang.org/x/sys/unix"
)
func osAccess(path string, flags AccessFlag) error {
var access uint32 // unix.F_OK
switch flags {
case ACCESS_READWRITE:
access = unix.R_OK | unix.W_OK
case ACCESS_READ:
access = unix.R_OK
}
return unix.Access(path, access)
}
func osSetMode(file *os.File, modeof string) error {
fi, err := os.Stat(modeof)
if err != nil {
return err
}
file.Chmod(fi.Mode())
if sys, ok := fi.Sys().(*syscall.Stat_t); ok {
file.Chown(int(sys.Uid), int(sys.Gid))
}
return nil
}

View File

@ -0,0 +1,106 @@
//go:build (linux || darwin || freebsd || openbsd || netbsd || dragonfly || illumos || sqlite3_flock) && !sqlite3_nosys
package vfs
import (
"os"
"time"
"golang.org/x/sys/unix"
)
func osGetSharedLock(file *os.File) _ErrorCode {
// Test the PENDING lock before acquiring a new SHARED lock.
if lock, _ := osGetLock(file, _PENDING_BYTE, 1); lock == unix.F_WRLCK {
return _BUSY
}
// Acquire the SHARED lock.
return osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
}
func osGetReservedLock(file *os.File) _ErrorCode {
// Acquire the RESERVED lock.
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
}
func osGetPendingLock(file *os.File, block bool) _ErrorCode {
var timeout time.Duration
if block {
timeout = -1
}
// Acquire the PENDING lock.
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
}
func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
var timeout time.Duration
if wait {
timeout = time.Millisecond
}
// Acquire the EXCLUSIVE lock.
return osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
}
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
if state >= LOCK_EXCLUSIVE {
// Downgrade to a SHARED lock.
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
// In theory, the downgrade to a SHARED cannot fail because another
// process is holding an incompatible lock. If it does, this
// indicates that the other process is not following the locking
// protocol. If this happens, return _IOERR_RDLOCK. Returning
// BUSY would confuse the upper layer.
return _IOERR_RDLOCK
}
}
// Release the PENDING and RESERVED locks.
return osUnlock(file, _PENDING_BYTE, 2)
}
func osReleaseLock(file *os.File, _ LockLevel) _ErrorCode {
// Release all locks.
return osUnlock(file, 0, 0)
}
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock.
lock, rc := osGetLock(file, _RESERVED_BYTE, 1)
return lock == unix.F_WRLCK, rc
}
func osGetLock(file *os.File, start, len int64) (int16, _ErrorCode) {
lock := unix.Flock_t{
Type: unix.F_WRLCK,
Start: start,
Len: len,
}
if unix.FcntlFlock(file.Fd(), unix.F_GETLK, &lock) != nil {
return 0, _IOERR_CHECKRESERVEDLOCK
}
return lock.Type, _OK
}
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
if err == nil {
return _OK
}
if errno, ok := err.(unix.Errno); ok {
switch errno {
case
unix.EACCES,
unix.EAGAIN,
unix.EBUSY,
unix.EINTR,
unix.ENOLCK,
unix.EDEADLK,
unix.ETIMEDOUT:
return _BUSY
case unix.EPERM:
return _PERM
}
if errno == unix.EWOULDBLOCK && unix.EWOULDBLOCK != unix.EAGAIN {
return _BUSY
}
}
return def
}

186
vendor/github.com/ncruces/go-sqlite3/vfs/os_windows.go generated vendored Normal file
View File

@ -0,0 +1,186 @@
//go:build !sqlite3_nosys
package vfs
import (
"math/rand"
"os"
"time"
"golang.org/x/sys/windows"
)
func osGetSharedLock(file *os.File) _ErrorCode {
// Acquire the PENDING lock temporarily before acquiring a new SHARED lock.
rc := osReadLock(file, _PENDING_BYTE, 1, 0)
if rc == _OK {
// Acquire the SHARED lock.
rc = osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
// Release the PENDING lock.
osUnlock(file, _PENDING_BYTE, 1)
}
return rc
}
func osGetReservedLock(file *os.File) _ErrorCode {
// Acquire the RESERVED lock.
return osWriteLock(file, _RESERVED_BYTE, 1, 0)
}
func osGetPendingLock(file *os.File, block bool) _ErrorCode {
var timeout time.Duration
if block {
timeout = -1
}
// Acquire the PENDING lock.
return osWriteLock(file, _PENDING_BYTE, 1, timeout)
}
func osGetExclusiveLock(file *os.File, wait bool) _ErrorCode {
var timeout time.Duration
if wait {
timeout = time.Millisecond
}
// Release the SHARED lock.
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
// Acquire the EXCLUSIVE lock.
rc := osWriteLock(file, _SHARED_FIRST, _SHARED_SIZE, timeout)
if rc != _OK {
// Reacquire the SHARED lock.
osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0)
}
return rc
}
func osDowngradeLock(file *os.File, state LockLevel) _ErrorCode {
if state >= LOCK_EXCLUSIVE {
// Release the EXCLUSIVE lock.
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
// Reacquire the SHARED lock.
if rc := osReadLock(file, _SHARED_FIRST, _SHARED_SIZE, 0); rc != _OK {
// This should never happen.
// We should always be able to reacquire the read lock.
return _IOERR_RDLOCK
}
}
// Release the PENDING and RESERVED locks.
if state >= LOCK_RESERVED {
osUnlock(file, _RESERVED_BYTE, 1)
}
if state >= LOCK_PENDING {
osUnlock(file, _PENDING_BYTE, 1)
}
return _OK
}
func osReleaseLock(file *os.File, state LockLevel) _ErrorCode {
// Release all locks.
if state >= LOCK_RESERVED {
osUnlock(file, _RESERVED_BYTE, 1)
}
if state >= LOCK_SHARED {
osUnlock(file, _SHARED_FIRST, _SHARED_SIZE)
}
if state >= LOCK_PENDING {
osUnlock(file, _PENDING_BYTE, 1)
}
return _OK
}
func osCheckReservedLock(file *os.File) (bool, _ErrorCode) {
// Test the RESERVED lock.
rc := osLock(file, 0, _RESERVED_BYTE, 1, 0, _IOERR_CHECKRESERVEDLOCK)
if rc == _BUSY {
return true, _OK
}
if rc == _OK {
// Release the RESERVED lock.
osUnlock(file, _RESERVED_BYTE, 1)
}
return false, rc
}
func osUnlock(file *os.File, start, len uint32) _ErrorCode {
err := windows.UnlockFileEx(windows.Handle(file.Fd()),
0, len, 0, &windows.Overlapped{Offset: start})
if err == windows.ERROR_NOT_LOCKED {
return _OK
}
if err != nil {
return _IOERR_UNLOCK
}
return _OK
}
func osLock(file *os.File, flags, start, len uint32, timeout time.Duration, def _ErrorCode) _ErrorCode {
var err error
switch {
case timeout == 0:
err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len)
case timeout < 0:
err = osLockEx(file, flags, start, len)
default:
before := time.Now()
for {
err = osLockEx(file, flags|windows.LOCKFILE_FAIL_IMMEDIATELY, start, len)
if errno, _ := err.(windows.Errno); errno != windows.ERROR_LOCK_VIOLATION {
break
}
if timeout < time.Since(before) {
break
}
osSleep(time.Duration(rand.Int63n(int64(time.Millisecond))))
}
}
return osLockErrorCode(err, def)
}
func osLockEx(file *os.File, flags, start, len uint32) error {
return windows.LockFileEx(windows.Handle(file.Fd()), flags,
0, len, 0, &windows.Overlapped{Offset: start})
}
func osReadLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
return osLock(file, 0, start, len, timeout, _IOERR_RDLOCK)
}
func osWriteLock(file *os.File, start, len uint32, timeout time.Duration) _ErrorCode {
return osLock(file, windows.LOCKFILE_EXCLUSIVE_LOCK, start, len, timeout, _IOERR_LOCK)
}
func osLockErrorCode(err error, def _ErrorCode) _ErrorCode {
if err == nil {
return _OK
}
if errno, ok := err.(windows.Errno); ok {
// https://devblogs.microsoft.com/oldnewthing/20140905-00/?p=63
switch errno {
case
windows.ERROR_LOCK_VIOLATION,
windows.ERROR_IO_PENDING,
windows.ERROR_OPERATION_ABORTED:
return _BUSY
}
}
return def
}
func osSleep(d time.Duration) {
if d > 0 {
period := max(1, d/(5*time.Millisecond))
if period < 16 {
windows.TimeBeginPeriod(uint32(period))
}
time.Sleep(d)
if period < 16 {
windows.TimeEndPeriod(uint32(period))
}
}
}

48
vendor/github.com/ncruces/go-sqlite3/vfs/registry.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
package vfs
import "sync"
var (
// +checklocks:vfsRegistryMtx
vfsRegistry map[string]VFS
vfsRegistryMtx sync.RWMutex
)
// Find returns a VFS given its name.
// If there is no match, nil is returned.
// If name is empty, the default VFS is returned.
//
// https://sqlite.org/c3ref/vfs_find.html
func Find(name string) VFS {
if name == "" || name == "os" {
return vfsOS{}
}
vfsRegistryMtx.RLock()
defer vfsRegistryMtx.RUnlock()
return vfsRegistry[name]
}
// Register registers a VFS.
// Empty and "os" are reserved names.
//
// https://sqlite.org/c3ref/vfs_find.html
func Register(name string, vfs VFS) {
if name == "" || name == "os" {
return
}
vfsRegistryMtx.Lock()
defer vfsRegistryMtx.Unlock()
if vfsRegistry == nil {
vfsRegistry = map[string]VFS{}
}
vfsRegistry[name] = vfs
}
// Unregister unregisters a VFS.
//
// https://sqlite.org/c3ref/vfs_find.html
func Unregister(name string) {
vfsRegistryMtx.Lock()
defer vfsRegistryMtx.Unlock()
delete(vfsRegistry, name)
}

173
vendor/github.com/ncruces/go-sqlite3/vfs/shm.go generated vendored Normal file
View File

@ -0,0 +1,173 @@
//go:build (darwin || linux) && (amd64 || arm64 || riscv64) && !(sqlite3_flock || sqlite3_noshm || sqlite3_nosys)
package vfs
import (
"context"
"io"
"os"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/tetratelabs/wazero/api"
"golang.org/x/sys/unix"
)
// SupportsSharedMemory is false on platforms that do not support shared memory.
// To use [WAL without shared-memory], you need to set [EXCLUSIVE locking mode].
//
// [WAL without shared-memory]: https://sqlite.org/wal.html#noshm
// [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode
const SupportsSharedMemory = true
const (
_SHM_NLOCK = 8
_SHM_BASE = 120
_SHM_DMS = _SHM_BASE + _SHM_NLOCK
)
func (f *vfsFile) SharedMemory() SharedMemory { return f.shm }
// NewSharedMemory returns a shared-memory WAL-index
// backed by a file with the given path.
// It will return nil if shared-memory is not supported,
// or not appropriate for the given flags.
// Only [OPEN_MAIN_DB] databases may need a WAL-index.
// You must ensure all concurrent accesses to a database
// use shared-memory instances created with the same path.
func NewSharedMemory(path string, flags OpenFlag) SharedMemory {
if flags&OPEN_MAIN_DB == 0 || flags&(OPEN_DELETEONCLOSE|OPEN_MEMORY) != 0 {
return nil
}
return &vfsShm{
path: path,
readOnly: flags&OPEN_READONLY != 0,
}
}
type vfsShm struct {
*os.File
path string
regions []*util.MappedRegion
readOnly bool
}
func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (uint32, error) {
// Ensure size is a multiple of the OS page size.
if int(size)&(unix.Getpagesize()-1) != 0 {
return 0, _IOERR_SHMMAP
}
if s.File == nil {
var flag int
if s.readOnly {
flag = unix.O_RDONLY
} else {
flag = unix.O_RDWR
}
f, err := os.OpenFile(s.path,
flag|unix.O_CREAT|unix.O_NOFOLLOW, 0666)
if err != nil {
return 0, _CANTOPEN
}
s.File = f
}
// Dead man's switch.
if lock, rc := osGetLock(s.File, _SHM_DMS, 1); rc != _OK {
return 0, _IOERR_LOCK
} else if lock == unix.F_WRLCK {
return 0, _BUSY
} else if lock == unix.F_UNLCK {
if s.readOnly {
return 0, _READONLY_CANTINIT
}
if rc := osWriteLock(s.File, _SHM_DMS, 1, 0); rc != _OK {
return 0, rc
}
if err := s.Truncate(0); err != nil {
return 0, _IOERR_SHMOPEN
}
}
if rc := osReadLock(s.File, _SHM_DMS, 1, 0); rc != _OK {
return 0, rc
}
// Check if file is big enough.
o, err := s.Seek(0, io.SeekEnd)
if err != nil {
return 0, _IOERR_SHMSIZE
}
if n := (int64(id) + 1) * int64(size); n > o {
if !extend {
return 0, nil
}
err := osAllocate(s.File, n)
if err != nil {
return 0, _IOERR_SHMSIZE
}
}
var prot int
if s.readOnly {
prot = unix.PROT_READ
} else {
prot = unix.PROT_READ | unix.PROT_WRITE
}
r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, prot)
if err != nil {
return 0, err
}
s.regions = append(s.regions, r)
return r.Ptr, nil
}
func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error {
// Argument check.
if n <= 0 || offset < 0 || offset+n > _SHM_NLOCK {
panic(util.AssertErr())
}
switch flags {
case
_SHM_LOCK | _SHM_SHARED,
_SHM_LOCK | _SHM_EXCLUSIVE,
_SHM_UNLOCK | _SHM_SHARED,
_SHM_UNLOCK | _SHM_EXCLUSIVE:
//
default:
panic(util.AssertErr())
}
if n != 1 && flags&_SHM_EXCLUSIVE == 0 {
panic(util.AssertErr())
}
switch {
case flags&_SHM_UNLOCK != 0:
return osUnlock(s.File, _SHM_BASE+int64(offset), int64(n))
case flags&_SHM_SHARED != 0:
return osReadLock(s.File, _SHM_BASE+int64(offset), int64(n), 0)
case flags&_SHM_EXCLUSIVE != 0:
return osWriteLock(s.File, _SHM_BASE+int64(offset), int64(n), 0)
default:
panic(util.AssertErr())
}
}
func (s *vfsShm) shmUnmap(delete bool) {
if s.File == nil {
return
}
// Unmap regions.
for _, r := range s.regions {
r.Unmap()
}
clear(s.regions)
s.regions = s.regions[:0]
// Close the file.
defer s.Close()
if delete {
os.Remove(s.Name())
}
s.File = nil
}

21
vendor/github.com/ncruces/go-sqlite3/vfs/shm_other.go generated vendored Normal file
View File

@ -0,0 +1,21 @@
//go:build !(darwin || linux) || !(amd64 || arm64 || riscv64) || sqlite3_flock || sqlite3_noshm || sqlite3_nosys
package vfs
// SupportsSharedMemory is false on platforms that do not support shared memory.
// To use [WAL without shared-memory], you need to set [EXCLUSIVE locking mode].
//
// [WAL without shared-memory]: https://sqlite.org/wal.html#noshm
// [EXCLUSIVE locking mode]: https://sqlite.org/pragma.html#pragma_locking_mode
const SupportsSharedMemory = false
// NewSharedMemory returns a shared-memory WAL-index
// backed by a file with the given path.
// It will return nil if shared-memory is not supported,
// or not appropriate for the given flags.
// Only [OPEN_MAIN_DB] databases may need a WAL-index.
// You must ensure all concurrent accesses to a database
// use shared-memory instances created with the same path.
func NewSharedMemory(path string, flags OpenFlag) SharedMemory {
return nil
}

459
vendor/github.com/ncruces/go-sqlite3/vfs/vfs.go generated vendored Normal file
View File

@ -0,0 +1,459 @@
package vfs
import (
"context"
"crypto/rand"
"io"
"reflect"
"sync"
"time"
"github.com/ncruces/go-sqlite3/internal/util"
"github.com/ncruces/julianday"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
// ExportHostFunctions is an internal API users need not call directly.
//
// ExportHostFunctions registers the required VFS host functions
// with the provided env module.
func ExportHostFunctions(env wazero.HostModuleBuilder) wazero.HostModuleBuilder {
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_64", vfsCurrentTime64)
util.ExportFuncIIIII(env, "go_full_pathname", vfsFullPathname)
util.ExportFuncIIII(env, "go_delete", vfsDelete)
util.ExportFuncIIIII(env, "go_access", vfsAccess)
util.ExportFuncIIIIIII(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)
util.ExportFuncIIIIII(env, "go_shm_map", vfsShmMap)
util.ExportFuncIIIII(env, "go_shm_lock", vfsShmLock)
util.ExportFuncIII(env, "go_shm_unmap", vfsShmUnmap)
util.ExportFuncVI(env, "go_shm_barrier", vfsShmBarrier)
return env
}
func vfsFind(ctx context.Context, mod api.Module, zVfsName uint32) uint32 {
name := util.ReadString(mod, zVfsName, _MAX_NAME)
if vfs := Find(name); vfs != nil && vfs != (vfsOS{}) {
return 1
}
return 0
}
func vfsLocaltime(ctx context.Context, mod api.Module, pTm uint32, t int64) _ErrorCode {
tm := time.Unix(t, 0)
var isdst int
if tm.IsDST() {
isdst = 1
}
const size = 32 / 8
// https://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html
util.WriteUint32(mod, pTm+0*size, uint32(tm.Second()))
util.WriteUint32(mod, pTm+1*size, uint32(tm.Minute()))
util.WriteUint32(mod, pTm+2*size, uint32(tm.Hour()))
util.WriteUint32(mod, pTm+3*size, uint32(tm.Day()))
util.WriteUint32(mod, pTm+4*size, uint32(tm.Month()-time.January))
util.WriteUint32(mod, pTm+5*size, uint32(tm.Year()-1900))
util.WriteUint32(mod, pTm+6*size, uint32(tm.Weekday()-time.Sunday))
util.WriteUint32(mod, pTm+7*size, uint32(tm.YearDay()-1))
util.WriteUint32(mod, pTm+8*size, uint32(isdst))
return _OK
}
func vfsRandomness(ctx context.Context, mod api.Module, pVfs uint32, nByte int32, zByte uint32) uint32 {
mem := util.View(mod, zByte, uint64(nByte))
n, _ := rand.Reader.Read(mem)
return uint32(n)
}
func vfsSleep(ctx context.Context, mod api.Module, pVfs uint32, nMicro int32) _ErrorCode {
osSleep(time.Duration(nMicro) * time.Microsecond)
return _OK
}
func vfsCurrentTime64(ctx context.Context, mod api.Module, pVfs, piNow uint32) _ErrorCode {
day, nsec := julianday.Date(time.Now())
msec := day*86_400_000 + nsec/1_000_000
util.WriteUint64(mod, piNow, uint64(msec))
return _OK
}
func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative uint32, nFull int32, zFull uint32) _ErrorCode {
vfs := vfsGet(mod, pVfs)
path := util.ReadString(mod, zRelative, _MAX_PATHNAME)
path, err := vfs.FullPathname(path)
if len(path) >= int(nFull) {
return _CANTOPEN_FULLPATH
}
util.WriteString(mod, zFull, path)
return vfsErrorCode(err, _CANTOPEN_FULLPATH)
}
func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath, syncDir uint32) _ErrorCode {
vfs := vfsGet(mod, pVfs)
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
err := vfs.Delete(path, syncDir != 0)
return vfsErrorCode(err, _IOERR_DELETE)
}
func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath uint32, flags AccessFlag, pResOut uint32) _ErrorCode {
vfs := vfsGet(mod, pVfs)
path := util.ReadString(mod, zPath, _MAX_PATHNAME)
ok, err := vfs.Access(path, flags)
var res uint32
if ok {
res = 1
}
util.WriteUint32(mod, pResOut, res)
return vfsErrorCode(err, _IOERR_ACCESS)
}
func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, flags OpenFlag, pOutFlags, pOutVFS uint32) _ErrorCode {
vfs := vfsGet(mod, pVfs)
var path string
if zPath != 0 {
path = util.ReadString(mod, zPath, _MAX_PATHNAME)
}
var file File
var err error
if ffs, ok := vfs.(VFSFilename); ok {
name := OpenFilename(ctx, mod, zPath, flags)
file, flags, err = ffs.OpenFilename(name, flags)
} else {
file, flags, err = vfs.Open(path, flags)
}
if err != nil {
return vfsErrorCode(err, _CANTOPEN)
}
if file, ok := file.(FilePowersafeOverwrite); ok {
name := OpenFilename(ctx, mod, zPath, flags)
if b, ok := util.ParseBool(name.URIParameter("psow")); ok {
file.SetPowersafeOverwrite(b)
}
}
if file, ok := file.(FileSharedMemory); ok &&
pOutVFS != 0 && file.SharedMemory() != nil {
util.WriteUint32(mod, pOutVFS, 1)
}
if pOutFlags != 0 {
util.WriteUint32(mod, pOutFlags, uint32(flags))
}
vfsFileRegister(ctx, mod, pFile, file)
return _OK
}
func vfsClose(ctx context.Context, mod api.Module, pFile uint32) _ErrorCode {
err := vfsFileClose(ctx, mod, pFile)
if err != nil {
return vfsErrorCode(err, _IOERR_CLOSE)
}
return _OK
}
func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile).(File)
buf := util.View(mod, zBuf, uint64(iAmt))
n, err := file.ReadAt(buf, iOfst)
if n == int(iAmt) {
return _OK
}
if err != io.EOF {
return vfsErrorCode(err, _IOERR_READ)
}
clear(buf[n:])
return _IOERR_SHORT_READ
}
func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf uint32, iAmt int32, iOfst int64) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile).(File)
buf := util.View(mod, zBuf, uint64(iAmt))
_, err := file.WriteAt(buf, iOfst)
if err != nil {
return vfsErrorCode(err, _IOERR_WRITE)
}
return _OK
}
func vfsTruncate(ctx context.Context, mod api.Module, pFile uint32, nByte int64) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile).(File)
err := file.Truncate(nByte)
return vfsErrorCode(err, _IOERR_TRUNCATE)
}
func vfsSync(ctx context.Context, mod api.Module, pFile uint32, flags SyncFlag) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile).(File)
err := file.Sync(flags)
return vfsErrorCode(err, _IOERR_FSYNC)
}
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile).(File)
size, err := file.Size()
util.WriteUint64(mod, pSize, uint64(size))
return vfsErrorCode(err, _IOERR_SEEK)
}
func vfsLock(ctx context.Context, mod api.Module, pFile uint32, eLock LockLevel) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile).(File)
err := file.Lock(eLock)
return vfsErrorCode(err, _IOERR_LOCK)
}
func vfsUnlock(ctx context.Context, mod api.Module, pFile uint32, eLock LockLevel) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile).(File)
err := file.Unlock(eLock)
return vfsErrorCode(err, _IOERR_UNLOCK)
}
func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut uint32) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile).(File)
locked, err := file.CheckReservedLock()
var res uint32
if locked {
res = 1
}
util.WriteUint32(mod, pResOut, res)
return vfsErrorCode(err, _IOERR_CHECKRESERVEDLOCK)
}
func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _FcntlOpcode, pArg uint32) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile).(File)
switch op {
case _FCNTL_LOCKSTATE:
if file, ok := file.(FileLockState); ok {
util.WriteUint32(mod, pArg, uint32(file.LockState()))
return _OK
}
case _FCNTL_PERSIST_WAL:
if file, ok := file.(FilePersistentWAL); ok {
if i := util.ReadUint32(mod, pArg); int32(i) >= 0 {
file.SetPersistentWAL(i != 0)
} else if file.PersistentWAL() {
util.WriteUint32(mod, pArg, 1)
} else {
util.WriteUint32(mod, pArg, 0)
}
return _OK
}
case _FCNTL_POWERSAFE_OVERWRITE:
if file, ok := file.(FilePowersafeOverwrite); ok {
if i := util.ReadUint32(mod, pArg); int32(i) >= 0 {
file.SetPowersafeOverwrite(i != 0)
} else if file.PowersafeOverwrite() {
util.WriteUint32(mod, pArg, 1)
} else {
util.WriteUint32(mod, pArg, 0)
}
return _OK
}
case _FCNTL_CHUNK_SIZE:
if file, ok := file.(FileChunkSize); ok {
size := util.ReadUint32(mod, pArg)
file.ChunkSize(int(size))
return _OK
}
case _FCNTL_SIZE_HINT:
if file, ok := file.(FileSizeHint); ok {
size := util.ReadUint64(mod, pArg)
err := file.SizeHint(int64(size))
return vfsErrorCode(err, _IOERR_TRUNCATE)
}
case _FCNTL_HAS_MOVED:
if file, ok := file.(FileHasMoved); ok {
moved, err := file.HasMoved()
var res uint32
if moved {
res = 1
}
util.WriteUint32(mod, pArg, res)
return vfsErrorCode(err, _IOERR_FSTAT)
}
case _FCNTL_OVERWRITE:
if file, ok := file.(FileOverwrite); ok {
err := file.Overwrite()
return vfsErrorCode(err, _IOERR)
}
case _FCNTL_COMMIT_PHASETWO:
if file, ok := file.(FileCommitPhaseTwo); ok {
err := file.CommitPhaseTwo()
return vfsErrorCode(err, _IOERR)
}
case _FCNTL_BEGIN_ATOMIC_WRITE:
if file, ok := file.(FileBatchAtomicWrite); ok {
err := file.BeginAtomicWrite()
return vfsErrorCode(err, _IOERR_BEGIN_ATOMIC)
}
case _FCNTL_COMMIT_ATOMIC_WRITE:
if file, ok := file.(FileBatchAtomicWrite); ok {
err := file.CommitAtomicWrite()
return vfsErrorCode(err, _IOERR_COMMIT_ATOMIC)
}
case _FCNTL_ROLLBACK_ATOMIC_WRITE:
if file, ok := file.(FileBatchAtomicWrite); ok {
err := file.RollbackAtomicWrite()
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)
}
case _FCNTL_PRAGMA:
if file, ok := file.(FilePragma); ok {
ptr := util.ReadUint32(mod, pArg+1*ptrlen)
name := util.ReadString(mod, ptr, _MAX_SQL_LENGTH)
var value string
if ptr := util.ReadUint32(mod, pArg+2*ptrlen); ptr != 0 {
value = util.ReadString(mod, ptr, _MAX_SQL_LENGTH)
}
out, err := file.Pragma(name, value)
ret := vfsErrorCode(err, _ERROR)
if ret == _ERROR {
out = err.Error()
}
if out != "" {
fn := mod.ExportedFunction("malloc")
stack := [...]uint64{uint64(len(out) + 1)}
if err := fn.CallWithStack(ctx, stack[:]); err != nil {
panic(err)
}
util.WriteUint32(mod, pArg, uint32(stack[0]))
util.WriteString(mod, uint32(stack[0]), out)
}
return ret
}
}
// Consider also implementing these opcodes (in use by SQLite):
// _FCNTL_BUSYHANDLER
// _FCNTL_LAST_ERRNO
// _FCNTL_SYNC
return _NOTFOUND
}
func vfsSectorSize(ctx context.Context, mod api.Module, pFile uint32) uint32 {
file := vfsFileGet(ctx, mod, pFile).(File)
return uint32(file.SectorSize())
}
func vfsDeviceCharacteristics(ctx context.Context, mod api.Module, pFile uint32) DeviceCharacteristic {
file := vfsFileGet(ctx, mod, pFile).(File)
return file.DeviceCharacteristics()
}
var shmBarrier sync.Mutex
func vfsShmBarrier(ctx context.Context, mod api.Module, pFile uint32) {
shmBarrier.Lock()
defer shmBarrier.Unlock()
}
func vfsShmMap(ctx context.Context, mod api.Module, pFile uint32, iRegion, szRegion int32, bExtend, pp uint32) _ErrorCode {
shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory()
p, err := shm.shmMap(ctx, mod, iRegion, szRegion, bExtend != 0)
if err != nil {
return vfsErrorCode(err, _IOERR_SHMMAP)
}
util.WriteUint32(mod, pp, p)
return _OK
}
func vfsShmLock(ctx context.Context, mod api.Module, pFile uint32, offset, n int32, flags _ShmFlag) _ErrorCode {
shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory()
err := shm.shmLock(offset, n, flags)
return vfsErrorCode(err, _IOERR_SHMLOCK)
}
func vfsShmUnmap(ctx context.Context, mod api.Module, pFile, bDelete uint32) _ErrorCode {
shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory()
shm.shmUnmap(bDelete != 0)
return _OK
}
func vfsGet(mod api.Module, pVfs uint32) VFS {
var name string
if pVfs != 0 {
const zNameOffset = 16
name = util.ReadString(mod, util.ReadUint32(mod, pVfs+zNameOffset), _MAX_NAME)
}
if vfs := Find(name); vfs != nil {
return vfs
}
panic(util.NoVFSErr + util.ErrorString(name))
}
func vfsFileRegister(ctx context.Context, mod api.Module, pFile uint32, file File) {
const fileHandleOffset = 4
id := util.AddHandle(ctx, file)
util.WriteUint32(mod, pFile+fileHandleOffset, id)
}
func vfsFileGet(ctx context.Context, mod api.Module, pFile uint32) any {
const fileHandleOffset = 4
id := util.ReadUint32(mod, pFile+fileHandleOffset)
return util.GetHandle(ctx, id)
}
func vfsFileClose(ctx context.Context, mod api.Module, pFile uint32) error {
const fileHandleOffset = 4
id := util.ReadUint32(mod, pFile+fileHandleOffset)
return util.DelHandle(ctx, id)
}
func vfsErrorCode(err error, def _ErrorCode) _ErrorCode {
if err == nil {
return _OK
}
switch v := reflect.ValueOf(err); v.Kind() {
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
return _ErrorCode(v.Uint())
}
return def
}