mirror of
https://github.com/neilotoole/sq.git
synced 2024-12-26 01:34:43 +03:00
Filepath and colorization issues with the v0.12.1 release on Windows (#50)
- Windows filepath handling - Windows colorization handling - Minor code tidy up and refactor
This commit is contained in:
parent
58d8e301e0
commit
062e2dea88
@ -348,6 +348,8 @@ func (rc *RunContext) preRunE() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rc.wrtr = newWriters(rc.Log, rc.Cmd, rc.Config.Options, rc.Out, rc.ErrOut)
|
rc.wrtr = newWriters(rc.Log, rc.Cmd, rc.Config.Options, rc.Out, rc.ErrOut)
|
||||||
|
rc.Out = rc.wrtr.out
|
||||||
|
rc.ErrOut = rc.wrtr.errOut
|
||||||
|
|
||||||
var scratchSrcFunc driver.ScratchSrcFunc
|
var scratchSrcFunc driver.ScratchSrcFunc
|
||||||
|
|
||||||
@ -456,6 +458,8 @@ func (rc *RunContext) databases() *driver.Databases {
|
|||||||
|
|
||||||
// writers is a container for the various output writer types.
|
// writers is a container for the various output writer types.
|
||||||
type writers struct {
|
type writers struct {
|
||||||
|
out io.Writer
|
||||||
|
errOut io.Writer
|
||||||
fmt *output.Formatting
|
fmt *output.Formatting
|
||||||
recordw output.RecordWriter
|
recordw output.RecordWriter
|
||||||
metaw output.MetadataWriter
|
metaw output.MetadataWriter
|
||||||
@ -491,6 +495,8 @@ func newWriters(log lg.Log, cmd *cobra.Command, opts config.Options, out, errOut
|
|||||||
// flags and set the various writer fields depending upon which
|
// flags and set the various writer fields depending upon which
|
||||||
// writers the format implements.
|
// writers the format implements.
|
||||||
w := &writers{
|
w := &writers{
|
||||||
|
out: out,
|
||||||
|
errOut: errOut,
|
||||||
fmt: fm,
|
fmt: fm,
|
||||||
recordw: tablew.NewRecordWriter(out, fm, hasHeader),
|
recordw: tablew.NewRecordWriter(out, fm, hasHeader),
|
||||||
metaw: tablew.NewMetadataWriter(out, fm),
|
metaw: tablew.NewMetadataWriter(out, fm),
|
||||||
|
@ -149,8 +149,8 @@ func execSrcAdd(rc *RunContext, cmd *cobra.Command, args []string) error {
|
|||||||
//
|
//
|
||||||
// The second form is particularly nice for bash completion etc.
|
// The second form is particularly nice for bash completion etc.
|
||||||
if typ == sqlite3.Type {
|
if typ == sqlite3.Type {
|
||||||
if !strings.HasPrefix(loc, "sqlite3:") {
|
if !strings.HasPrefix(loc, sqlite3.Prefix) {
|
||||||
loc = "sqlite3:" + loc
|
loc = sqlite3.Prefix + loc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,8 +181,8 @@ func newNotifyAddHipChatCmd() (*cobra.Command, runFunc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func execNotifyAddHipChat(rc *RunContext, cmd *cobra.Command, args []string) error {
|
func execNotifyAddHipChat(rc *RunContext, cmd *cobra.Command, args []string) error {
|
||||||
fmt.Println("Add HipChat room")
|
fmt.Fprintln(rc.Out, "Add HipChat room")
|
||||||
fmt.Println(strings.Join(args, " | "))
|
fmt.Fprintln(rc.Out, strings.Join(args, " | "))
|
||||||
|
|
||||||
var label string
|
var label string
|
||||||
var err error
|
var err error
|
||||||
@ -194,7 +194,7 @@ func execNotifyAddHipChat(rc *RunContext, cmd *cobra.Command, args []string) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
if label != "" {
|
if label != "" {
|
||||||
fmt.Printf("Label: %s", label)
|
fmt.Fprintf(rc.Out, "Label: %s", label)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -18,24 +18,15 @@ import (
|
|||||||
"github.com/neilotoole/sq/testh/sakila"
|
"github.com/neilotoole/sq/testh/sakila"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCmdSLQ_Insert_LONG(t *testing.T) {
|
// TestCmdSLQ_Insert tests "sq slq QUERY --insert=dest.tbl".
|
||||||
testh.SkipShort(t, true)
|
|
||||||
testCmdSLQ_Insert(t, sakila.All, sakila.SQLAll)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCmdSLQ_Insert(t *testing.T) {
|
func TestCmdSLQ_Insert(t *testing.T) {
|
||||||
testCmdSLQ_Insert(t, sakila.SQLLatest, sakila.SQLLatest)
|
for _, origin := range sakila.SQLLatest {
|
||||||
}
|
|
||||||
|
|
||||||
// testCmdSLQ_Insert tests "sq slq QUERY --insert=dest.tbl".
|
|
||||||
func testCmdSLQ_Insert(t *testing.T, origins, dests []string) {
|
|
||||||
for _, origin := range origins {
|
|
||||||
origin := origin
|
origin := origin
|
||||||
|
|
||||||
t.Run("origin_"+origin, func(t *testing.T) {
|
t.Run("origin_"+origin, func(t *testing.T) {
|
||||||
testh.SkipShort(t, origin == sakila.XLSX)
|
testh.SkipShort(t, origin == sakila.XLSX)
|
||||||
|
|
||||||
for _, dest := range dests {
|
for _, dest := range sakila.SQLLatest {
|
||||||
dest := dest
|
dest := dest
|
||||||
|
|
||||||
t.Run("dest_"+dest, func(t *testing.T) {
|
t.Run("dest_"+dest, func(t *testing.T) {
|
||||||
|
@ -16,23 +16,15 @@ import (
|
|||||||
"github.com/neilotoole/sq/testh/testsrc"
|
"github.com/neilotoole/sq/testh/testsrc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCmdSQL_Insert_LONG(t *testing.T) {
|
// TestCmdSQL_Insert tests "sq sql QUERY --insert=dest.tbl".
|
||||||
testh.SkipShort(t, true)
|
|
||||||
testCmdSQL_Insert(t, sakila.All, sakila.All)
|
|
||||||
}
|
|
||||||
func TestCmdSQL_Insert(t *testing.T) {
|
func TestCmdSQL_Insert(t *testing.T) {
|
||||||
testCmdSQL_Insert(t, sakila.SQLLatest, sakila.SQLLatest)
|
for _, origin := range sakila.SQLLatest {
|
||||||
}
|
|
||||||
|
|
||||||
// testCmdSQL_Insert tests "sq sql QUERY --insert=dest.tbl".
|
|
||||||
func testCmdSQL_Insert(t *testing.T, origins, dests []string) {
|
|
||||||
for _, origin := range origins {
|
|
||||||
origin := origin
|
origin := origin
|
||||||
|
|
||||||
t.Run("origin_"+origin, func(t *testing.T) {
|
t.Run("origin_"+origin, func(t *testing.T) {
|
||||||
testh.SkipShort(t, origin == sakila.XLSX)
|
testh.SkipShort(t, origin == sakila.XLSX)
|
||||||
|
|
||||||
for _, dest := range dests {
|
for _, dest := range sakila.SQLLatest {
|
||||||
dest := dest
|
dest := dest
|
||||||
|
|
||||||
t.Run("dest_"+dest, func(t *testing.T) {
|
t.Run("dest_"+dest, func(t *testing.T) {
|
||||||
@ -125,8 +117,8 @@ func TestCmdSQL_StdinQuery(t *testing.T) {
|
|||||||
wantCount int
|
wantCount int
|
||||||
wantErr bool
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
{fpath: proj.Abs(sakila.PathCSVActor), tbl: source.MonotableName, wantCount: sakila.TblActorCount + 1}, // +1 is for the header row
|
{fpath: proj.Abs(sakila.PathCSVActorNoHeader), tbl: source.MonotableName, wantCount: sakila.TblActorCount},
|
||||||
{fpath: proj.Abs(sakila.PathXLSXSubset), tbl: sakila.TblActor, wantCount: sakila.TblActorCount + 1},
|
{fpath: proj.Abs(sakila.PathXLSXSubset), tbl: sakila.TblActor, wantCount: sakila.TblActorCount + 1}, // +1 is for the header row in the XLSX file
|
||||||
{fpath: proj.Abs("README.md"), wantErr: true},
|
{fpath: proj.Abs("README.md"), wantErr: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,9 +132,10 @@ func TestCmdSQL_StdinQuery(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ru := newRun(t).hush()
|
ru := newRun(t).hush()
|
||||||
|
//ru := newRun(t)
|
||||||
ru.rc.Stdin = f
|
ru.rc.Stdin = f
|
||||||
|
|
||||||
err = ru.exec("sql", "SELECT * FROM "+tc.tbl)
|
err = ru.exec("sql", "--no-header", "SELECT * FROM "+tc.tbl)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
return
|
return
|
||||||
|
@ -23,19 +23,19 @@ func execVersion(rc *RunContext, cmd *cobra.Command, args []string) error {
|
|||||||
// If buildinfo.Version is not set (building without ldflags),
|
// If buildinfo.Version is not set (building without ldflags),
|
||||||
// then we set a dummy version.
|
// then we set a dummy version.
|
||||||
if version == "" {
|
if version == "" {
|
||||||
version = "0.0.0.dev"
|
version = "0.0.0-dev"
|
||||||
}
|
}
|
||||||
|
|
||||||
rc.wrtr.fmt.Hilite.Fprintf(rc.Out, "sq %s", version)
|
rc.wrtr.fmt.Hilite.Fprintf(rc.Out, "sq %s", version)
|
||||||
|
|
||||||
if len(buildinfo.Commit) > 0 {
|
if len(buildinfo.Commit) > 0 {
|
||||||
fmt.Fprintf(rc.Out, " ")
|
fmt.Fprint(rc.Out, " ")
|
||||||
rc.wrtr.fmt.Faint.Fprintf(rc.Out, "#"+buildinfo.Commit)
|
rc.wrtr.fmt.Faint.Fprint(rc.Out, "#"+buildinfo.Commit)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(buildinfo.Timestamp) > 0 {
|
if len(buildinfo.Timestamp) > 0 {
|
||||||
fmt.Fprintf(rc.Out, " ")
|
fmt.Fprint(rc.Out, " ")
|
||||||
rc.wrtr.fmt.Faint.Fprintf(rc.Out, buildinfo.Timestamp)
|
rc.wrtr.fmt.Faint.Fprint(rc.Out, buildinfo.Timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(rc.Out)
|
fmt.Fprintln(rc.Out)
|
||||||
|
@ -349,7 +349,7 @@ func (t *Table) printHeading() {
|
|||||||
pad := ConditionString(i == end && !t.borders.Left, Space, t.pColumn)
|
pad := ConditionString(i == end && !t.borders.Left, Space, t.pColumn)
|
||||||
|
|
||||||
head := t.headerTrans(fmt.Sprintf("%s %s ", padFunc(h, Space, v), pad))
|
head := t.headerTrans(fmt.Sprintf("%s %s ", padFunc(h, Space, v), pad))
|
||||||
fmt.Print(head)
|
fmt.Fprint(t.out, head)
|
||||||
|
|
||||||
}
|
}
|
||||||
// Next line
|
// Next line
|
||||||
|
@ -46,7 +46,7 @@ func (d *database) SourceMetadata(ctx context.Context) (*source.Metadata, error)
|
|||||||
|
|
||||||
meta := &source.Metadata{Handle: d.src.Handle, SourceType: Type, DBDriverType: dbDrvr}
|
meta := &source.Metadata{Handle: d.src.Handle, SourceType: Type, DBDriverType: dbDrvr}
|
||||||
|
|
||||||
dsn, err := PathFromSourceLocation(d.src)
|
dsn, err := PathFromLocation(d.src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,9 @@ const (
|
|||||||
|
|
||||||
// dbDrvr is the backing sqlite3 SQL driver impl name.
|
// dbDrvr is the backing sqlite3 SQL driver impl name.
|
||||||
dbDrvr = "sqlite3"
|
dbDrvr = "sqlite3"
|
||||||
|
|
||||||
|
// Prefix is the scheme+separator value "sqlite3://".
|
||||||
|
Prefix = "sqlite3://"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider is the SQLite3 implementation of driver.Provider.
|
// Provider is the SQLite3 implementation of driver.Provider.
|
||||||
@ -63,7 +66,7 @@ func (d *Driver) DriverMetadata() driver.Metadata {
|
|||||||
func (d *Driver) Open(ctx context.Context, src *source.Source) (driver.Database, error) {
|
func (d *Driver) Open(ctx context.Context, src *source.Source) (driver.Database, error) {
|
||||||
d.log.Debug("Opening data source: ", src)
|
d.log.Debug("Opening data source: ", src)
|
||||||
|
|
||||||
dsn, err := PathFromSourceLocation(src)
|
dsn, err := PathFromLocation(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -77,7 +80,7 @@ func (d *Driver) Open(ctx context.Context, src *source.Source) (driver.Database,
|
|||||||
|
|
||||||
// Truncate implements driver.Driver.
|
// Truncate implements driver.Driver.
|
||||||
func (d *Driver) Truncate(ctx context.Context, src *source.Source, tbl string, reset bool) (affected int64, err error) {
|
func (d *Driver) Truncate(ctx context.Context, src *source.Source, tbl string, reset bool) (affected int64, err error) {
|
||||||
dsn, err := PathFromSourceLocation(src)
|
dsn, err := PathFromLocation(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@ -118,7 +121,7 @@ func (d *Driver) ValidateSource(src *source.Source) (*source.Source, error) {
|
|||||||
|
|
||||||
// Ping implements driver.Driver.
|
// Ping implements driver.Driver.
|
||||||
func (d *Driver) Ping(ctx context.Context, src *source.Source) error {
|
func (d *Driver) Ping(ctx context.Context, src *source.Source) error {
|
||||||
dbase, err := d.Open(context.TODO(), src)
|
dbase, err := d.Open(ctx, src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -378,24 +381,24 @@ func NewScratchSource(log lg.Log, name string) (src *source.Source, clnup func()
|
|||||||
src = &source.Source{
|
src = &source.Source{
|
||||||
Type: Type,
|
Type: Type,
|
||||||
Handle: source.ScratchHandle,
|
Handle: source.ScratchHandle,
|
||||||
Location: dbDrvr + "://" + f.Name(),
|
Location: Prefix + f.Name(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return src, cleanFn, nil
|
return src, cleanFn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PathFromSourceLocation returns absolute file path
|
// PathFromLocation returns the absolute file path
|
||||||
// from the source location, which typically has the "sqlite3:" prefix.
|
// from the source location, which should have the "sqlite3://" prefix.
|
||||||
func PathFromSourceLocation(src *source.Source) (string, error) {
|
func PathFromLocation(src *source.Source) (string, error) {
|
||||||
if src.Type != Type {
|
if src.Type != Type {
|
||||||
return "", errz.Errorf("driver %q does not support %q", Type, src.Type)
|
return "", errz.Errorf("driver %q does not support %q", Type, src.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(src.Location, dbDrvr+":") {
|
if !strings.HasPrefix(src.Location, Prefix) {
|
||||||
return "", errz.Errorf("sqlite3 source location must begin with %q but was: %s", Type, src.RedactedLocation())
|
return "", errz.Errorf("sqlite3 source location must begin with %q but was: %s", Prefix, src.RedactedLocation())
|
||||||
}
|
}
|
||||||
|
|
||||||
loc := strings.TrimPrefix(src.Location, dbDrvr+":")
|
loc := strings.TrimPrefix(src.Location, Prefix)
|
||||||
if len(loc) < 2 {
|
if len(loc) < 2 {
|
||||||
return "", errz.Errorf("sqlite3 source location is too short: %s", src.RedactedLocation())
|
return "", errz.Errorf("sqlite3 source location is too short: %s", src.RedactedLocation())
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/neilotoole/sq/drivers/sqlite3"
|
||||||
|
"github.com/neilotoole/sq/libsq/source"
|
||||||
"github.com/neilotoole/sq/libsq/sqlmodel"
|
"github.com/neilotoole/sq/libsq/sqlmodel"
|
||||||
"github.com/neilotoole/sq/libsq/sqlz"
|
"github.com/neilotoole/sq/libsq/sqlz"
|
||||||
"github.com/neilotoole/sq/libsq/stringz"
|
"github.com/neilotoole/sq/libsq/stringz"
|
||||||
@ -160,3 +162,36 @@ func TestDriver_CreateTable_NotNullDefault(t *testing.T) {
|
|||||||
require.NotNil(t, sink.Recs[0][i])
|
require.NotNil(t, sink.Recs[0][i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPathFromLocation(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
loc string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{loc: "sqlite3:///test.db", want: "/test.db"},
|
||||||
|
{loc: "postgres:///test.db", wantErr: true},
|
||||||
|
{loc: `sqlite3://C:\dir\sakila.db`, want: `C:\dir\sakila.db`},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
|
||||||
|
t.Run(tc.loc, func(t *testing.T) {
|
||||||
|
src := &source.Source{
|
||||||
|
Handle: "@h1",
|
||||||
|
Type: sqlite3.Type,
|
||||||
|
Location: tc.loc,
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := sqlite3.PathFromLocation(src)
|
||||||
|
if tc.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -214,7 +214,8 @@ func TestSQLDriver_PrepareUpdateStmt(t *testing.T) {
|
|||||||
|
|
||||||
func TestDriver_Ping(t *testing.T) {
|
func TestDriver_Ping(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
testCases := append(sakila.All, sakila.CSVActor, sakila.CSVActorHTTP)
|
testCases := sakila.All
|
||||||
|
testCases = append(testCases, sakila.CSVActor, sakila.CSVActorHTTP)
|
||||||
|
|
||||||
for _, handle := range testCases {
|
for _, handle := range testCases {
|
||||||
handle := handle
|
handle := handle
|
||||||
@ -235,7 +236,8 @@ func TestDriver_Ping(t *testing.T) {
|
|||||||
|
|
||||||
func TestDriver_Open(t *testing.T) {
|
func TestDriver_Open(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
testCases := append(sakila.All, sakila.CSVActor, sakila.CSVActorHTTP)
|
testCases := sakila.All
|
||||||
|
testCases = append(testCases, sakila.CSVActor, sakila.CSVActorHTTP)
|
||||||
|
|
||||||
for _, handle := range testCases {
|
for _, handle := range testCases {
|
||||||
handle := handle
|
handle := handle
|
||||||
|
@ -455,8 +455,11 @@ func AbsLocation(loc string) string {
|
|||||||
return loc
|
return loc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isFpath returns true if loc is a file path.
|
||||||
func isFpath(loc string) (fpath string, ok bool) {
|
func isFpath(loc string) (fpath string, ok bool) {
|
||||||
if strings.ContainsRune(loc, ':') {
|
// This is not exactly an industrial-strength algorithm...
|
||||||
|
if strings.Contains(loc, ":/") {
|
||||||
|
// Excludes "http:/" etc
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package source
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/neilotoole/lg/testlg"
|
"github.com/neilotoole/lg/testlg"
|
||||||
@ -55,15 +56,18 @@ func TestParseLoc(t *testing.T) {
|
|||||||
loc string
|
loc string
|
||||||
want parsedLoc
|
want parsedLoc
|
||||||
wantErr bool
|
wantErr bool
|
||||||
|
windows bool
|
||||||
}{
|
}{
|
||||||
{loc: "/path/to/sakila.xlsx", want: parsedLoc{name: "sakila", ext: ".xlsx"}},
|
{loc: "/path/to/sakila.xlsx", want: parsedLoc{name: "sakila", ext: ".xlsx"}},
|
||||||
{loc: "relative/path/to/sakila.xlsx", want: parsedLoc{name: "sakila", ext: ".xlsx"}},
|
{loc: "relative/path/to/sakila.xlsx", want: parsedLoc{name: "sakila", ext: ".xlsx"}},
|
||||||
{loc: "./relative/path/to/sakila.xlsx", want: parsedLoc{name: "sakila", ext: ".xlsx"}},
|
{loc: "./relative/path/to/sakila.xlsx", want: parsedLoc{name: "sakila", ext: ".xlsx"}},
|
||||||
{loc: "https://server:8080/path/to/sakila.xlsx", want: parsedLoc{scheme: "https", hostname: "server", port: 8080, name: "sakila", ext: ".xlsx"}},
|
{loc: "https://server:8080/path/to/sakila.xlsx", want: parsedLoc{scheme: "https", hostname: "server", port: 8080, name: "sakila", ext: ".xlsx"}},
|
||||||
{loc: "http://server/path/to/sakila.xlsx?param=val¶m2=val2", want: parsedLoc{scheme: "http", hostname: "server", name: "sakila", ext: ".xlsx"}},
|
{loc: "http://server/path/to/sakila.xlsx?param=val¶m2=val2", want: parsedLoc{scheme: "http", hostname: "server", name: "sakila", ext: ".xlsx"}},
|
||||||
{loc: "sqlite3:/path/to/sakila.db", want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", ext: ".db", dsn: "/path/to/sakila.db"}},
|
{loc: "sqlite3:/path/to/sakila.db", wantErr: true}, // the scheme is malformed (should be "sqlite3://...")
|
||||||
{loc: "sqlite3:/path/to/sakila.sqlite", want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", ext: ".sqlite", dsn: "/path/to/sakila.sqlite"}},
|
{loc: "sqlite3:///path/to/sakila.sqlite", want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", ext: ".sqlite", dsn: "/path/to/sakila.sqlite"}},
|
||||||
{loc: "sqlite3:/path/to/sakila", want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", dsn: "/path/to/sakila"}},
|
{loc: `sqlite3://C:\path\to\sakila.sqlite`, windows: true, want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", ext: ".sqlite", dsn: `C:\path\to\sakila.sqlite`}},
|
||||||
|
{loc: `sqlite3://C:\path\to\sakila.sqlite?param=val`, windows: true, want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", ext: ".sqlite", dsn: `C:\path\to\sakila.sqlite?param=val`}},
|
||||||
|
{loc: "sqlite3:///path/to/sakila", want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", dsn: "/path/to/sakila"}},
|
||||||
{loc: "sqlite3://path/to/sakila.db", want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", ext: ".db", dsn: "path/to/sakila.db"}},
|
{loc: "sqlite3://path/to/sakila.db", want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", ext: ".db", dsn: "path/to/sakila.db"}},
|
||||||
{loc: "sqlite3:///path/to/sakila.db", want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", ext: ".db", dsn: "/path/to/sakila.db"}},
|
{loc: "sqlite3:///path/to/sakila.db", want: parsedLoc{typ: typeSL3, scheme: "sqlite3", name: "sakila", ext: ".db", dsn: "/path/to/sakila.db"}},
|
||||||
{loc: "sqlserver://sakila:p_ssW0rd@localhost?database=sakila", want: parsedLoc{typ: typeMS, scheme: "sqlserver", user: dbuser, pass: dbpass, hostname: "localhost", name: "sakila", dsn: "Password=p_ssW0rd;Server=localhost;User ID=sakila;database=sakila"}},
|
{loc: "sqlserver://sakila:p_ssW0rd@localhost?database=sakila", want: parsedLoc{typ: typeMS, scheme: "sqlserver", user: dbuser, pass: dbpass, hostname: "localhost", name: "sakila", dsn: "Password=p_ssW0rd;Server=localhost;User ID=sakila;database=sakila"}},
|
||||||
@ -77,6 +81,10 @@ func TestParseLoc(t *testing.T) {
|
|||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.loc, func(t *testing.T) {
|
t.Run(tc.loc, func(t *testing.T) {
|
||||||
|
if tc.windows && runtime.GOOS != "windows" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
tc.want.loc = tc.loc // set this here rather than verbosely in the setup
|
tc.want.loc = tc.loc // set this here rather than verbosely in the setup
|
||||||
got, gotErr := parseLoc(tc.loc)
|
got, gotErr := parseLoc(tc.loc)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
|
@ -150,8 +150,13 @@ type parsedLoc struct {
|
|||||||
func parseLoc(loc string) (*parsedLoc, error) {
|
func parseLoc(loc string) (*parsedLoc, error) {
|
||||||
ploc := &parsedLoc{loc: loc}
|
ploc := &parsedLoc{loc: loc}
|
||||||
|
|
||||||
if !strings.ContainsRune(loc, ':') {
|
if !strings.Contains(loc, "://") {
|
||||||
// no scheme: it's just a file path
|
if strings.Contains(loc, ":/") {
|
||||||
|
// malformed location, such as "sqlite3:/path/to/file"
|
||||||
|
return nil, errz.Errorf("parse location: invalid scheme: %q", loc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// no scheme: it's just a regular file path for a document such as an Excel file
|
||||||
name := filepath.Base(loc)
|
name := filepath.Base(loc)
|
||||||
ploc.ext = filepath.Ext(name)
|
ploc.ext = filepath.Ext(name)
|
||||||
if ploc.ext != "" {
|
if ploc.ext != "" {
|
||||||
@ -184,20 +189,22 @@ func parseLoc(loc string) (*parsedLoc, error) {
|
|||||||
return ploc, nil
|
return ploc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := dburl.Parse(loc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errz.Err(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ploc.scheme = u.OriginalScheme
|
|
||||||
ploc.dsn = u.DSN
|
|
||||||
ploc.user = u.User.Username()
|
|
||||||
ploc.pass, _ = u.User.Password()
|
|
||||||
|
|
||||||
// sqlite3 is a special case, handle it now
|
// sqlite3 is a special case, handle it now
|
||||||
if ploc.scheme == "sqlite3" {
|
const sqlitePrefix = "sqlite3://"
|
||||||
|
if strings.HasPrefix(loc, sqlitePrefix) {
|
||||||
|
fpath := strings.TrimPrefix(loc, sqlitePrefix)
|
||||||
|
|
||||||
|
ploc.scheme = "sqlite3"
|
||||||
ploc.typ = typeSL3
|
ploc.typ = typeSL3
|
||||||
name := path.Base(u.DSN)
|
ploc.dsn = fpath
|
||||||
|
|
||||||
|
// fpath could include params, e.g. "sqlite3://C:\sakila.db?param=val"
|
||||||
|
if i := strings.IndexRune(fpath, '?'); i >= 0 {
|
||||||
|
// Snip off the params
|
||||||
|
fpath = fpath[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
name := filepath.Base(fpath)
|
||||||
ploc.ext = filepath.Ext(name)
|
ploc.ext = filepath.Ext(name)
|
||||||
if ploc.ext != "" {
|
if ploc.ext != "" {
|
||||||
name = name[:len(name)-len(ploc.ext)]
|
name = name[:len(name)-len(ploc.ext)]
|
||||||
@ -207,6 +214,15 @@ func parseLoc(loc string) (*parsedLoc, error) {
|
|||||||
return ploc, nil
|
return ploc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u, err := dburl.Parse(loc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errz.Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ploc.scheme = u.OriginalScheme
|
||||||
|
ploc.dsn = u.DSN
|
||||||
|
ploc.user = u.User.Username()
|
||||||
|
ploc.pass, _ = u.User.Password()
|
||||||
ploc.hostname = u.Hostname()
|
ploc.hostname = u.Hostname()
|
||||||
if u.Port() != "" {
|
if u.Port() != "" {
|
||||||
ploc.port, err = strconv.Atoi(u.Port())
|
ploc.port, err = strconv.Atoi(u.Port())
|
||||||
|
56
testh/testdata/sources.sq.yml
vendored
56
testh/testdata/sources.sq.yml
vendored
@ -29,90 +29,90 @@ sources:
|
|||||||
location: sqlserver://sakila:p_ssW0rd@${SQ_TEST_SRC__SAKILA_MS17}?database=sakila
|
location: sqlserver://sakila:p_ssW0rd@${SQ_TEST_SRC__SAKILA_MS17}?database=sakila
|
||||||
- handle: '@sakila_xlsx'
|
- handle: '@sakila_xlsx'
|
||||||
type: xlsx
|
type: xlsx
|
||||||
location: "${SQ_ROOT}/drivers/xlsx/testdata/sakila.xlsx"
|
location: '${SQ_ROOT}/drivers/xlsx/testdata/sakila.xlsx'
|
||||||
options:
|
options:
|
||||||
header:
|
header:
|
||||||
- "true"
|
- 'true'
|
||||||
- handle: '@sakila_xlsx_subset'
|
- handle: '@sakila_xlsx_subset'
|
||||||
type: xlsx
|
type: xlsx
|
||||||
location: "${SQ_ROOT}/drivers/xlsx/testdata/sakila_subset.xlsx"
|
location: '${SQ_ROOT}/drivers/xlsx/testdata/sakila_subset.xlsx'
|
||||||
options:
|
options:
|
||||||
header:
|
header:
|
||||||
- "true"
|
- 'true'
|
||||||
- handle: '@sakila_xlsx_noheader'
|
- handle: '@sakila_xlsx_noheader'
|
||||||
type: xlsx
|
type: xlsx
|
||||||
location: "${SQ_ROOT}/drivers/xlsx/testdata/sakila_noheader.xlsx"
|
location: '${SQ_ROOT}/drivers/xlsx/testdata/sakila_noheader.xlsx'
|
||||||
options:
|
options:
|
||||||
header:
|
header:
|
||||||
- "false"
|
- 'false'
|
||||||
- handle: '@sakila_csv_actor'
|
- handle: '@sakila_csv_actor'
|
||||||
type: csv
|
type: csv
|
||||||
location: "${SQ_ROOT}/drivers/csv/testdata/sakila-csv/actor.csv"
|
location: '${SQ_ROOT}/drivers/csv/testdata/sakila-csv/actor.csv'
|
||||||
options:
|
options:
|
||||||
header:
|
header:
|
||||||
- "true"
|
- 'true'
|
||||||
- handle: '@sakila_csv_actor_http'
|
- handle: '@sakila_csv_actor_http'
|
||||||
type: csv
|
type: csv
|
||||||
location: "https://sq.io/testdata/actor.csv"
|
location: 'https://sq.io/testdata/actor.csv'
|
||||||
options:
|
options:
|
||||||
header:
|
header:
|
||||||
- "true"
|
- 'true'
|
||||||
- handle: '@sakila_csv_actor_noheader'
|
- handle: '@sakila_csv_actor_noheader'
|
||||||
type: csv
|
type: csv
|
||||||
location: "${SQ_ROOT}/drivers/csv/testdata/sakila-csv-noheader/actor.csv"
|
location: '${SQ_ROOT}/drivers/csv/testdata/sakila-csv-noheader/actor.csv'
|
||||||
options:
|
options:
|
||||||
header:
|
header:
|
||||||
- "false"
|
- 'false'
|
||||||
- handle: '@sakila_tsv_actor'
|
- handle: '@sakila_tsv_actor'
|
||||||
type: tsv
|
type: tsv
|
||||||
location: "${SQ_ROOT}/drivers/csv/testdata/sakila-tsv/actor.tsv"
|
location: '${SQ_ROOT}/drivers/csv/testdata/sakila-tsv/actor.tsv'
|
||||||
options:
|
options:
|
||||||
header:
|
header:
|
||||||
- "true"
|
- 'true'
|
||||||
- handle: '@sakila_tsv_actor_noheader'
|
- handle: '@sakila_tsv_actor_noheader'
|
||||||
type: tsv
|
type: tsv
|
||||||
location: "${SQ_ROOT}/drivers/csv/testdata/sakila-tsv-noheader/actor.tsv"
|
location: '${SQ_ROOT}/drivers/csv/testdata/sakila-tsv-noheader/actor.tsv'
|
||||||
options:
|
options:
|
||||||
header:
|
header:
|
||||||
- "false"
|
- 'false'
|
||||||
|
|
||||||
- handle: '@csv_person'
|
- handle: '@csv_person'
|
||||||
type: csv
|
type: csv
|
||||||
location: "${SQ_ROOT}/drivers/csv/testdata/person.csv"
|
location: '${SQ_ROOT}/drivers/csv/testdata/person.csv'
|
||||||
- handle: '@csv_person_big'
|
- handle: '@csv_person_big'
|
||||||
options:
|
options:
|
||||||
header:
|
header:
|
||||||
- "true"
|
- 'true'
|
||||||
type: csv
|
type: csv
|
||||||
location: "${SQ_ROOT}/drivers/csv/testdata/person_big.csv"
|
location: '${SQ_ROOT}/drivers/csv/testdata/person_big.csv'
|
||||||
- handle: '@csv_person_noheader'
|
- handle: '@csv_person_noheader'
|
||||||
type: csv
|
type: csv
|
||||||
location: "${SQ_ROOT}/drivers/csv/testdata/person_noheader.csv"
|
location: '${SQ_ROOT}/drivers/csv/testdata/person_noheader.csv'
|
||||||
- handle: '@tsv_person'
|
- handle: '@tsv_person'
|
||||||
type: tsv
|
type: tsv
|
||||||
location: "${SQ_ROOT}/sq/drivers/csv/testdata/person.tsv"
|
location: '${SQ_ROOT}/sq/drivers/csv/testdata/person.tsv'
|
||||||
- handle: '@tsv_person_noheader'
|
- handle: '@tsv_person_noheader'
|
||||||
type: tsv
|
type: tsv
|
||||||
location: "${SQ_ROOT}/drivers/csv/testdata/person_noheader.tsv"
|
location: '${SQ_ROOT}/drivers/csv/testdata/person_noheader.tsv'
|
||||||
- handle: '@tsv_person_noheader_cols'
|
- handle: '@tsv_person_noheader_cols'
|
||||||
type: tsv
|
type: tsv
|
||||||
location: "${SQ_ROOT}/drivers/csv/testdata/person_noheader.tsv"
|
location: '${SQ_ROOT}/drivers/csv/testdata/person_noheader.tsv'
|
||||||
options:
|
options:
|
||||||
cols:
|
cols:
|
||||||
- uid,username,email
|
- uid,username,email
|
||||||
- handle: '@xl_header'
|
- handle: '@xl_header'
|
||||||
type: xlsx
|
type: xlsx
|
||||||
location: "${SQ_ROOT}/drivers/xlsx/testdata/test_header.xlsx"
|
location: '${SQ_ROOT}/drivers/xlsx/testdata/test_header.xlsx'
|
||||||
- handle: '@xl_noheader'
|
- handle: '@xl_noheader'
|
||||||
type: xlsx
|
type: xlsx
|
||||||
location: "${SQ_ROOT}/drivers/xlsx/testdata/test_noheader.xlsx"
|
location: '${SQ_ROOT}/drivers/xlsx/testdata/test_noheader.xlsx'
|
||||||
- handle: '@ud_ppl'
|
- handle: '@ud_ppl'
|
||||||
type: ppl
|
type: ppl
|
||||||
location: "${SQ_ROOT}/drivers/userdriver/xmlud/testdata/people.xml"
|
location: '${SQ_ROOT}/drivers/userdriver/xmlud/testdata/people.xml'
|
||||||
- handle: '@ud_rss_nytimes_local'
|
- handle: '@ud_rss_nytimes_local'
|
||||||
type: rss
|
type: rss
|
||||||
location: "${SQ_ROOT}/drivers/userdriver/xmlud/testdata/nytimes_local.rss.xml"
|
location: '${SQ_ROOT}/drivers/userdriver/xmlud/testdata/nytimes_local.rss.xml'
|
||||||
- handle: '@miscdb'
|
- handle: '@miscdb'
|
||||||
type: sqlite3
|
type: sqlite3
|
||||||
location: "sqlite3://${SQ_ROOT}/drivers/sqlite3/testdata/misc.db"
|
location: 'sqlite3://${SQ_ROOT}/drivers/sqlite3/testdata/misc.db'
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ func (h *Helper) Source(handle string) *source.Source {
|
|||||||
switch src.Type {
|
switch src.Type {
|
||||||
case sqlite3.Type:
|
case sqlite3.Type:
|
||||||
// This could be easily generalized for CSV/XLSX etc.
|
// This could be easily generalized for CSV/XLSX etc.
|
||||||
fpath, err := sqlite3.PathFromSourceLocation(src)
|
fpath, err := sqlite3.PathFromLocation(src)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
srcFile, err := os.Open(fpath)
|
srcFile, err := os.Open(fpath)
|
||||||
@ -167,7 +167,7 @@ func (h *Helper) Source(handle string) *source.Source {
|
|||||||
_, err = io.Copy(destFile, srcFile)
|
_, err = io.Copy(destFile, srcFile)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
src.Location = "sqlite3:" + destFile.Name()
|
src.Location = sqlite3.Prefix + destFile.Name()
|
||||||
}
|
}
|
||||||
h.srcCache[handle] = src
|
h.srcCache[handle] = src
|
||||||
return src
|
return src
|
||||||
|
Loading…
Reference in New Issue
Block a user