mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-12 05:59:14 +00:00
Refactor extensions.
This commit is contained in:
36
ext/csv/arg.go
Normal file
36
ext/csv/arg.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package csv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/vtabutil"
|
||||
)
|
||||
|
||||
func uintArg(key, val string) (int, error) {
|
||||
i, err := strconv.ParseUint(val, 10, 15)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("csv: invalid %q parameter: %s", key, val)
|
||||
}
|
||||
return int(i), nil
|
||||
}
|
||||
|
||||
func boolArg(key, val string) (bool, error) {
|
||||
if val == "" {
|
||||
return true, nil
|
||||
}
|
||||
b, ok := util.ParseBool(val)
|
||||
if ok {
|
||||
return b, nil
|
||||
}
|
||||
return false, fmt.Errorf("csv: invalid %q parameter: %s", key, val)
|
||||
}
|
||||
|
||||
func runeArg(key, val string) (rune, error) {
|
||||
r, _, tail, err := strconv.UnquoteChar(vtabutil.Unquote(val), 0)
|
||||
if tail != "" || err != nil {
|
||||
return 0, fmt.Errorf("csv: invalid %q parameter: %s", key, val)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
package csv
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
func Test_uintParam(t *testing.T) {
|
||||
"github.com/ncruces/go-sqlite3/util/vtabutil"
|
||||
)
|
||||
|
||||
func Test_uintArg(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
@@ -20,22 +24,22 @@ func Test_uintParam(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.arg, func(t *testing.T) {
|
||||
key, val := getParam(tt.arg)
|
||||
key, val := vtabutil.NamedArg(tt.arg)
|
||||
if key != tt.key {
|
||||
t.Errorf("getParam() %v, want err %v", key, tt.key)
|
||||
t.Errorf("NamedArg() %v, want err %v", key, tt.key)
|
||||
}
|
||||
got, err := uintParam(key, val)
|
||||
got, err := uintArg(key, val)
|
||||
if (err != nil) != tt.err {
|
||||
t.Fatalf("uintParam() error = %v, want err %v", err, tt.err)
|
||||
t.Fatalf("uintArg() error = %v, want err %v", err, tt.err)
|
||||
}
|
||||
if got != tt.val {
|
||||
t.Errorf("uintParam() = %v, want %v", got, tt.val)
|
||||
t.Errorf("uintArg() = %v, want %v", got, tt.val)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_boolParam(t *testing.T) {
|
||||
func Test_boolArg(t *testing.T) {
|
||||
tests := []struct {
|
||||
arg string
|
||||
key string
|
||||
@@ -56,22 +60,22 @@ func Test_boolParam(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.arg, func(t *testing.T) {
|
||||
key, val := getParam(tt.arg)
|
||||
key, val := vtabutil.NamedArg(tt.arg)
|
||||
if key != tt.key {
|
||||
t.Errorf("getParam() %v, want err %v", key, tt.key)
|
||||
t.Errorf("NamedArg() %v, want err %v", key, tt.key)
|
||||
}
|
||||
got, err := boolParam(key, val)
|
||||
got, err := boolArg(key, val)
|
||||
if (err != nil) != tt.err {
|
||||
t.Fatalf("boolParam() error = %v, want err %v", err, tt.err)
|
||||
t.Fatalf("boolArg() error = %v, want err %v", err, tt.err)
|
||||
}
|
||||
if got != tt.val {
|
||||
t.Errorf("boolParam() = %v, want %v", got, tt.val)
|
||||
t.Errorf("boolArg() = %v, want %v", got, tt.val)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_runeParam(t *testing.T) {
|
||||
func Test_runeArg(t *testing.T) {
|
||||
tests := []struct {
|
||||
arg string
|
||||
key string
|
||||
@@ -88,16 +92,16 @@ func Test_runeParam(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.arg, func(t *testing.T) {
|
||||
key, val := getParam(tt.arg)
|
||||
key, val := vtabutil.NamedArg(tt.arg)
|
||||
if key != tt.key {
|
||||
t.Errorf("getParam() %v, want err %v", key, tt.key)
|
||||
t.Errorf("NamedArg() %v, want err %v", key, tt.key)
|
||||
}
|
||||
got, err := runeParam(key, val)
|
||||
got, err := runeArg(key, val)
|
||||
if (err != nil) != tt.err {
|
||||
t.Fatalf("runeParam() error = %v, want err %v", err, tt.err)
|
||||
t.Fatalf("runeArg() error = %v, want err %v", err, tt.err)
|
||||
}
|
||||
if got != tt.val {
|
||||
t.Errorf("runeParam() = %v, want %v", got, tt.val)
|
||||
t.Errorf("runeArg() = %v, want %v", got, tt.val)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -15,13 +15,14 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/fsutil"
|
||||
"github.com/ncruces/go-sqlite3/util/vtabutil"
|
||||
)
|
||||
|
||||
// Register registers the CSV virtual table.
|
||||
// If a filename is specified, [os.Open] is used to open the file.
|
||||
func Register(db *sqlite3.Conn) {
|
||||
RegisterFS(db, util.OSFS{})
|
||||
RegisterFS(db, fsutil.OSFS{})
|
||||
}
|
||||
|
||||
// RegisterFS registers the CSV virtual table.
|
||||
@@ -40,23 +41,23 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) {
|
||||
)
|
||||
|
||||
for _, arg := range arg {
|
||||
key, val := getParam(arg)
|
||||
key, val := vtabutil.NamedArg(arg)
|
||||
if _, ok := done[key]; ok {
|
||||
return nil, fmt.Errorf("csv: more than one %q parameter", key)
|
||||
}
|
||||
switch key {
|
||||
case "filename":
|
||||
filename = unquoteParam(val)
|
||||
filename = vtabutil.Unquote(val)
|
||||
case "data":
|
||||
data = unquoteParam(val)
|
||||
data = vtabutil.Unquote(val)
|
||||
case "schema":
|
||||
schema = unquoteParam(val)
|
||||
schema = vtabutil.Unquote(val)
|
||||
case "header":
|
||||
header, err = boolParam(key, val)
|
||||
header, err = boolArg(key, val)
|
||||
case "columns":
|
||||
columns, err = uintParam(key, val)
|
||||
columns, err = uintArg(key, val)
|
||||
case "comma":
|
||||
comma, err = runeParam(key, val)
|
||||
comma, err = runeArg(key, val)
|
||||
default:
|
||||
return nil, fmt.Errorf("csv: unknown %q parameter", key)
|
||||
}
|
||||
@@ -81,8 +82,8 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) {
|
||||
if schema == "" {
|
||||
var row []string
|
||||
if header || columns < 0 {
|
||||
csv, close, err := table.newReader()
|
||||
defer close.Close()
|
||||
csv, c, err := table.newReader()
|
||||
defer c.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,13 +134,11 @@ func (t *table) Integrity(schema, table string, flags int) error {
|
||||
if flags&1 != 0 {
|
||||
return nil
|
||||
}
|
||||
csv, close, err := t.newReader()
|
||||
csv, c, err := t.newReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if close != nil {
|
||||
defer close.Close()
|
||||
}
|
||||
defer c.Close()
|
||||
_, err = csv.ReadAll()
|
||||
return err
|
||||
}
|
||||
@@ -176,20 +175,28 @@ func (t *table) newReader() (*csv.Reader, io.Closer, error) {
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
table *table
|
||||
close io.Closer
|
||||
csv *csv.Reader
|
||||
row []string
|
||||
rowID int64
|
||||
table *table
|
||||
closer io.Closer
|
||||
csv *csv.Reader
|
||||
row []string
|
||||
rowID int64
|
||||
}
|
||||
|
||||
func (c *cursor) Close() error {
|
||||
return c.close.Close()
|
||||
func (c *cursor) Close() (err error) {
|
||||
if c.closer != nil {
|
||||
err = c.closer.Close()
|
||||
c.closer = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *cursor) Filter(idxNum int, idxStr string, arg ...sqlite3.Value) error {
|
||||
var err error
|
||||
c.csv, c.close, err = c.table.newReader()
|
||||
err := c.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.csv, c.closer, err = c.table.newReader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package csv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func getParam(arg string) (key, val string) {
|
||||
key, val, _ = strings.Cut(arg, "=")
|
||||
key = strings.TrimSpace(key)
|
||||
val = strings.TrimSpace(val)
|
||||
return
|
||||
}
|
||||
|
||||
func uintParam(key, val string) (int, error) {
|
||||
i, err := strconv.ParseUint(val, 10, 15)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("csv: invalid %q parameter: %s", key, val)
|
||||
}
|
||||
return int(i), nil
|
||||
}
|
||||
|
||||
func boolParam(key, val string) (bool, error) {
|
||||
if val == "" || val == "1" ||
|
||||
strings.EqualFold(val, "true") ||
|
||||
strings.EqualFold(val, "yes") ||
|
||||
strings.EqualFold(val, "on") {
|
||||
return true, nil
|
||||
}
|
||||
if val == "0" ||
|
||||
strings.EqualFold(val, "false") ||
|
||||
strings.EqualFold(val, "no") ||
|
||||
strings.EqualFold(val, "off") {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("csv: invalid %q parameter: %s", key, val)
|
||||
}
|
||||
|
||||
func runeParam(key, val string) (rune, error) {
|
||||
r, _, tail, err := strconv.UnquoteChar(unquoteParam(val), 0)
|
||||
if tail != "" || err != nil {
|
||||
return 0, fmt.Errorf("csv: invalid %q parameter: %s", key, val)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func unquoteParam(val string) string {
|
||||
if len(val) < 2 {
|
||||
return val
|
||||
}
|
||||
if val[0] != val[len(val)-1] {
|
||||
return val
|
||||
}
|
||||
var old, new string
|
||||
switch val[0] {
|
||||
default:
|
||||
return val
|
||||
case '"':
|
||||
old, new = `""`, `"`
|
||||
case '\'':
|
||||
old, new = `''`, `'`
|
||||
}
|
||||
return strings.ReplaceAll(val[1:len(val)-1], old, new)
|
||||
}
|
||||
@@ -13,13 +13,13 @@ import (
|
||||
)
|
||||
|
||||
// Register registers SQL functions readfile, writefile, lsmode,
|
||||
// and the eponymous virtual table fsdir.
|
||||
// and the table-valued function fsdir.
|
||||
func Register(db *sqlite3.Conn) {
|
||||
RegisterFS(db, nil)
|
||||
}
|
||||
|
||||
// Register registers SQL functions readfile, lsmode,
|
||||
// and the eponymous virtual table fsdir;
|
||||
// and the table-valued function fsdir;
|
||||
// fsys will be used to read files and list directories.
|
||||
func RegisterFS(db *sqlite3.Conn, fsys fs.FS) {
|
||||
db.CreateFunction("lsmode", 1, 0, lsmode)
|
||||
@@ -27,8 +27,8 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) {
|
||||
if fsys == 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)`)
|
||||
sqlite3.CreateModule(db, "fsdir", nil, func(db *sqlite3.Conn, _, _, _ string, _ ...string) (fsdir, error) {
|
||||
err := db.DeclareVtab(`CREATE TABLE x(name,mode,mtime TIMESTAMP,data,path HIDDEN,dir HIDDEN)`)
|
||||
db.VtabConfig(sqlite3.VTAB_DIRECTONLY)
|
||||
return fsdir{fsys}, err
|
||||
})
|
||||
|
||||
@@ -48,11 +48,11 @@ func (d fsdir) BestIndex(idx *sqlite3.IndexInfo) error {
|
||||
}
|
||||
|
||||
func (d fsdir) Open() (sqlite3.VTabCursor, error) {
|
||||
return &cursor{fsys: d.fsys}, nil
|
||||
return &cursor{fsdir: d}, nil
|
||||
}
|
||||
|
||||
type cursor struct {
|
||||
fsys fs.FS
|
||||
fsdir
|
||||
curr entry
|
||||
next chan entry
|
||||
done chan struct{}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/fsutil"
|
||||
)
|
||||
|
||||
func writefile(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
@@ -22,7 +23,7 @@ func writefile(ctx sqlite3.Context, arg ...sqlite3.Value) {
|
||||
|
||||
var mode fs.FileMode
|
||||
if len(arg) > 2 {
|
||||
mode = fixMode(fs.FileMode(arg[2].Int()))
|
||||
mode = fsutil.FileModeFromValue(arg[2])
|
||||
}
|
||||
|
||||
n, err := createFileAndDir(file, mode, arg[1])
|
||||
@@ -88,40 +89,6 @@ func createFile(path string, mode fs.FileMode, data sqlite3.Value) (int, error)
|
||||
return 0, fmt.Errorf("invalid mode: %v", mode)
|
||||
}
|
||||
|
||||
func fixMode(mode fs.FileMode) fs.FileMode {
|
||||
const (
|
||||
S_IFMT fs.FileMode = 0170000
|
||||
S_IFIFO fs.FileMode = 0010000
|
||||
S_IFCHR fs.FileMode = 0020000
|
||||
S_IFDIR fs.FileMode = 0040000
|
||||
S_IFBLK fs.FileMode = 0060000
|
||||
S_IFREG fs.FileMode = 0100000
|
||||
S_IFLNK fs.FileMode = 0120000
|
||||
S_IFSOCK fs.FileMode = 0140000
|
||||
)
|
||||
|
||||
switch mode & S_IFMT {
|
||||
case S_IFDIR:
|
||||
mode |= fs.ModeDir
|
||||
case S_IFLNK:
|
||||
mode |= fs.ModeSymlink
|
||||
case S_IFBLK:
|
||||
mode |= fs.ModeDevice
|
||||
case S_IFCHR:
|
||||
mode |= fs.ModeCharDevice | fs.ModeDevice
|
||||
case S_IFIFO:
|
||||
mode |= fs.ModeNamedPipe
|
||||
case S_IFSOCK:
|
||||
mode |= fs.ModeSocket
|
||||
case S_IFREG, 0:
|
||||
//
|
||||
default:
|
||||
mode |= fs.ModeIrregular
|
||||
}
|
||||
|
||||
return mode &^ S_IFMT
|
||||
}
|
||||
|
||||
func fixPerm(mode fs.FileMode, def fs.FileMode) fs.FileMode {
|
||||
if mode.Perm() == 0 {
|
||||
return def
|
||||
|
||||
@@ -56,7 +56,7 @@ func Test_writefile(t *testing.T) {
|
||||
var mode fs.FileMode
|
||||
var mtime time.Time
|
||||
var data sql.NullString
|
||||
err := rows.Scan(&name, &mode, sqlite3.TimeFormatUnixFrac.Scanner(&mtime), &data)
|
||||
err := rows.Scan(&name, &mode, &mtime, &data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -90,26 +90,3 @@ func Test_writefile(t *testing.T) {
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_fixMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
mode fs.FileMode
|
||||
want fs.FileMode
|
||||
}{
|
||||
{0010754, 0754 | fs.ModeNamedPipe},
|
||||
{0020754, 0754 | fs.ModeCharDevice | fs.ModeDevice},
|
||||
{0040754, 0754 | fs.ModeDir},
|
||||
{0060754, 0754 | fs.ModeDevice},
|
||||
{0100754, 0754},
|
||||
{0120754, 0754 | fs.ModeSymlink},
|
||||
{0140754, 0754 | fs.ModeSocket},
|
||||
{0170754, 0754 | fs.ModeIrregular},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.mode.String(), func(t *testing.T) {
|
||||
if got := fixMode(tt.mode); got != tt.want {
|
||||
t.Errorf("fixMode() = %o, want %o", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,20 +18,20 @@ import (
|
||||
"io/fs"
|
||||
|
||||
"github.com/ncruces/go-sqlite3"
|
||||
"github.com/ncruces/go-sqlite3/internal/util"
|
||||
"github.com/ncruces/go-sqlite3/util/fsutil"
|
||||
)
|
||||
|
||||
// Register registers the lines and lines_read virtual tables.
|
||||
// The lines virtual table reads from a database blob or text.
|
||||
// The lines_read virtual table reads from a file or an [io.Reader].
|
||||
// Register registers the lines and lines_read table-valued functions.
|
||||
// The lines function reads from a database blob or text.
|
||||
// The lines_read function reads from a file or an [io.Reader].
|
||||
// If a filename is specified, [os.Open] is used to open the file.
|
||||
func Register(db *sqlite3.Conn) {
|
||||
RegisterFS(db, util.OSFS{})
|
||||
RegisterFS(db, fsutil.OSFS{})
|
||||
}
|
||||
|
||||
// RegisterFS registers the lines and lines_read virtual tables.
|
||||
// The lines virtual table reads from a database blob or text.
|
||||
// The lines_read virtual table reads from a file or an [io.Reader].
|
||||
// RegisterFS registers the lines and lines_read table-valued functions.
|
||||
// The lines function reads from a database blob or text.
|
||||
// The lines_read function reads from a file or an [io.Reader].
|
||||
// If a filename is specified, fsys is used to open the file.
|
||||
func RegisterFS(db *sqlite3.Conn, fsys fs.FS) {
|
||||
sqlite3.CreateModule[lines](db, "lines", nil,
|
||||
|
||||
Reference in New Issue
Block a user