ReaderVFS.

This commit is contained in:
Nuno Cruces
2023-05-23 16:34:09 +01:00
parent cfb69e4ce7
commit cc2d16ac83
10 changed files with 198 additions and 4 deletions

View File

@@ -73,7 +73,7 @@ Performance is tested by running
- [ ] custom SQL functions
- [ ] custom VFSes
- [ ] in-memory VFS
- [ ] read-only VFS, wrapping an [`io.ReaderAt`](https://pkg.go.dev/io#ReaderAt)
- [x] read-only VFS, wrapping an [`io.ReaderAt`](https://pkg.go.dev/io#ReaderAt)
- [ ] cloud-based VFS, based on [Cloud Backed SQLite](https://sqlite.org/cloudsqlite/doc/trunk/www/index.wiki)
- [x] custom VFS API

1
go.mod
View File

@@ -4,6 +4,7 @@ go 1.19
require (
github.com/ncruces/julianday v0.1.5
github.com/psanford/httpreadat v0.1.0
github.com/tetratelabs/wazero v1.1.0
golang.org/x/sync v0.2.0
golang.org/x/sys v0.8.0

2
go.sum
View File

@@ -1,5 +1,7 @@
github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FBZk=
github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
github.com/tetratelabs/wazero v1.1.0 h1:EByoAhC+QcYpwSZJSs/aV0uokxPwBgKxfiokSUwAknQ=
github.com/tetratelabs/wazero v1.1.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=

View File

@@ -25,7 +25,7 @@ type File interface {
WriteAt(p []byte, off int64) (n int, err error)
Truncate(size int64) error
Sync(flags SyncFlag) error
FileSize() (int64, error)
Size() (int64, error)
Lock(lock LockLevel) error
Unlock(lock LockLevel) error
CheckReservedLock() (bool, error)

View File

@@ -19,6 +19,7 @@ const (
_OK _ErrorCode = util.OK
_PERM _ErrorCode = util.PERM
_BUSY _ErrorCode = util.BUSY
_READONLY _ErrorCode = util.READONLY
_IOERR _ErrorCode = util.IOERR
_NOTFOUND _ErrorCode = util.NOTFOUND
_CANTOPEN _ErrorCode = util.CANTOPEN

View File

@@ -151,7 +151,7 @@ func (f *vfsFile) Sync(flags SyncFlag) error {
return nil
}
func (f *vfsFile) FileSize() (int64, error) {
func (f *vfsFile) Size() (int64, error) {
return f.Seek(0, io.SeekEnd)
}

118
sqlite3vfs/reader.go Normal file
View File

@@ -0,0 +1,118 @@
package sqlite3vfs
import (
"io"
"io/fs"
)
// A ReaderVFS is [VFS] for immutable databases.
type ReaderVFS map[string]SizeReaderAt
var _ VFS = ReaderVFS{}
// A SizeReaderAt is a ReaderAt with a Size method.
// Use [NewSizeReaderAt] to adapt different Size interfaces.
type SizeReaderAt interface {
Size() (int64, error)
io.ReaderAt
}
// Open implements the [VFS] interface.
func (vfs ReaderVFS) Open(name string, flags OpenFlag) (File, OpenFlag, error) {
if flags&OPEN_MAIN_DB == 0 {
return nil, flags, _CANTOPEN
}
if ra, ok := vfs[name]; ok {
return readerFile{ra}, flags, nil
}
return nil, flags, _CANTOPEN
}
// Delete implements the [VFS] interface.
func (vfs ReaderVFS) Delete(name string, dirSync bool) error {
return _IOERR_DELETE
}
// Access implements the [VFS] interface.
func (vfs ReaderVFS) Access(name string, flag AccessFlag) (bool, error) {
return false, nil
}
// FullPathname implements the [VFS] interface.
func (vfs ReaderVFS) FullPathname(name string) (string, error) {
return name, nil
}
type readerFile struct{ SizeReaderAt }
func (r readerFile) Close() error {
if c, ok := r.SizeReaderAt.(io.Closer); ok {
return c.Close()
}
return nil
}
func (readerFile) WriteAt(b []byte, off int64) (n int, err error) {
return 0, _READONLY
}
func (readerFile) Truncate(size int64) error {
return _READONLY
}
func (readerFile) Sync(flag SyncFlag) error {
return nil
}
func (readerFile) Lock(elock LockLevel) error {
return nil
}
func (readerFile) Unlock(elock LockLevel) error {
return nil
}
func (readerFile) CheckReservedLock() (bool, error) {
return false, nil
}
func (readerFile) SectorSize() int {
return 0
}
func (readerFile) DeviceCharacteristics() DeviceCharacteristic {
return IOCAP_IMMUTABLE
}
// NewSizeReaderAt returns a SizeReaderAt given an io.ReaderAt
// that implements one of:
// - Size() (int64, error)
// - Size() int64
// - Len() int
// - Stat() (fs.FileInfo, error)
// - Seek(offset int64, whence int) (int64, error)
func NewSizeReaderAt(r io.ReaderAt) SizeReaderAt {
return sizer{r}
}
type sizer struct{ io.ReaderAt }
func (s sizer) Size() (int64, error) {
switch s := s.ReaderAt.(type) {
case interface{ Size() (int64, error) }:
return s.Size()
case interface{ Size() int64 }:
return s.Size(), nil
case interface{ Len() int }:
return int64(s.Len()), nil
case interface{ Stat() (fs.FileInfo, error) }:
fi, err := s.Stat()
if err != nil {
return 0, err
}
return fi.Size(), nil
case io.Seeker:
return s.Seek(0, io.SeekEnd)
}
return 0, _IOERR_SEEK
}

60
sqlite3vfs/reader_test.go Normal file
View File

@@ -0,0 +1,60 @@
package sqlite3vfs_test
import (
"database/sql"
"fmt"
"log"
_ "github.com/ncruces/go-sqlite3/driver"
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/ncruces/go-sqlite3/sqlite3vfs"
"github.com/psanford/httpreadat"
)
func ExampleReaderVFS() {
sqlite3vfs.Register("httpvfs", sqlite3vfs.ReaderVFS{
"demo.db": httpreadat.New("https://www.sanford.io/demo.db"),
})
db, err := sql.Open("sqlite3", "file:demo.db?vfs=httpvfs&mode=ro")
if err != nil {
log.Fatal(err)
}
defer db.Close()
magname := map[int]string{
3: "thousand",
6: "million",
9: "billion",
}
rows, err := db.Query(`
SELECT period, data_value, magntude, units FROM csv
WHERE period > '2010'
LIMIT 10`)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var period, units string
var value int64
var mag int
err = rows.Scan(&period, &value, &mag, &units)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s: %d %s %s\n", period, value, magname[mag], units)
}
// Output:
// 2010.03: 17463 million Dollars
// 2010.06: 17260 million Dollars
// 2010.09: 15419 million Dollars
// 2010.12: 17088 million Dollars
// 2011.03: 18516 million Dollars
// 2011.06: 18835 million Dollars
// 2011.09: 16390 million Dollars
// 2011.12: 18748 million Dollars
// 2012.03: 18477 million Dollars
// 2012.06: 18270 million Dollars
}

View File

@@ -234,7 +234,7 @@ func vfsSync(ctx context.Context, mod api.Module, pFile uint32, flags SyncFlag)
func vfsFileSize(ctx context.Context, mod api.Module, pFile, pSize uint32) _ErrorCode {
file := vfsFileGet(ctx, mod, pFile)
size, err := file.FileSize()
size, err := file.Size()
util.WriteUint64(mod, pSize, uint64(size))
return vfsErrorCode(err, _IOERR_SEEK)
}

View File

@@ -23,6 +23,18 @@ func TestConn_Open_dir(t *testing.T) {
}
}
func TestConn_Open_notfound(t *testing.T) {
t.Parallel()
_, err := sqlite3.OpenFlags("test.db", sqlite3.OPEN_READONLY)
if err == nil {
t.Fatal("want error")
}
if !errors.Is(err, sqlite3.CANTOPEN) {
t.Errorf("got %v, want sqlite3.CANTOPEN", err)
}
}
func TestConn_Close(t *testing.T) {
var conn *sqlite3.Conn
conn.Close()