diff --git a/config.go b/config.go index 153fbd6..b390ffa 100644 --- a/config.go +++ b/config.go @@ -265,7 +265,7 @@ func traceCallback(ctx context.Context, mod api.Module, evt TraceEvent, pDB, pAr } } if arg1 != nil { - _, rc = errorCode(c.trace(evt, arg1, arg2), ERROR) + _ = c.trace(evt, arg1, arg2) } } return rc diff --git a/context.go b/context.go index 269bf52..f7a8cc5 100644 --- a/context.go +++ b/context.go @@ -198,14 +198,14 @@ func (ctx Context) ResultError(err error) { return } - msg, code := errorCode(err, _OK) + msg, code := errorCode(err, ERROR) if msg != "" { defer ctx.c.arena.mark()() ptr := ctx.c.arena.string(msg) ctx.c.call("sqlite3_result_error", stk_t(ctx.handle), stk_t(ptr), stk_t(len(msg))) } - if code != _OK { + if code != res_t(ERROR) { ctx.c.call("sqlite3_result_error_code", stk_t(ctx.handle), stk_t(code)) } diff --git a/driver/driver_test.go b/driver/driver_test.go index 1748fa7..863fcfc 100644 --- a/driver/driver_test.go +++ b/driver/driver_test.go @@ -44,8 +44,8 @@ func Test_Open_dir(t *testing.T) { if err == nil { t.Fatal("want error") } - if !errors.Is(err, sqlite3.CANTOPEN) { - t.Errorf("got %v, want sqlite3.CANTOPEN", err) + if !errors.Is(err, sqlite3.CANTOPEN_ISDIR) { + t.Errorf("got %v, want sqlite3.CANTOPEN_ISDIR", err) } } diff --git a/error.go b/error.go index 83b057d..3dfea7b 100644 --- a/error.go +++ b/error.go @@ -11,6 +11,7 @@ import ( // // https://sqlite.org/c3ref/errcode.html type Error struct { + sys error msg string sql string code res_t @@ -33,7 +34,7 @@ func (e *Error) ExtendedCode() ExtendedErrorCode { // Error implements the error interface. func (e *Error) Error() string { var b strings.Builder - b.WriteString(util.ErrorCodeString(uint32(e.code))) + b.WriteString(util.ErrorCodeString(e.code)) if e.msg != "" { b.WriteString(": ") @@ -43,6 +44,14 @@ func (e *Error) Error() string { return b.String() } +// Unwrap returns the underlying operating system error +// that caused the I/O error or failure to open a file. +// +// https://sqlite.org/c3ref/system_errno.html +func (e *Error) Unwrap() error { + return e.sys +} + // Is tests whether this error matches a given [ErrorCode] or [ExtendedErrorCode]. // // It makes it possible to do: @@ -90,7 +99,16 @@ func (e *Error) SQL() string { // Error implements the error interface. func (e ErrorCode) Error() string { - return util.ErrorCodeString(uint32(e)) + return util.ErrorCodeString(e) +} + +// As converts this error to an [ExtendedErrorCode]. +func (e ErrorCode) As(err any) bool { + c, ok := err.(*xErrorCode) + if ok { + *c = xErrorCode(e) + } + return ok } // Temporary returns true for [BUSY] errors. @@ -105,7 +123,7 @@ func (e ErrorCode) ExtendedCode() ExtendedErrorCode { // Error implements the error interface. func (e ExtendedErrorCode) Error() string { - return util.ErrorCodeString(uint32(e)) + return util.ErrorCodeString(e) } // Is tests whether this error matches a given [ErrorCode]. @@ -150,14 +168,10 @@ func errorCode(err error, def ErrorCode) (msg string, code res_t) { return code.msg, res_t(code.code) } - var ecode ErrorCode var xcode xErrorCode - switch { - case errors.As(err, &xcode): + if errors.As(err, &xcode) { code = res_t(xcode) - case errors.As(err, &ecode): - code = res_t(ecode) - default: + } else { code = res_t(def) } return err.Error(), code diff --git a/internal/util/error.go b/internal/util/error.go index 76769ed..d9bce5b 100644 --- a/internal/util/error.go +++ b/internal/util/error.go @@ -33,8 +33,12 @@ func AssertErr() ErrorString { return ErrorString(msg) } -func ErrorCodeString(rc uint32) string { - switch rc { +type errorCode interface { + ~uint8 | ~uint16 | ~uint32 | ~int32 +} + +func ErrorCodeString[T errorCode](rc T) string { + switch uint32(rc) { case ABORT_ROLLBACK: return "sqlite3: abort due to ROLLBACK" case ROW: @@ -42,7 +46,7 @@ func ErrorCodeString(rc uint32) string { case DONE: return "sqlite3: no more rows available" } - switch rc & 0xff { + switch uint8(rc) { case OK: return "sqlite3: not an error" case ERROR: diff --git a/internal/util/module.go b/internal/util/module.go index ba5754a..06b90bb 100644 --- a/internal/util/module.go +++ b/internal/util/module.go @@ -12,6 +12,7 @@ type ConnKey struct{} type moduleKey struct{} type moduleState struct { + sysError error mmapState handleState } @@ -23,3 +24,15 @@ func NewContext(ctx context.Context) context.Context { ctx = context.WithValue(ctx, moduleKey{}, state) return ctx } + +func GetSystemError(ctx context.Context) error { + s := ctx.Value(moduleKey{}).(*moduleState) + return s.sysError +} + +func SetSystemError(ctx context.Context, err error) { + s, ok := ctx.Value(moduleKey{}).(*moduleState) + if ok { + s.sysError = err + } +} diff --git a/sqlite.go b/sqlite.go index fb64ac5..67f961c 100644 --- a/sqlite.go +++ b/sqlite.go @@ -125,14 +125,15 @@ func (sqlt *sqlite) error(rc res_t, handle ptr_t, sql ...string) error { panic(util.OOMErr) } + var msg, query string if handle != 0 { - var msg, query string if ptr := ptr_t(sqlt.call("sqlite3_errmsg", stk_t(handle))); ptr != 0 { msg = util.ReadString(sqlt.mod, ptr, _MAX_LENGTH) + msg = strings.TrimPrefix(msg, "sqlite3: ") + msg = strings.TrimPrefix(msg, util.ErrorCodeString(rc)[len("sqlite3: "):]) + msg = strings.TrimPrefix(msg, ": ") if msg == "not an error" { msg = "" - } else { - msg = strings.TrimPrefix(msg, util.ErrorCodeString(uint32(rc))[len("sqlite3: "):]) } } @@ -141,10 +142,16 @@ func (sqlt *sqlite) error(rc res_t, handle ptr_t, sql ...string) error { query = sql[0][i:] } } + } - if msg != "" || query != "" { - return &Error{code: rc, msg: msg, sql: query} - } + var sys error + switch ErrorCode(rc) { + case CANTOPEN, IOERR: + sys = util.GetSystemError(sqlt.ctx) + } + + if sys != nil || msg != "" || query != "" { + return &Error{code: rc, sys: sys, msg: msg, sql: query} } return xErrorCode(rc) } diff --git a/tests/conn_test.go b/tests/conn_test.go index 1271f2f..bacd80c 100644 --- a/tests/conn_test.go +++ b/tests/conn_test.go @@ -22,8 +22,8 @@ func TestConn_Open_dir(t *testing.T) { if err == nil { t.Fatal("want error") } - if !errors.Is(err, sqlite3.CANTOPEN) { - t.Errorf("got %v, want sqlite3.CANTOPEN", err) + if !errors.Is(err, sqlite3.CANTOPEN_ISDIR) { + t.Errorf("got %v, want sqlite3.CANTOPEN_ISDIR", err) } } diff --git a/vfs/README.md b/vfs/README.md index 68cb92d..44d5944 100644 --- a/vfs/README.md +++ b/vfs/README.md @@ -73,7 +73,7 @@ to check if your build supports shared memory. ### Blocking Locks -On Windows and macOS, this package implements +On macOS, this package implements [Wal-mode blocking locks](https://sqlite.org/src/doc/tip/doc/wal-lock.md). ### Batch-Atomic Write diff --git a/vfs/api.go b/vfs/api.go index a0d36b2..46a716e 100644 --- a/vfs/api.go +++ b/vfs/api.go @@ -193,8 +193,8 @@ type FileSharedMemory interface { // 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) (ptr_t, _ErrorCode) - shmLock(int32, int32, _ShmFlag) _ErrorCode + shmMap(context.Context, api.Module, int32, int32, bool) (ptr_t, error) + shmLock(int32, int32, _ShmFlag) error shmUnmap(bool) shmBarrier() io.Closer diff --git a/vfs/const.go b/vfs/const.go index 9ed67e3..87e19d9 100644 --- a/vfs/const.go +++ b/vfs/const.go @@ -20,7 +20,7 @@ type ( type _ErrorCode uint32 func (e _ErrorCode) Error() string { - return util.ErrorCodeString(uint32(e)) + return util.ErrorCodeString(e) } const ( diff --git a/vfs/file.go b/vfs/file.go index bdebdf6..518f7dc 100644 --- a/vfs/file.go +++ b/vfs/file.go @@ -40,7 +40,7 @@ func evalSymlinks(path string) (string, error) { func (vfsOS) Delete(path string, syncDir bool) error { err := os.Remove(path) if errors.Is(err, fs.ErrNotExist) { - return _IOERR_DELETE_NOENT + return sysError{err, _IOERR_DELETE_NOENT} } if err != nil { return err @@ -53,7 +53,7 @@ func (vfsOS) Delete(path string, syncDir bool) error { defer f.Close() err = osSync(f, 0, SYNC_FULL) if err != nil { - return _IOERR_DIR_FSYNC + return sysError{err, _IOERR_DIR_FSYNC} } } return nil @@ -108,14 +108,14 @@ func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error } if err != nil { if name == nil { - return nil, flags, _IOERR_GETTEMPPATH + return nil, flags, sysError{err, _IOERR_GETTEMPPATH} } if errors.Is(err, syscall.EISDIR) { - return nil, flags, _CANTOPEN_ISDIR + return nil, flags, sysError{err, _CANTOPEN_ISDIR} } if isCreate && isJournl && errors.Is(err, fs.ErrPermission) && osAccess(name.String(), ACCESS_EXISTS) != nil { - return nil, flags, _READONLY_DIRECTORY + return nil, flags, sysError{err, _READONLY_DIRECTORY} } return nil, flags, err } @@ -123,7 +123,7 @@ func (vfsOS) OpenFilename(name *Filename, flags OpenFlag) (File, OpenFlag, error if modeof := name.URIParameter("modeof"); modeof != "" { if err = osSetMode(f, modeof); err != nil { f.Close() - return nil, flags, _IOERR_FSTAT + return nil, flags, sysError{err, _IOERR_FSTAT} } } if isUnix && flags&OPEN_DELETEONCLOSE != 0 { @@ -193,7 +193,7 @@ func (f *vfsFile) Sync(flags SyncFlag) error { defer d.Close() err = osSync(f.File, f.flags, flags) if err != nil { - return _IOERR_DIR_FSYNC + return sysError{err, _IOERR_DIR_FSYNC} } } return nil diff --git a/vfs/os_unix.go b/vfs/os_unix.go index ec312cc..8aa07e4 100644 --- a/vfs/os_unix.go +++ b/vfs/os_unix.go @@ -33,7 +33,7 @@ func osReadAt(file *os.File, p []byte, off int64) (int, error) { unix.ERANGE, unix.EIO, unix.ENXIO: - return n, _IOERR_CORRUPTFS + return n, sysError{err, _IOERR_CORRUPTFS} } } return n, err @@ -42,7 +42,7 @@ func osReadAt(file *os.File, p []byte, off int64) (int, error) { func osWriteAt(file *os.File, p []byte, off int64) (int, error) { n, err := file.WriteAt(p, off) if errno, ok := err.(unix.Errno); ok && errno == unix.ENOSPC { - return n, _FULL + return n, sysError{err, _FULL} } return n, err } diff --git a/vfs/os_windows.go b/vfs/os_windows.go index 0a6693d..19ad7a2 100644 --- a/vfs/os_windows.go +++ b/vfs/os_windows.go @@ -20,7 +20,7 @@ func osWriteAt(file *os.File, p []byte, off int64) (int, error) { case windows.ERROR_HANDLE_DISK_FULL, windows.ERROR_DISK_FULL: - return n, _FULL + return n, sysError{err, _FULL} } } return n, err diff --git a/vfs/shm_bsd.go b/vfs/shm_bsd.go index be1495d..4545517 100644 --- a/vfs/shm_bsd.go +++ b/vfs/shm_bsd.go @@ -141,7 +141,7 @@ func (s *vfsShm) shmOpen() (rc _ErrorCode) { return _OK } -func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, _ErrorCode) { +func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, error) { // Ensure size is a multiple of the OS page size. if int(size)&(unix.Getpagesize()-1) != 0 { return 0, _IOERR_SHMMAP @@ -154,26 +154,30 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext // Check if file is big enough. o, err := s.Seek(0, io.SeekEnd) if err != nil { - return 0, _IOERR_SHMSIZE + return 0, sysError{err, _IOERR_SHMSIZE} } if n := (int64(id) + 1) * int64(size); n > o { if !extend { - return 0, _OK + return 0, nil } - if osAllocate(s.File, n) != nil { - return 0, _IOERR_SHMSIZE + if err := osAllocate(s.File, n); err != nil { + return 0, sysError{err, _IOERR_SHMSIZE} } } r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, false) if err != nil { - return 0, _IOERR_SHMMAP + return 0, err } s.regions = append(s.regions, r) - return r.Ptr, _OK + return r.Ptr, nil } -func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { +func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error { + if s.vfsShmParent == nil { + return _IOERR_SHMLOCK + } + s.Lock() defer s.Unlock() diff --git a/vfs/shm_copy.go b/vfs/shm_copy.go index db8ddb4..4e20bcb 100644 --- a/vfs/shm_copy.go +++ b/vfs/shm_copy.go @@ -31,8 +31,8 @@ const ( // // https://sqlite.org/walformat.html#the_wal_index_file_format -func (s *vfsShm) shmAcquire(ptr *_ErrorCode) { - if ptr != nil && *ptr != _OK { +func (s *vfsShm) shmAcquire(errp *error) { + if errp != nil && *errp != _OK { return } if len(s.ptrs) == 0 || shmEqual(s.shadow[0][:], s.shared[0][:]) { diff --git a/vfs/shm_dotlk.go b/vfs/shm_dotlk.go index cb697a9..4dbfb3a 100644 --- a/vfs/shm_dotlk.go +++ b/vfs/shm_dotlk.go @@ -59,7 +59,7 @@ func (s *vfsShm) Close() error { } if err := dotlk.Unlock(s.path); err != nil { - return _IOERR_UNLOCK + return sysError{err, _IOERR_UNLOCK} } delete(vfsShmList, s.path) s.vfsShmParent = nil @@ -96,7 +96,7 @@ func (s *vfsShm) shmOpen() _ErrorCode { return _OK } -func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, _ErrorCode) { +func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, error) { if size != _WALINDEX_PGSZ { return 0, _IOERR_SHMMAP } @@ -116,7 +116,7 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext // Extend shared memory. if int(id) >= len(s.shared) { if !extend { - return 0, _OK + return 0, nil } s.shared = append(s.shared, make([][_WALINDEX_PGSZ]byte, int(id)-len(s.shared)+1)...) } @@ -140,16 +140,20 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext } s.shadow[0][4] = 1 - return s.ptrs[id], _OK + return s.ptrs[id], nil } -func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (rc _ErrorCode) { +func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (err error) { + if s.vfsShmParent == nil { + return _IOERR_SHMLOCK + } + s.Lock() defer s.Unlock() switch { case flags&_SHM_LOCK != 0: - defer s.shmAcquire(&rc) + defer s.shmAcquire(&err) case flags&_SHM_EXCLUSIVE != 0: s.shmRelease() } diff --git a/vfs/shm_ofd.go b/vfs/shm_ofd.go index b0f50fc..d7a3781 100644 --- a/vfs/shm_ofd.go +++ b/vfs/shm_ofd.go @@ -73,7 +73,7 @@ func (s *vfsShm) shmOpen() _ErrorCode { return rc } -func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, _ErrorCode) { +func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (ptr_t, error) { // Ensure size is a multiple of the OS page size. if int(size)&(unix.Getpagesize()-1) != 0 { return 0, _IOERR_SHMMAP @@ -86,29 +86,32 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext // Check if file is big enough. o, err := s.Seek(0, io.SeekEnd) if err != nil { - return 0, _IOERR_SHMSIZE + return 0, sysError{err, _IOERR_SHMSIZE} } if n := (int64(id) + 1) * int64(size); n > o { if !extend { - return 0, _OK + return 0, nil } - if s.readOnly || osAllocate(s.File, n) != nil { + if s.readOnly { return 0, _IOERR_SHMSIZE } + if err := osAllocate(s.File, n); err != nil { + return 0, sysError{err, _IOERR_SHMSIZE} + } } r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size, s.readOnly) if err != nil { - return 0, _IOERR_SHMMAP + return 0, err } s.regions = append(s.regions, r) if s.readOnly { return r.Ptr, _READONLY } - return r.Ptr, _OK + return r.Ptr, nil } -func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { +func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) error { // Argument check. switch { case n <= 0: @@ -129,6 +132,10 @@ func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) _ErrorCode { panic(util.AssertErr()) } + if s.File == nil { + return _IOERR_SHMLOCK + } + var timeout time.Duration if s.blocking { timeout = time.Millisecond diff --git a/vfs/shm_windows.go b/vfs/shm_windows.go index 0be523a..bf54206 100644 --- a/vfs/shm_windows.go +++ b/vfs/shm_windows.go @@ -65,7 +65,7 @@ func (s *vfsShm) shmOpen() _ErrorCode { return rc } -func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (_ ptr_t, rc _ErrorCode) { +func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, extend bool) (_ ptr_t, err error) { // Ensure size is a multiple of the OS page size. if size != _WALINDEX_PGSZ || (windows.Getpagesize()-1)&_WALINDEX_PGSZ != 0 { return 0, _IOERR_SHMMAP @@ -79,19 +79,19 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext return 0, rc } - defer s.shmAcquire(&rc) + defer s.shmAcquire(&err) // Check if file is big enough. o, err := s.Seek(0, io.SeekEnd) if err != nil { - return 0, _IOERR_SHMSIZE + return 0, sysError{err, _IOERR_SHMSIZE} } if n := (int64(id) + 1) * int64(size); n > o { if !extend { - return 0, _OK + return 0, nil } - if osAllocate(s.File, n) != nil { - return 0, _IOERR_SHMSIZE + if err := osAllocate(s.File, n); err != nil { + return 0, sysError{err, _IOERR_SHMSIZE} } } @@ -99,7 +99,7 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext for int(id) >= len(s.shared) { r, err := util.MapRegion(ctx, mod, s.File, int64(id)*int64(size), size) if err != nil { - return 0, _IOERR_SHMMAP + return 0, err } s.regions = append(s.regions, r) s.shared = append(s.shared, r.Data) @@ -124,13 +124,17 @@ func (s *vfsShm) shmMap(ctx context.Context, mod api.Module, id, size int32, ext } s.shadow[0][4] = 1 - return s.ptrs[id], _OK + return s.ptrs[id], nil } -func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (rc _ErrorCode) { +func (s *vfsShm) shmLock(offset, n int32, flags _ShmFlag) (err error) { + if s.File == nil { + return _IOERR_SHMLOCK + } + switch { case flags&_SHM_LOCK != 0: - defer s.shmAcquire(&rc) + defer s.shmAcquire(&err) case flags&_SHM_EXCLUSIVE != 0: s.shmRelease() } diff --git a/vfs/vfs.go b/vfs/vfs.go index aef81c3..49ca25b 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -102,7 +102,7 @@ func vfsFullPathname(ctx context.Context, mod api.Module, pVfs, zRelative ptr_t, } util.WriteString(mod, zFull, path) - return vfsErrorCode(err, _CANTOPEN_FULLPATH) + return vfsErrorCode(ctx, err, _CANTOPEN_FULLPATH) } func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath ptr_t, syncDir int32) _ErrorCode { @@ -110,7 +110,7 @@ func vfsDelete(ctx context.Context, mod api.Module, pVfs, zPath ptr_t, syncDir i path := util.ReadString(mod, zPath, _MAX_PATHNAME) err := vfs.Delete(path, syncDir != 0) - return vfsErrorCode(err, _IOERR_DELETE) + return vfsErrorCode(ctx, err, _IOERR_DELETE) } func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath ptr_t, flags AccessFlag, pResOut ptr_t) _ErrorCode { @@ -119,7 +119,7 @@ func vfsAccess(ctx context.Context, mod api.Module, pVfs, zPath ptr_t, flags Acc ok, err := vfs.Access(path, flags) util.WriteBool(mod, pResOut, ok) - return vfsErrorCode(err, _IOERR_ACCESS) + return vfsErrorCode(ctx, err, _IOERR_ACCESS) } func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile ptr_t, flags OpenFlag, pOutFlags, pOutVFS ptr_t) _ErrorCode { @@ -134,7 +134,7 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile ptr_t, flag file, flags, err = vfs.Open(name.String(), flags) } if err != nil { - return vfsErrorCode(err, _CANTOPEN) + return vfsErrorCode(ctx, err, _CANTOPEN) } if file, ok := file.(FilePowersafeOverwrite); ok { @@ -155,7 +155,7 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile ptr_t, flag func vfsClose(ctx context.Context, mod api.Module, pFile ptr_t) _ErrorCode { err := vfsFileClose(ctx, mod, pFile) - return vfsErrorCode(err, _IOERR_CLOSE) + return vfsErrorCode(ctx, err, _IOERR_CLOSE) } func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf ptr_t, iAmt int32, iOfst int64) _ErrorCode { @@ -167,7 +167,7 @@ func vfsRead(ctx context.Context, mod api.Module, pFile, zBuf ptr_t, iAmt int32, return _OK } if err != io.EOF { - return vfsErrorCode(err, _IOERR_READ) + return vfsErrorCode(ctx, err, _IOERR_READ) } clear(buf[n:]) return _IOERR_SHORT_READ @@ -178,45 +178,45 @@ func vfsWrite(ctx context.Context, mod api.Module, pFile, zBuf ptr_t, iAmt int32 buf := util.View(mod, zBuf, int64(iAmt)) _, err := file.WriteAt(buf, iOfst) - return vfsErrorCode(err, _IOERR_WRITE) + return vfsErrorCode(ctx, err, _IOERR_WRITE) } func vfsTruncate(ctx context.Context, mod api.Module, pFile ptr_t, nByte int64) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) err := file.Truncate(nByte) - return vfsErrorCode(err, _IOERR_TRUNCATE) + return vfsErrorCode(ctx, err, _IOERR_TRUNCATE) } func vfsSync(ctx context.Context, mod api.Module, pFile ptr_t, flags SyncFlag) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) err := file.Sync(flags) - return vfsErrorCode(err, _IOERR_FSYNC) + return vfsErrorCode(ctx, err, _IOERR_FSYNC) } func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize ptr_t) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) size, err := file.Size() util.Write64(mod, pSize, size) - return vfsErrorCode(err, _IOERR_SEEK) + return vfsErrorCode(ctx, err, _IOERR_SEEK) } func vfsLock(ctx context.Context, mod api.Module, pFile ptr_t, eLock LockLevel) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) err := file.Lock(eLock) - return vfsErrorCode(err, _IOERR_LOCK) + return vfsErrorCode(ctx, err, _IOERR_LOCK) } func vfsUnlock(ctx context.Context, mod api.Module, pFile ptr_t, eLock LockLevel) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) err := file.Unlock(eLock) - return vfsErrorCode(err, _IOERR_UNLOCK) + return vfsErrorCode(ctx, err, _IOERR_UNLOCK) } func vfsCheckReservedLock(ctx context.Context, mod api.Module, pFile, pResOut ptr_t) _ErrorCode { file := vfsFileGet(ctx, mod, pFile).(File) locked, err := file.CheckReservedLock() util.WriteBool(mod, pResOut, locked) - return vfsErrorCode(err, _IOERR_CHECKRESERVEDLOCK) + return vfsErrorCode(ctx, err, _IOERR_CHECKRESERVEDLOCK) } func vfsFileControl(ctx context.Context, mod api.Module, pFile ptr_t, op _FcntlOpcode, pArg ptr_t) _ErrorCode { @@ -268,20 +268,20 @@ func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _Fcnt if file, ok := file.(FileSizeHint); ok { size := util.Read64[int64](mod, pArg) err := file.SizeHint(size) - return vfsErrorCode(err, _IOERR_TRUNCATE) + return vfsErrorCode(ctx, err, _IOERR_TRUNCATE) } case _FCNTL_HAS_MOVED: if file, ok := file.(FileHasMoved); ok { moved, err := file.HasMoved() util.WriteBool(mod, pArg, moved) - return vfsErrorCode(err, _IOERR_FSTAT) + return vfsErrorCode(ctx, err, _IOERR_FSTAT) } case _FCNTL_OVERWRITE: if file, ok := file.(FileOverwrite); ok { err := file.Overwrite() - return vfsErrorCode(err, _IOERR) + return vfsErrorCode(ctx, err, _IOERR) } case _FCNTL_SYNC: @@ -291,29 +291,29 @@ func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _Fcnt name = util.ReadString(mod, pArg, _MAX_PATHNAME) } err := file.SyncSuper(name) - return vfsErrorCode(err, _IOERR) + return vfsErrorCode(ctx, err, _IOERR) } case _FCNTL_COMMIT_PHASETWO: if file, ok := file.(FileCommitPhaseTwo); ok { err := file.CommitPhaseTwo() - return vfsErrorCode(err, _IOERR) + return vfsErrorCode(ctx, err, _IOERR) } case _FCNTL_BEGIN_ATOMIC_WRITE: if file, ok := file.(FileBatchAtomicWrite); ok { err := file.BeginAtomicWrite() - return vfsErrorCode(err, _IOERR_BEGIN_ATOMIC) + return vfsErrorCode(ctx, err, _IOERR_BEGIN_ATOMIC) } case _FCNTL_COMMIT_ATOMIC_WRITE: if file, ok := file.(FileBatchAtomicWrite); ok { err := file.CommitAtomicWrite() - return vfsErrorCode(err, _IOERR_COMMIT_ATOMIC) + return vfsErrorCode(ctx, err, _IOERR_COMMIT_ATOMIC) } case _FCNTL_ROLLBACK_ATOMIC_WRITE: if file, ok := file.(FileBatchAtomicWrite); ok { err := file.RollbackAtomicWrite() - return vfsErrorCode(err, _IOERR_ROLLBACK_ATOMIC) + return vfsErrorCode(ctx, err, _IOERR_ROLLBACK_ATOMIC) } case _FCNTL_CKPT_START: @@ -338,7 +338,7 @@ func vfsFileControlImpl(ctx context.Context, mod api.Module, file File, op _Fcnt out, err := file.Pragma(strings.ToLower(name), value) - ret := vfsErrorCode(err, _ERROR) + ret := vfsErrorCode(ctx, err, _ERROR) if ret == _ERROR { out = err.Error() } @@ -407,14 +407,15 @@ func vfsShmBarrier(ctx context.Context, mod api.Module, pFile ptr_t) { func vfsShmMap(ctx context.Context, mod api.Module, pFile ptr_t, iRegion, szRegion, bExtend int32, pp ptr_t) _ErrorCode { shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() - p, rc := shm.shmMap(ctx, mod, iRegion, szRegion, bExtend != 0) + p, err := shm.shmMap(ctx, mod, iRegion, szRegion, bExtend != 0) util.Write32(mod, pp, p) - return rc + return vfsErrorCode(ctx, err, _IOERR_SHMMAP) } func vfsShmLock(ctx context.Context, mod api.Module, pFile ptr_t, offset, n int32, flags _ShmFlag) _ErrorCode { shm := vfsFileGet(ctx, mod, pFile).(FileSharedMemory).SharedMemory() - return shm.shmLock(offset, n, flags) + err := shm.shmLock(offset, n, flags) + return vfsErrorCode(ctx, err, _IOERR_SHMLOCK) } func vfsShmUnmap(ctx context.Context, mod api.Module, pFile ptr_t, bDelete int32) _ErrorCode { @@ -454,13 +455,36 @@ func vfsFileClose(ctx context.Context, mod api.Module, pFile ptr_t) error { return util.DelHandle(ctx, id) } -func vfsErrorCode(err error, def _ErrorCode) _ErrorCode { - if err == nil { - return _OK +func vfsErrorCode(ctx context.Context, err error, code _ErrorCode) _ErrorCode { + var sys error + + switch err := err.(type) { + case nil: + code = _OK + case _ErrorCode: + code = err + case sysError: + code = err.code + sys = err.error + default: + switch v := reflect.ValueOf(err); v.Kind() { + case reflect.Uint8, reflect.Uint16: + code = _ErrorCode(v.Uint()) + } } - switch v := reflect.ValueOf(err); v.Kind() { - case reflect.Uint8, reflect.Uint16, reflect.Uint32: - return _ErrorCode(v.Uint()) - } - return def + + util.SetSystemError(ctx, sys) + return code +} + +func SystemError[T interface{ ~uint8 | ~uint16 }](err error, code T) error { + if err == nil { + return nil + } + return sysError{error: err, code: _ErrorCode(code)} +} + +type sysError struct { + error + code _ErrorCode } diff --git a/vtab.go b/vtab.go index 16ff280..0725eae 100644 --- a/vtab.go +++ b/vtab.go @@ -465,19 +465,19 @@ func vtabModuleCallback(i vtabConstructor) func(_ context.Context, _ api.Module, vtabPutHandle(ctx, mod, ppVTab, val[0].Interface()) } - return vtabError(ctx, mod, pzErr, _PTR_ERROR, err) + return vtabError(ctx, mod, pzErr, _PTR_ERROR, err, ERROR) } } func vtabDisconnectCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t { err := vtabDelHandle(ctx, mod, pVTab) - return vtabError(ctx, mod, 0, _PTR_ERROR, err) + return vtabError(ctx, mod, 0, _PTR_ERROR, err, ERROR) } func vtabDestroyCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabDestroyer) err := errors.Join(vtab.Destroy(), vtabDelHandle(ctx, mod, pVTab)) - return vtabError(ctx, mod, 0, _PTR_ERROR, err) + return vtabError(ctx, mod, 0, _PTR_ERROR, err, ERROR) } func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo ptr_t) res_t { @@ -490,7 +490,7 @@ func vtabBestIndexCallback(ctx context.Context, mod api.Module, pVTab, pIdxInfo err := vtab.BestIndex(&info) info.save() - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func vtabUpdateCallback(ctx context.Context, mod api.Module, pVTab ptr_t, nArg int32, pArg, pRowID ptr_t) res_t { @@ -504,13 +504,13 @@ func vtabUpdateCallback(ctx context.Context, mod api.Module, pVTab ptr_t, nArg i util.Write64(mod, pRowID, rowID) } - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func vtabRenameCallback(ctx context.Context, mod api.Module, pVTab, zNew ptr_t) res_t { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabRenamer) err := vtab.Rename(util.ReadString(mod, zNew, _MAX_NAME)) - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func vtabFindFuncCallback(ctx context.Context, mod api.Module, pVTab ptr_t, nArg int32, zName, pxFunc ptr_t) int32 { @@ -534,51 +534,49 @@ func vtabIntegrityCallback(ctx context.Context, mod api.Module, pVTab, zSchema, err := vtab.Integrity(schema, table, int(mFlags)) // xIntegrity should return OK - even if it finds problems in the content of the virtual table. // https://sqlite.org/vtab.html#xintegrity - vtabError(ctx, mod, pzErr, _PTR_ERROR, err) - _, code := errorCode(err, _OK) - return code + return vtabError(ctx, mod, pzErr, _PTR_ERROR, err, _OK) } func vtabBeginCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn) err := vtab.Begin() - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func vtabSyncCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn) err := vtab.Sync() - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func vtabCommitCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn) err := vtab.Commit() - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func vtabRollbackCallback(ctx context.Context, mod api.Module, pVTab ptr_t) res_t { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabTxn) err := vtab.Rollback() - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func vtabSavepointCallback(ctx context.Context, mod api.Module, pVTab ptr_t, id int32) res_t { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer) err := vtab.Savepoint(int(id)) - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func vtabReleaseCallback(ctx context.Context, mod api.Module, pVTab ptr_t, id int32) res_t { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer) err := vtab.Release(int(id)) - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func vtabRollbackToCallback(ctx context.Context, mod api.Module, pVTab ptr_t, id int32) res_t { vtab := vtabGetHandle(ctx, mod, pVTab).(VTabSavepointer) err := vtab.RollbackTo(int(id)) - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur ptr_t) res_t { @@ -589,12 +587,12 @@ func cursorOpenCallback(ctx context.Context, mod api.Module, pVTab, ppCur ptr_t) vtabPutHandle(ctx, mod, ppCur, cursor) } - return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err) + return vtabError(ctx, mod, pVTab, _VTAB_ERROR, err, ERROR) } func cursorCloseCallback(ctx context.Context, mod api.Module, pCur ptr_t) res_t { err := vtabDelHandle(ctx, mod, pCur) - return vtabError(ctx, mod, 0, _VTAB_ERROR, err) + return vtabError(ctx, mod, 0, _PTR_ERROR, err, ERROR) } func cursorFilterCallback(ctx context.Context, mod api.Module, pCur ptr_t, idxNum int32, idxStr ptr_t, nArg int32, pArg ptr_t) res_t { @@ -609,7 +607,7 @@ func cursorFilterCallback(ctx context.Context, mod api.Module, pCur ptr_t, idxNu cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) err := cursor.Filter(int(idxNum), idxName, *args...) - return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err) + return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err, ERROR) } func cursorEOFCallback(ctx context.Context, mod api.Module, pCur ptr_t) int32 { @@ -623,14 +621,14 @@ func cursorEOFCallback(ctx context.Context, mod api.Module, pCur ptr_t) int32 { func cursorNextCallback(ctx context.Context, mod api.Module, pCur ptr_t) res_t { cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) err := cursor.Next() - return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err) + return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err, ERROR) } func cursorColumnCallback(ctx context.Context, mod api.Module, pCur, pCtx ptr_t, n int32) res_t { cursor := vtabGetHandle(ctx, mod, pCur).(VTabCursor) db := ctx.Value(connKey{}).(*Conn) err := cursor.Column(Context{db, pCtx}, int(n)) - return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err) + return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err, ERROR) } func cursorRowIDCallback(ctx context.Context, mod api.Module, pCur, pRowID ptr_t) res_t { @@ -641,7 +639,7 @@ func cursorRowIDCallback(ctx context.Context, mod api.Module, pCur, pRowID ptr_t util.Write64(mod, pRowID, rowID) } - return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err) + return vtabError(ctx, mod, pCur, _CURSOR_ERROR, err, ERROR) } const ( @@ -650,10 +648,10 @@ const ( _CURSOR_ERROR ) -func vtabError(ctx context.Context, mod api.Module, ptr ptr_t, kind uint32, err error) res_t { +func vtabError(ctx context.Context, mod api.Module, ptr ptr_t, kind uint32, err error, def ErrorCode) res_t { const zErrMsgOffset = 8 - msg, code := errorCode(err, ERROR) - if msg != "" && ptr != 0 { + msg, code := errorCode(err, def) + if ptr != 0 && msg != "" { switch kind { case _VTAB_ERROR: ptr = ptr + zErrMsgOffset // zErrMsg