mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 05:59:14 +00:00
More fileio.
This commit is contained in:
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user