VFS pragma.

This commit is contained in:
Nuno Cruces
2024-04-25 13:29:19 +01:00
parent 09a0ce04ce
commit 8e327a9783
6 changed files with 97 additions and 6 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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