diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b444581..1dff6a2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -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" \ No newline at end of file diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 30ece3c..29540be 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -58,7 +58,6 @@ jobs: with: chart: true amend: true - reuse-go: true if: | github.event_name == 'push' && matrix.os == 'ubuntu-latest' diff --git a/const.go b/const.go index 5c46934..01c3451 100644 --- a/const.go +++ b/const.go @@ -176,10 +176,11 @@ const ( type FunctionFlag uint32 const ( - DETERMINISTIC FunctionFlag = 0x000000800 - DIRECTONLY FunctionFlag = 0x000080000 - SUBTYPE FunctionFlag = 0x000100000 - INNOCUOUS FunctionFlag = 0x000200000 + DETERMINISTIC FunctionFlag = 0x000000800 + DIRECTONLY FunctionFlag = 0x000080000 + SUBTYPE FunctionFlag = 0x000100000 + INNOCUOUS FunctionFlag = 0x000200000 + RESULT_SUBTYPE FunctionFlag = 0x001000000 ) // StmtStatus name counter values associated with the [Stmt.Status] method. diff --git a/ext/array/array.go b/ext/array/array.go index 41eced8..a43c24d 100644 --- a/ext/array/array.go +++ b/ext/array/array.go @@ -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) { diff --git a/ext/fileio/fileio.go b/ext/fileio/fileio.go index 652d22c..311dc9d 100644 --- a/ext/fileio/fileio.go +++ b/ext/fileio/fileio.go @@ -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("writefile", -1, sqlite3.DIRECTONLY, writefile) + 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)) - } else { - ctx.ResultBlob(d) +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 { + 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)) + } } } diff --git a/ext/fileio/fsdir.go b/ext/fileio/fsdir.go index e4e331b..d8798fc 100644 --- a/ext/fileio/fsdir.go +++ b/ext/fileio/fsdir.go @@ -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 @@ -60,9 +61,9 @@ type cursor struct { } type entry struct { - path string - entry fs.DirEntry - err error + path string + fs.DirEntry + err error } func (c *cursor) Close() 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 } } diff --git a/vfs/memdb/README.md b/vfs/memdb/README.md index 07ed91c..193e29d 100644 --- a/vfs/memdb/README.md +++ b/vfs/memdb/README.md @@ -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: