From 8e327a9783d9229aadc8c0a6b2e5283a6137aeca Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Thu, 25 Apr 2024 13:29:19 +0100 Subject: [PATCH] VFS pragma. --- README.md | 1 - conn.go | 2 +- vfs/adiantum/hbsh.go | 21 ++++++++++++++++++++ vfs/api.go | 29 ++++++++++++++++++++++++++++ vfs/const.go | 4 ++++ vfs/vfs.go | 46 ++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 97 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 01fbaa9..f19fb25 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,6 @@ It also benefits greatly from [SQLite's](https://sqlite.org/testing.html) and Every commit is [tested](.github/workflows/test.yml) on Linux (amd64/arm64/386/riscv64), macOS (amd64/arm64), Windows, FreeBSD and illumos. - The Go VFS is tested by running SQLite's [mptest](https://github.com/sqlite/sqlite/blob/master/mptest/mptest.c). diff --git a/conn.go b/conn.go index fc2d765..e227931 100644 --- a/conn.go +++ b/conn.go @@ -176,7 +176,7 @@ func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) { // // https://sqlite.org/c3ref/prepare.html func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) { - if len(sql) > _MAX_LENGTH { + if len(sql) > _MAX_SQL_LENGTH { return nil, "", TOOBIG } diff --git a/vfs/adiantum/hbsh.go b/vfs/adiantum/hbsh.go index aaebbda..082999a 100644 --- a/vfs/adiantum/hbsh.go +++ b/vfs/adiantum/hbsh.go @@ -168,6 +168,13 @@ func (h *hbshFile) SharedMemory() vfs.SharedMemory { // Wrap optional methods. +func (h *hbshFile) ChunkSize(size int) { + if f, ok := h.File.(vfs.FileChunkSize); ok { + size = (size + blockSize - 1) &^ (blockSize - 1) // round up + f.ChunkSize(size) + } +} + func (h *hbshFile) SizeHint(size int64) error { if f, ok := h.File.(vfs.FileSizeHint); ok { size = (size + blockSize - 1) &^ (blockSize - 1) // round up @@ -217,3 +224,17 @@ func (h *hbshFile) RollbackAtomicWrite() error { } return sqlite3.NOTFOUND } + +func (h *hbshFile) CheckpointDone() error { + if f, ok := h.File.(vfs.FileCheckpoint); ok { + return f.CheckpointDone() + } + return sqlite3.NOTFOUND +} + +func (h *hbshFile) CheckpointStart() error { + if f, ok := h.File.(vfs.FileCheckpoint); ok { + return f.CheckpointStart() + } + return sqlite3.NOTFOUND +} diff --git a/vfs/api.go b/vfs/api.go index 24bbe06..c8e7fb0 100644 --- a/vfs/api.go +++ b/vfs/api.go @@ -57,6 +57,15 @@ type FileLockState interface { 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. // @@ -125,6 +134,26 @@ 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. +// +// 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. // It's OK for SharedMemory to return nil. type FileSharedMemory interface { diff --git a/vfs/const.go b/vfs/const.go index 6405d61..7f409f3 100644 --- a/vfs/const.go +++ b/vfs/const.go @@ -4,8 +4,11 @@ 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 @@ -17,6 +20,7 @@ func (e _ErrorCode) Error() string { const ( _OK _ErrorCode = util.OK + _ERROR _ErrorCode = util.ERROR _PERM _ErrorCode = util.PERM _BUSY _ErrorCode = util.BUSY _READONLY _ErrorCode = util.READONLY diff --git a/vfs/vfs.go b/vfs/vfs.go index 9080202..c37ec22 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -284,6 +284,13 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl 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) @@ -329,14 +336,45 @@ func vfsFileControl(ctx context.Context, mod api.Module, pFile uint32, op _Fcntl 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 { + name := util.ReadUint32(mod, pArg+1*ptrlen) + value := util.ReadUint32(mod, pArg+2*ptrlen) + out, err := file.Pragma( + util.ReadString(mod, name, _MAX_SQL_LENGTH), + util.ReadString(mod, value, _MAX_SQL_LENGTH)) + if err != nil { + 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 _ERROR + } + return vfsErrorCode(err, _ERROR) + } } // Consider also implementing these opcodes (in use by SQLite): // _FCNTL_BUSYHANDLER - // _FCNTL_CHUNK_SIZE - // _FCNTL_CKPT_DONE - // _FCNTL_CKPT_START - // _FCNTL_PRAGMA + // _FCNTL_LAST_ERRNO // _FCNTL_SYNC return _NOTFOUND }