mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-11 21:49:13 +00:00
ReaderVFS.
This commit is contained in:
@@ -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
1
go.mod
@@ -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
2
go.sum
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
118
sqlite3vfs/reader.go
Normal 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
60
sqlite3vfs/reader_test.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user