More fileio.

This commit is contained in:
Nuno Cruces
2023-12-12 01:00:13 +00:00
parent c99fbcea6f
commit 6e8d5e5be6
7 changed files with 77 additions and 47 deletions

View File

@@ -9,3 +9,7 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

View File

@@ -58,7 +58,6 @@ jobs:
with:
chart: true
amend: true
reuse-go: true
if: |
github.event_name == 'push' &&
matrix.os == 'ubuntu-latest'

View File

@@ -180,6 +180,7 @@ const (
DIRECTONLY FunctionFlag = 0x000080000
SUBTYPE FunctionFlag = 0x000100000
INNOCUOUS FunctionFlag = 0x000200000
RESULT_SUBTYPE FunctionFlag = 0x001000000
)
// StmtStatus name counter values associated with the [Stmt.Status] method.

View File

@@ -1,4 +1,6 @@
// Package array provides the array table-valued SQL function.
//
// https://sqlite.org/carray.html
package array
import (
@@ -11,8 +13,6 @@ import (
// Register registers the array single-argument, table-valued SQL function.
// The argument must be an [sqlite3.Pointer] to a Go slice or array
// of ints, floats, bools, strings or blobs.
//
// https://sqlite.org/carray.html
func Register(db *sqlite3.Conn) {
sqlite3.CreateModule[array](db, "array", nil,
func(db *sqlite3.Conn, _, _, _ string, _ ...string) (array, error) {

View File

@@ -1,3 +1,6 @@
// Package fileio provides SQL functions to read and write files.
//
// https://sqlite.org/src/doc/tip/ext/misc/fileio.c
package fileio
import (
@@ -9,14 +12,25 @@ import (
"github.com/ncruces/go-sqlite3"
)
// Register registers SQL functions readfile, writefile, lsmode,
// and the eponymous virtual table fsdir.
func Register(db *sqlite3.Conn) {
RegisterFS(db, nil)
}
// Register registers SQL functions readfile, lsmode,
// and the eponymous virtual table fsdir;
// fs will be used to read files and list directories.
func RegisterFS(db *sqlite3.Conn, fs fs.FS) {
db.CreateFunction("lsmode", 1, 0, lsmode)
db.CreateFunction("readfile", 1, sqlite3.DIRECTONLY, readfile)
db.CreateFunction("readfile", 1, sqlite3.DIRECTONLY, readfile(fs))
if fs == nil {
db.CreateFunction("writefile", -1, sqlite3.DIRECTONLY, writefile)
}
sqlite3.CreateModule(db, "fsdir", nil, func(db *sqlite3.Conn, module, schema, table string, arg ...string) (fsdir, error) {
err := db.DeclareVtab(`CREATE TABLE x(name,mode,mtime,data,path HIDDEN,dir HIDDEN)`)
db.VtabConfig(sqlite3.VTAB_DIRECTONLY)
return fsdir{}, err
return fsdir{fs}, err
})
}
@@ -24,14 +38,22 @@ func lsmode(ctx sqlite3.Context, arg ...sqlite3.Value) {
ctx.ResultText(fs.FileMode(arg[0].Int()).String())
}
func readfile(ctx sqlite3.Context, arg ...sqlite3.Value) {
d, err := os.ReadFile(arg[0].Text())
if errors.Is(err, fs.ErrNotExist) {
return
}
if err != nil {
ctx.ResultError(fmt.Errorf("readfile: %w", err))
func readfile(f fs.FS) func(ctx sqlite3.Context, arg ...sqlite3.Value) {
return func(ctx sqlite3.Context, arg ...sqlite3.Value) {
var err error
var data []byte
if f != nil {
data, err = fs.ReadFile(f, arg[0].Text())
} else {
ctx.ResultBlob(d)
data, err = os.ReadFile(arg[0].Text())
}
switch {
case err == nil:
ctx.ResultBlob(data)
case !errors.Is(err, fs.ErrNotExist):
ctx.ResultError(fmt.Errorf("readfile: %w", err))
}
}
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
@@ -13,10 +14,10 @@ import (
type fsdir struct{ fs.FS }
func (d fsdir) BestIndex(idx *sqlite3.IndexInfo) error {
var path, dir bool
var root, base bool
for i, cst := range idx.Constraint {
switch cst.Column {
case 4: // path
case 4: // root
if !cst.Usable || cst.Op != sqlite3.INDEX_CONSTRAINT_EQ {
return sqlite3.CONSTRAINT
}
@@ -24,8 +25,8 @@ func (d fsdir) BestIndex(idx *sqlite3.IndexInfo) error {
Omit: true,
ArgvIndex: 1,
}
path = true
case 5: // dir
root = true
case 5: // base
if !cst.Usable || cst.Op != sqlite3.INDEX_CONSTRAINT_EQ {
return sqlite3.CONSTRAINT
}
@@ -33,13 +34,13 @@ func (d fsdir) BestIndex(idx *sqlite3.IndexInfo) error {
Omit: true,
ArgvIndex: 2,
}
dir = true
base = true
}
}
if path {
if root {
idx.EstimatedCost = 100
}
if dir {
if base {
idx.EstimatedCost = 10
}
return nil
@@ -51,7 +52,7 @@ func (d fsdir) Open() (sqlite3.VTabCursor, error) {
type cursor struct {
fs fs.FS
dir string
base string
rowID int64
eof bool
curr entry
@@ -61,7 +62,7 @@ type cursor struct {
type entry struct {
path string
entry fs.DirEntry
fs.DirEntry
err error
}
@@ -84,21 +85,24 @@ func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
return fmt.Errorf("fsdir: wrong number of arguments")
}
path := arg[0].Text()
root := arg[0].Text()
if len(arg) > 1 {
if dir := arg[1].RawText(); c.fs != nil {
c.dir = string(dir) + "/"
base := arg[1].Text()
if c.fs != nil {
root = path.Join(base, root)
base = path.Clean(base) + "/"
} else {
c.dir = string(dir) + string(filepath.Separator)
root = filepath.Join(base, root)
base = filepath.Clean(base) + string(filepath.Separator)
}
path = c.dir + path
c.base = base
}
c.rowID = 0
c.eof = false
c.next = make(chan entry)
c.done = make(chan struct{})
go c.WalkDir(path)
go c.WalkDir(root)
return c.Next()
}
@@ -121,32 +125,32 @@ func (c *cursor) RowID() (int64, error) {
func (c *cursor) Column(ctx *sqlite3.Context, n int) error {
switch n {
case 0: // name
name := strings.TrimPrefix(c.curr.path, c.dir)
name := strings.TrimPrefix(c.curr.path, c.base)
ctx.ResultText(name)
case 1: // mode
i, err := c.curr.entry.Info()
i, err := c.curr.Info()
if err != nil {
return err
}
ctx.ResultInt64(int64(i.Mode()))
case 2: // mtime
i, err := c.curr.entry.Info()
i, err := c.curr.Info()
if err != nil {
return err
}
ctx.ResultTime(i.ModTime(), sqlite3.TimeFormatUnixFrac)
case 3: // data
switch typ := c.curr.entry.Type(); {
switch typ := c.curr.Type(); {
case typ.IsRegular():
var data []byte
var err error
if name := c.curr.entry.Name(); c.fs != nil {
data, err = fs.ReadFile(c.fs, name)
if c.fs != nil {
data, err = fs.ReadFile(c.fs, c.curr.path)
} else {
data, err = os.ReadFile(name)
data, err = os.ReadFile(c.curr.path)
}
if err != nil {
return err
@@ -154,7 +158,7 @@ func (c *cursor) Column(ctx *sqlite3.Context, n int) error {
ctx.ResultBlob(data)
case typ&fs.ModeSymlink != 0 && c.fs == nil:
t, err := os.Readlink(c.curr.entry.Name())
t, err := os.Readlink(c.curr.path)
if err != nil {
return err
}
@@ -188,11 +192,11 @@ func (c *cursor) WalkDir(path string) {
}
}
func (c *cursor) WalkDirFunc(path string, de fs.DirEntry, err error) error {
func (c *cursor) WalkDirFunc(path string, d fs.DirEntry, err error) error {
select {
case <-c.done:
return fs.SkipAll
case c.next <- entry{path, de, err}:
case c.next <- entry{path, d, err}:
return nil
}
}

View File

@@ -1,6 +1,6 @@
# Go `"memdb"` SQLite VFS
This package implements the [`"memdb"`](https://sqlite.org/src/file/src/memdb.c)
This package implements the [`"memdb"`](https://sqlite.org/src/doc/tip/src/memdb.c)
SQLite VFS in pure Go.
It has some benefits over the C version: