diff --git a/ext/closure/closure.go b/ext/closure/closure.go index 2fcb3ef..43e911e 100644 --- a/ext/closure/closure.go +++ b/ext/closure/closure.go @@ -12,7 +12,7 @@ import ( "github.com/ncruces/go-sqlite3" "github.com/ncruces/go-sqlite3/internal/util" - "github.com/ncruces/go-sqlite3/util/vtabutil" + "github.com/ncruces/go-sqlite3/util/sql3util" ) const ( @@ -39,17 +39,17 @@ func Register(db *sqlite3.Conn) error { ) for _, arg := range arg { - key, val := vtabutil.NamedArg(arg) + key, val := sql3util.NamedArg(arg) if done.Contains(key) { return nil, fmt.Errorf("transitive_closure: more than one %q parameter", key) } switch key { case "tablename": - table = vtabutil.Unquote(val) + table = sql3util.Unquote(val) case "idcolumn": - column = vtabutil.Unquote(val) + column = sql3util.Unquote(val) case "parentcolumn": - parent = vtabutil.Unquote(val) + parent = sql3util.Unquote(val) default: return nil, fmt.Errorf("transitive_closure: unknown %q parameter", key) } diff --git a/ext/csv/arg.go b/ext/csv/arg.go index 247dfef..3b8b9a7 100644 --- a/ext/csv/arg.go +++ b/ext/csv/arg.go @@ -4,8 +4,7 @@ import ( "fmt" "strconv" - "github.com/ncruces/go-sqlite3/internal/util" - "github.com/ncruces/go-sqlite3/util/vtabutil" + "github.com/ncruces/go-sqlite3/util/sql3util" ) func uintArg(key, val string) (int, error) { @@ -20,7 +19,7 @@ func boolArg(key, val string) (bool, error) { if val == "" { return true, nil } - b, ok := util.ParseBool(val) + b, ok := sql3util.ParseBool(val) if ok { return b, nil } @@ -28,7 +27,7 @@ func boolArg(key, val string) (bool, error) { } func runeArg(key, val string) (rune, error) { - r, _, tail, err := strconv.UnquoteChar(vtabutil.Unquote(val), 0) + r, _, tail, err := strconv.UnquoteChar(sql3util.Unquote(val), 0) if tail != "" || err != nil { return 0, fmt.Errorf("csv: invalid %q parameter: %s", key, val) } diff --git a/ext/csv/arg_test.go b/ext/csv/arg_test.go index 6d009b8..c1a0a42 100644 --- a/ext/csv/arg_test.go +++ b/ext/csv/arg_test.go @@ -3,7 +3,7 @@ package csv import ( "testing" - "github.com/ncruces/go-sqlite3/util/vtabutil" + "github.com/ncruces/go-sqlite3/util/sql3util" ) func Test_uintArg(t *testing.T) { @@ -24,7 +24,7 @@ func Test_uintArg(t *testing.T) { } for _, tt := range tests { t.Run(tt.arg, func(t *testing.T) { - key, val := vtabutil.NamedArg(tt.arg) + key, val := sql3util.NamedArg(tt.arg) if key != tt.key { t.Errorf("NamedArg() %v, want err %v", key, tt.key) } @@ -62,7 +62,7 @@ func Test_boolArg(t *testing.T) { } for _, tt := range tests { t.Run(tt.arg, func(t *testing.T) { - key, val := vtabutil.NamedArg(tt.arg) + key, val := sql3util.NamedArg(tt.arg) if key != tt.key { t.Errorf("NamedArg() %v, want err %v", key, tt.key) } @@ -96,7 +96,7 @@ func Test_runeArg(t *testing.T) { } for _, tt := range tests { t.Run(tt.arg, func(t *testing.T) { - key, val := vtabutil.NamedArg(tt.arg) + key, val := sql3util.NamedArg(tt.arg) if key != tt.key { t.Errorf("NamedArg() %v, want err %v", key, tt.key) } diff --git a/ext/csv/csv.go b/ext/csv/csv.go index 83feaf9..20d4a84 100644 --- a/ext/csv/csv.go +++ b/ext/csv/csv.go @@ -18,7 +18,7 @@ import ( "github.com/ncruces/go-sqlite3" "github.com/ncruces/go-sqlite3/internal/util" "github.com/ncruces/go-sqlite3/util/osutil" - "github.com/ncruces/go-sqlite3/util/vtabutil" + "github.com/ncruces/go-sqlite3/util/sql3util" ) // Register registers the CSV virtual table. @@ -44,17 +44,17 @@ func RegisterFS(db *sqlite3.Conn, fsys fs.FS) error { ) for _, arg := range arg { - key, val := vtabutil.NamedArg(arg) + key, val := sql3util.NamedArg(arg) if done.Contains(key) { return nil, fmt.Errorf("csv: more than one %q parameter", key) } switch key { case "filename": - filename = vtabutil.Unquote(val) + filename = sql3util.Unquote(val) case "data": - data = vtabutil.Unquote(val) + data = sql3util.Unquote(val) case "schema": - schema = vtabutil.Unquote(val) + schema = sql3util.Unquote(val) case "header": header, err = boolArg(key, val) case "columns": diff --git a/ext/csv/types.go b/ext/csv/types.go index 034982e..f0fd6d6 100644 --- a/ext/csv/types.go +++ b/ext/csv/types.go @@ -3,7 +3,7 @@ package csv import ( "strings" - "github.com/ncruces/go-sqlite3/util/vtabutil" + "github.com/ncruces/go-sqlite3/util/sql3util" ) type affinity byte @@ -17,7 +17,7 @@ const ( ) func getColumnAffinities(schema string) ([]affinity, error) { - tab, err := vtabutil.Parse(schema) + tab, err := sql3util.ParseTable(schema) if err != nil { return nil, err } diff --git a/internal/util/bool.go b/internal/util/bool.go deleted file mode 100644 index 8427f30..0000000 --- a/internal/util/bool.go +++ /dev/null @@ -1,22 +0,0 @@ -package util - -import "strings" - -func ParseBool(s string) (b, ok bool) { - if len(s) == 0 { - return false, false - } - if s[0] == '0' { - return false, true - } - if '1' <= s[0] && s[0] <= '9' { - return true, true - } - switch strings.ToLower(s) { - case "true", "yes", "on": - return true, true - case "false", "no", "off": - return false, true - } - return false, false -} diff --git a/internal/util/bool_test.go b/internal/util/bool_test.go deleted file mode 100644 index b89b2e8..0000000 --- a/internal/util/bool_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package util - -import "testing" - -func TestParseBool(t *testing.T) { - tests := []struct { - str string - val bool - ok bool - }{ - {"", false, false}, - {"0", false, true}, - {"1", true, true}, - {"9", true, true}, - {"T", false, false}, - {"true", true, true}, - {"FALSE", false, true}, - {"false?", false, false}, - } - for _, tt := range tests { - t.Run(tt.str, func(t *testing.T) { - gotVal, gotOK := ParseBool(tt.str) - if gotVal != tt.val || gotOK != tt.ok { - t.Errorf("ParseBool(%q) = (%v, %v) want (%v, %v)", tt.str, gotVal, gotOK, tt.val, tt.ok) - } - }) - } -} diff --git a/tests/db_test.go b/tests/db_test.go index a10f01b..e002151 100644 --- a/tests/db_test.go +++ b/tests/db_test.go @@ -1,19 +1,17 @@ package tests import ( + _ "embed" "os" "path/filepath" "testing" - _ "embed" - "github.com/ncruces/go-sqlite3" _ "github.com/ncruces/go-sqlite3/embed" _ "github.com/ncruces/go-sqlite3/internal/testcfg" "github.com/ncruces/go-sqlite3/vfs" _ "github.com/ncruces/go-sqlite3/vfs/adiantum" "github.com/ncruces/go-sqlite3/vfs/memdb" - _ "github.com/ncruces/go-sqlite3/vfs/memdb" _ "github.com/ncruces/go-sqlite3/vfs/xts" ) diff --git a/util/vtabutil/README.md b/util/sql3util/README.md similarity index 55% rename from util/vtabutil/README.md rename to util/sql3util/README.md index 7653ae0..9f47f5a 100644 --- a/util/vtabutil/README.md +++ b/util/sql3util/README.md @@ -1,6 +1,7 @@ -# Virtual Table utility functions +# SQLite utility functions -This package implements utilities mostly useful to virtual table implementations. +This package implements assorted SQLite utilities +useful to extension writers. It also wraps a [parser](https://github.com/marcobambini/sqlite-createtable-parser) for the [`CREATE`](https://sqlite.org/lang_createtable.html) and diff --git a/util/vtabutil/arg.go b/util/sql3util/arg.go similarity index 51% rename from util/vtabutil/arg.go rename to util/sql3util/arg.go index 566c730..b75f7dd 100644 --- a/util/vtabutil/arg.go +++ b/util/sql3util/arg.go @@ -1,4 +1,4 @@ -package vtabutil +package sql3util import "strings" @@ -17,19 +17,44 @@ func Unquote(val string) string { if len(val) < 2 { return val } - if val[0] != val[len(val)-1] { + fst := val[0] + lst := val[len(val)-1] + rst := val[1 : len(val)-1] + if fst == '[' && lst == ']' { + return rst + } + if fst != lst { return val } var old, new string - switch val[0] { + switch fst { default: return val - case '"': - old, new = `""`, `"` case '`': old, new = "``", "`" + case '"': + old, new = `""`, `"` case '\'': old, new = `''`, `'` } - return strings.ReplaceAll(val[1:len(val)-1], old, new) + return strings.ReplaceAll(rst, old, new) +} + +func ParseBool(s string) (b, ok bool) { + if len(s) == 0 { + return false, false + } + if s[0] == '0' { + return false, true + } + if '1' <= s[0] && s[0] <= '9' { + return true, true + } + switch strings.ToLower(s) { + case "true", "yes", "on": + return true, true + case "false", "no", "off": + return false, true + } + return false, false } diff --git a/util/sql3util/arg_test.go b/util/sql3util/arg_test.go new file mode 100644 index 0000000..fcd34c0 --- /dev/null +++ b/util/sql3util/arg_test.go @@ -0,0 +1,55 @@ +package sql3util_test + +import ( + "testing" + + "github.com/ncruces/go-sqlite3/util/sql3util" +) + +func TestUnquote(t *testing.T) { + tests := []struct { + val string + want string + }{ + {"a", "a"}, + {"abc", "abc"}, + {"abba", "abba"}, + {"`ab``c`", "ab`c"}, + {"'ab''c'", "ab'c"}, + {"'ab``c'", "ab``c"}, + {"[ab``c]", "ab``c"}, + {`"ab""c"`, `ab"c`}, + } + for _, tt := range tests { + t.Run(tt.val, func(t *testing.T) { + if got := sql3util.Unquote(tt.val); got != tt.want { + t.Errorf("Unquote(%s) = %s, want %s", tt.val, got, tt.want) + } + }) + } +} + +func TestParseBool(t *testing.T) { + tests := []struct { + str string + val bool + ok bool + }{ + {"", false, false}, + {"0", false, true}, + {"1", true, true}, + {"9", true, true}, + {"T", false, false}, + {"true", true, true}, + {"FALSE", false, true}, + {"false?", false, false}, + } + for _, tt := range tests { + t.Run(tt.str, func(t *testing.T) { + gotVal, gotOK := sql3util.ParseBool(tt.str) + if gotVal != tt.val || gotOK != tt.ok { + t.Errorf("ParseBool(%q) = (%v, %v) want (%v, %v)", tt.str, gotVal, gotOK, tt.val, tt.ok) + } + }) + } +} diff --git a/util/vtabutil/const.go b/util/sql3util/const.go similarity index 98% rename from util/vtabutil/const.go rename to util/sql3util/const.go index 291527f..10e8af3 100644 --- a/util/vtabutil/const.go +++ b/util/sql3util/const.go @@ -1,4 +1,4 @@ -package vtabutil +package sql3util const ( _NONE = iota diff --git a/util/vtabutil/parse.go b/util/sql3util/parse.go similarity index 97% rename from util/vtabutil/parse.go rename to util/sql3util/parse.go index 10a4a32..7326f7d 100644 --- a/util/vtabutil/parse.go +++ b/util/sql3util/parse.go @@ -1,10 +1,9 @@ -package vtabutil +package sql3util import ( "context" - "sync" - _ "embed" + "sync" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" @@ -25,11 +24,11 @@ var ( compiled wazero.CompiledModule ) -// Parse parses a [CREATE] or [ALTER TABLE] command. +// ParseTable parses a [CREATE] or [ALTER TABLE] command. // // [CREATE]: https://sqlite.org/lang_createtable.html // [ALTER TABLE]: https://sqlite.org/lang_altertable.html -func Parse(sql string) (_ *Table, err error) { +func ParseTable(sql string) (_ *Table, err error) { once.Do(func() { ctx := context.Background() cfg := wazero.NewRuntimeConfigInterpreter() diff --git a/util/vtabutil/parse/.gitignore b/util/sql3util/parse/.gitignore similarity index 100% rename from util/vtabutil/parse/.gitignore rename to util/sql3util/parse/.gitignore diff --git a/util/vtabutil/parse/build.sh b/util/sql3util/parse/build.sh similarity index 100% rename from util/vtabutil/parse/build.sh rename to util/sql3util/parse/build.sh diff --git a/util/vtabutil/parse/download.sh b/util/sql3util/parse/download.sh similarity index 100% rename from util/vtabutil/parse/download.sh rename to util/sql3util/parse/download.sh diff --git a/util/vtabutil/parse/main.c b/util/sql3util/parse/main.c similarity index 100% rename from util/vtabutil/parse/main.c rename to util/sql3util/parse/main.c diff --git a/util/vtabutil/parse/sql3parse_table.wasm b/util/sql3util/parse/sql3parse_table.wasm similarity index 100% rename from util/vtabutil/parse/sql3parse_table.wasm rename to util/sql3util/parse/sql3parse_table.wasm diff --git a/util/vtabutil/parse_test.go b/util/sql3util/parse_test.go similarity index 75% rename from util/vtabutil/parse_test.go rename to util/sql3util/parse_test.go index 0dd7776..c45f12a 100644 --- a/util/vtabutil/parse_test.go +++ b/util/sql3util/parse_test.go @@ -1,13 +1,13 @@ -package vtabutil_test +package sql3util_test import ( "testing" - "github.com/ncruces/go-sqlite3/util/vtabutil" + "github.com/ncruces/go-sqlite3/util/sql3util" ) func TestParse(t *testing.T) { - tab, err := vtabutil.Parse(`CREATE TABLE child(x REFERENCES parent)`) + tab, err := sql3util.ParseTable(`CREATE TABLE child(x REFERENCES parent)`) if err != nil { t.Fatal(err) } diff --git a/util/sql3util/sql3util.go b/util/sql3util/sql3util.go new file mode 100644 index 0000000..2c4bf83 --- /dev/null +++ b/util/sql3util/sql3util.go @@ -0,0 +1,2 @@ +// Package sql3util implements SQLite utilities. +package sql3util diff --git a/util/vfsutil/wrap.go b/util/vfsutil/wrap.go index 6cb53a6..72ac778 100644 --- a/util/vfsutil/wrap.go +++ b/util/vfsutil/wrap.go @@ -1,4 +1,4 @@ -// Package vtabutil implements virtual filesystem utilities. +// Package vfsutil implements virtual filesystem utilities. package vfsutil import ( diff --git a/util/vtabutil/vtabutil.go b/util/vtabutil/vtabutil.go deleted file mode 100644 index ce71e46..0000000 --- a/util/vtabutil/vtabutil.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package vtabutil implements virtual table utilities. -package vtabutil diff --git a/vfs/memdb/example_test.go b/vfs/memdb/example_test.go index ca15fbb..c7437cc 100644 --- a/vfs/memdb/example_test.go +++ b/vfs/memdb/example_test.go @@ -2,11 +2,10 @@ package memdb_test import ( "database/sql" + _ "embed" "fmt" "log" - _ "embed" - _ "github.com/ncruces/go-sqlite3/driver" _ "github.com/ncruces/go-sqlite3/embed" "github.com/ncruces/go-sqlite3/vfs/memdb" diff --git a/vfs/memdb/memdb_test.go b/vfs/memdb/memdb_test.go index 35ec244..31dc1ce 100644 --- a/vfs/memdb/memdb_test.go +++ b/vfs/memdb/memdb_test.go @@ -1,9 +1,8 @@ package memdb import ( - "testing" - _ "embed" + "testing" "github.com/ncruces/go-sqlite3" _ "github.com/ncruces/go-sqlite3/embed" diff --git a/vfs/readervfs/example_test.go b/vfs/readervfs/example_test.go index 712a556..37be1da 100644 --- a/vfs/readervfs/example_test.go +++ b/vfs/readervfs/example_test.go @@ -2,12 +2,11 @@ package readervfs_test import ( "database/sql" + _ "embed" "fmt" "log" "strings" - _ "embed" - "github.com/psanford/httpreadat" _ "github.com/ncruces/go-sqlite3/driver" diff --git a/vfs/shm.go b/vfs/shm.go index e2f04d6..402676a 100644 --- a/vfs/shm.go +++ b/vfs/shm.go @@ -203,6 +203,7 @@ func (s *vfsShm) shmUnmap(delete bool) { func (s *vfsShm) shmBarrier() { s.Lock() + //lint:ignore SA2001 memory barrier. s.Unlock() } diff --git a/vfs/shm_bsd.go b/vfs/shm_bsd.go index 7657b0d..8dc6ec9 100644 --- a/vfs/shm_bsd.go +++ b/vfs/shm_bsd.go @@ -273,5 +273,6 @@ func (s *vfsShm) shmUnmap(delete bool) { func (s *vfsShm) shmBarrier() { s.lockMtx.Lock() + //lint:ignore SA2001 memory barrier. s.lockMtx.Unlock() } diff --git a/vfs/tests/speedtest1/speedtest1_test.go b/vfs/tests/speedtest1/speedtest1_test.go index b672641..233ea44 100644 --- a/vfs/tests/speedtest1/speedtest1_test.go +++ b/vfs/tests/speedtest1/speedtest1_test.go @@ -5,6 +5,7 @@ import ( "compress/bzip2" "context" "crypto/rand" + _ "embed" "flag" "io" "os" @@ -14,8 +15,6 @@ import ( "strings" "testing" - _ "embed" - "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" "github.com/tetratelabs/wazero/experimental" diff --git a/vfs/vfs.go b/vfs/vfs.go index ef14ea3..d4519fb 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -11,6 +11,7 @@ import ( "github.com/tetratelabs/wazero/api" "github.com/ncruces/go-sqlite3/internal/util" + "github.com/ncruces/go-sqlite3/util/sql3util" "github.com/ncruces/julianday" ) @@ -146,7 +147,7 @@ func vfsOpen(ctx context.Context, mod api.Module, pVfs, zPath, pFile uint32, fla } if file, ok := file.(FilePowersafeOverwrite); ok { - if b, ok := util.ParseBool(name.URIParameter("psow")); ok { + if b, ok := sql3util.ParseBool(name.URIParameter("psow")); ok { file.SetPowersafeOverwrite(b) } }