From 489e6d3e3fc25663c18d22a033b7864019b0f912 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Fri, 13 Nov 2020 01:50:21 +0900 Subject: [PATCH 01/15] Add io/fs#FS Driver #471 --- source/iofs/README.md | 38 ++++++++++++++++++++++++++ source/iofs/iofs.go | 36 ++++++++++++++++++++++++ source/iofs/iofs_test.go | 31 +++++++++++++++++++++ source/iofs/testdata/1_foobar.down.sql | 1 + source/iofs/testdata/1_foobar.up.sql | 1 + source/iofs/testdata/3_foobar.up.sql | 1 + source/iofs/testdata/4_foobar.down.sql | 1 + source/iofs/testdata/4_foobar.up.sql | 1 + source/iofs/testdata/5_foobar.down.sql | 1 + source/iofs/testdata/7_foobar.down.sql | 1 + source/iofs/testdata/7_foobar.up.sql | 1 + 11 files changed, 113 insertions(+) create mode 100644 source/iofs/README.md create mode 100644 source/iofs/iofs.go create mode 100644 source/iofs/iofs_test.go create mode 100644 source/iofs/testdata/1_foobar.down.sql create mode 100644 source/iofs/testdata/1_foobar.up.sql create mode 100644 source/iofs/testdata/3_foobar.up.sql create mode 100644 source/iofs/testdata/4_foobar.down.sql create mode 100644 source/iofs/testdata/4_foobar.up.sql create mode 100644 source/iofs/testdata/5_foobar.down.sql create mode 100644 source/iofs/testdata/7_foobar.down.sql create mode 100644 source/iofs/testdata/7_foobar.up.sql diff --git a/source/iofs/README.md b/source/iofs/README.md new file mode 100644 index 000000000..4a72504bf --- /dev/null +++ b/source/iofs/README.md @@ -0,0 +1,38 @@ +# iofs + +Driver with file system interface (`io/fs#FS`) supported from Go 1.16. + +This Driver cannot be used with versions below Go 1.15. + +## Usage + +Directory embedding example + +```go +package main + +import ( + "embed" + "log" + + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/golang-migrate/migrate/v4/source/iofs" +) + +//go:embed migrations +var fs embed.FS + +func main() { + d, err := iofs.WithInstance(fs, "migrations") + if err != nil { + log.Fatal(err) + } + m, err := migrate.NewWithSourceInstance("iofs", d, "postgres://postgres@localhost/postgres?sslmode=disable") + if err != nil { + log.Fatal(err) + } + err = m.Up() + // ... +} +``` diff --git a/source/iofs/iofs.go b/source/iofs/iofs.go new file mode 100644 index 000000000..43f820e9c --- /dev/null +++ b/source/iofs/iofs.go @@ -0,0 +1,36 @@ +// +build go1.16 + +package iofs + +import ( + "errors" + "fmt" + "io/fs" + "net/http" + + "github.com/golang-migrate/migrate/v4/source" + "github.com/golang-migrate/migrate/v4/source/httpfs" +) + +func init() { + source.Register("iofs", &Iofs{}) +} + +// Iofs is a source driver for io/fs#FS. +type Iofs struct { + httpfs.PartialDriver +} + +// Open by url does not supported with Iofs. +func (i *Iofs) Open(url string) (source.Driver, error) { + return nil, errors.New("iofs driver does not support open by url") +} + +// WithInstance wraps io/fs#FS as source.Driver. +func WithInstance(fsys fs.FS, path string) (source.Driver, error) { + var i Iofs + if err := i.Init(http.FS(fsys), path); err != nil { + return nil, fmt.Errorf("failed to init driver with path %s: %w", path, err) + } + return &i, nil +} diff --git a/source/iofs/iofs_test.go b/source/iofs/iofs_test.go new file mode 100644 index 000000000..e2bfba2d8 --- /dev/null +++ b/source/iofs/iofs_test.go @@ -0,0 +1,31 @@ +// +build go1.16 + +package iofs_test + +import ( + "embed" + "testing" + + "github.com/golang-migrate/migrate/v4/source/iofs" + st "github.com/golang-migrate/migrate/v4/source/testing" +) + +//go:embed testdata +var fs embed.FS + +func Test(t *testing.T) { + d, err := iofs.WithInstance(fs, "testdata") + if err != nil { + t.Fatal(err) + } + + st.Test(t, d) +} + +func TestOpen(t *testing.T) { + i := new(iofs.Iofs) + _, err := i.Open("") + if err == nil { + t.Fatal("iofs driver does not support open by url") + } +} diff --git a/source/iofs/testdata/1_foobar.down.sql b/source/iofs/testdata/1_foobar.down.sql new file mode 100644 index 000000000..4267951a5 --- /dev/null +++ b/source/iofs/testdata/1_foobar.down.sql @@ -0,0 +1 @@ +1 down diff --git a/source/iofs/testdata/1_foobar.up.sql b/source/iofs/testdata/1_foobar.up.sql new file mode 100644 index 000000000..046fd5a5d --- /dev/null +++ b/source/iofs/testdata/1_foobar.up.sql @@ -0,0 +1 @@ +1 up diff --git a/source/iofs/testdata/3_foobar.up.sql b/source/iofs/testdata/3_foobar.up.sql new file mode 100644 index 000000000..77c1b77dc --- /dev/null +++ b/source/iofs/testdata/3_foobar.up.sql @@ -0,0 +1 @@ +3 up diff --git a/source/iofs/testdata/4_foobar.down.sql b/source/iofs/testdata/4_foobar.down.sql new file mode 100644 index 000000000..b405d8bd0 --- /dev/null +++ b/source/iofs/testdata/4_foobar.down.sql @@ -0,0 +1 @@ +4 down diff --git a/source/iofs/testdata/4_foobar.up.sql b/source/iofs/testdata/4_foobar.up.sql new file mode 100644 index 000000000..eba61bb94 --- /dev/null +++ b/source/iofs/testdata/4_foobar.up.sql @@ -0,0 +1 @@ +4 up diff --git a/source/iofs/testdata/5_foobar.down.sql b/source/iofs/testdata/5_foobar.down.sql new file mode 100644 index 000000000..6dc96e206 --- /dev/null +++ b/source/iofs/testdata/5_foobar.down.sql @@ -0,0 +1 @@ +5 down diff --git a/source/iofs/testdata/7_foobar.down.sql b/source/iofs/testdata/7_foobar.down.sql new file mode 100644 index 000000000..46636016b --- /dev/null +++ b/source/iofs/testdata/7_foobar.down.sql @@ -0,0 +1 @@ +7 down diff --git a/source/iofs/testdata/7_foobar.up.sql b/source/iofs/testdata/7_foobar.up.sql new file mode 100644 index 000000000..cdbc410ee --- /dev/null +++ b/source/iofs/testdata/7_foobar.up.sql @@ -0,0 +1 @@ +7 up From 65cbb0ffc3d0d963277d58b592d8a37e77b67576 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Fri, 13 Nov 2020 18:05:24 +0900 Subject: [PATCH 02/15] Refactor iofs.Iofs to be independent of httpfs --- Makefile | 2 +- internal/cli/build_iofs.go | 8 +++ source/errors.go | 9 ++- source/iofs/README.md | 2 +- source/iofs/iofs.go | 134 +++++++++++++++++++++++++++++++++++-- 5 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 internal/cli/build_iofs.go diff --git a/Makefile b/Makefile index 152cdb7bd..ddf03bead 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -SOURCE ?= file go_bindata github github_ee bitbucket aws_s3 google_cloud_storage godoc_vfs gitlab +SOURCE ?= file go_bindata github github_ee bitbucket aws_s3 google_cloud_storage godoc_vfs gitlab iofs DATABASE ?= postgres mysql redshift cassandra spanner cockroachdb clickhouse mongodb sqlserver firebird neo4j DATABASE_TEST ?= $(DATABASE) sqlite sqlcipher VERSION ?= $(shell git describe --tags 2>/dev/null | cut -c 2-) diff --git a/internal/cli/build_iofs.go b/internal/cli/build_iofs.go new file mode 100644 index 000000000..2a1346029 --- /dev/null +++ b/internal/cli/build_iofs.go @@ -0,0 +1,8 @@ +// +build go1.16 +// +build iofs + +package cli + +import ( + _ "github.com/golang-migrate/migrate/v4/source/iofs" +) diff --git a/source/errors.go b/source/errors.go index 93d66e0d4..50203d934 100644 --- a/source/errors.go +++ b/source/errors.go @@ -1,15 +1,18 @@ package source -import "os" - // ErrDuplicateMigration is an error type for reporting duplicate migration // files. type ErrDuplicateMigration struct { Migration - os.FileInfo + FileInfo } // Error implements error interface. func (e ErrDuplicateMigration) Error() string { return "duplicate migration file: " + e.Name() } + +// FileInfo is the interface that extracts the minimum required function from os.FileInfo by ErrDuplicateMigration. +type FileInfo interface { + Name() string +} diff --git a/source/iofs/README.md b/source/iofs/README.md index 4a72504bf..c24745694 100644 --- a/source/iofs/README.md +++ b/source/iofs/README.md @@ -2,7 +2,7 @@ Driver with file system interface (`io/fs#FS`) supported from Go 1.16. -This Driver cannot be used with versions below Go 1.15. +This Driver cannot be used with Go versions 1.15 and below. ## Usage diff --git a/source/iofs/iofs.go b/source/iofs/iofs.go index 43f820e9c..f889997ed 100644 --- a/source/iofs/iofs.go +++ b/source/iofs/iofs.go @@ -5,11 +5,12 @@ package iofs import ( "errors" "fmt" + "io" "io/fs" - "net/http" + "path" + "strconv" "github.com/golang-migrate/migrate/v4/source" - "github.com/golang-migrate/migrate/v4/source/httpfs" ) func init() { @@ -18,7 +19,9 @@ func init() { // Iofs is a source driver for io/fs#FS. type Iofs struct { - httpfs.PartialDriver + migrations *source.Migrations + fsys fs.ReadDirFS + path string } // Open by url does not supported with Iofs. @@ -27,10 +30,131 @@ func (i *Iofs) Open(url string) (source.Driver, error) { } // WithInstance wraps io/fs#FS as source.Driver. -func WithInstance(fsys fs.FS, path string) (source.Driver, error) { +func WithInstance(fsys fs.ReadDirFS, path string) (source.Driver, error) { var i Iofs - if err := i.Init(http.FS(fsys), path); err != nil { + if err := i.Init(fsys, path); err != nil { return nil, fmt.Errorf("failed to init driver with path %s: %w", path, err) } return &i, nil } + +// Init prepares not initialized Iofs instance to read migrations from a +// fs.ReadDirFS instance and a relative path. +func (p *Iofs) Init(fsys fs.ReadDirFS, path string) error { + entries, err := fsys.ReadDir(path) + if err != nil { + return err + } + + ms := source.NewMigrations() + for _, e := range entries { + if e.IsDir() { + continue + } + m, err := source.DefaultParse(e.Name()) + if err != nil { + continue + } + if !ms.Append(m) { + return source.ErrDuplicateMigration{ + Migration: *m, + FileInfo: e, + } + } + } + + p.fsys = fsys + p.path = path + p.migrations = ms + return nil +} + +// Close is part of source.Driver interface implementation. This is a no-op. +func (p *Iofs) Close() error { + return nil +} + +// First is part of source.Driver interface implementation. +func (p *Iofs) First() (version uint, err error) { + if version, ok := p.migrations.First(); ok { + return version, nil + } + return 0, &fs.PathError{ + Op: "first", + Path: p.path, + Err: fs.ErrNotExist, + } +} + +// Prev is part of source.Driver interface implementation. +func (p *Iofs) Prev(version uint) (prevVersion uint, err error) { + if version, ok := p.migrations.Prev(version); ok { + return version, nil + } + return 0, &fs.PathError{ + Op: "prev for version " + strconv.FormatUint(uint64(version), 10), + Path: p.path, + Err: fs.ErrNotExist, + } +} + +// Next is part of source.Driver interface implementation. +func (p *Iofs) Next(version uint) (nextVersion uint, err error) { + if version, ok := p.migrations.Next(version); ok { + return version, nil + } + return 0, &fs.PathError{ + Op: "next for version " + strconv.FormatUint(uint64(version), 10), + Path: p.path, + Err: fs.ErrNotExist, + } +} + +// ReadUp is part of source.Driver interface implementation. +func (p *Iofs) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { + if m, ok := p.migrations.Up(version); ok { + body, err := p.open(path.Join(p.path, m.Raw)) + if err != nil { + return nil, "", err + } + return body, m.Identifier, nil + } + return nil, "", &fs.PathError{ + Op: "read up for version " + strconv.FormatUint(uint64(version), 10), + Path: p.path, + Err: fs.ErrNotExist, + } +} + +// ReadDown is part of source.Driver interface implementation. +func (p *Iofs) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { + if m, ok := p.migrations.Down(version); ok { + body, err := p.open(path.Join(p.path, m.Raw)) + if err != nil { + return nil, "", err + } + return body, m.Identifier, nil + } + return nil, "", &fs.PathError{ + Op: "read down for version " + strconv.FormatUint(uint64(version), 10), + Path: p.path, + Err: fs.ErrNotExist, + } +} + +func (p *Iofs) open(path string) (fs.File, error) { + f, err := p.fsys.Open(path) + if err == nil { + return f, nil + } + // Some non-standard file systems may return errors that don't include the path, that + // makes debugging harder. + if !errors.As(err, new(*fs.PathError)) { + err = &fs.PathError{ + Op: "open", + Path: path, + Err: err, + } + } + return nil, err +} From e18e378b2065bfc93302652a75a0ef31d3486c12 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Sat, 14 Nov 2020 21:56:20 +0900 Subject: [PATCH 03/15] Rename Iofs to IoFS, accepts fs.FS and use fs.DirEntry.Info() to undo the changes in ErrDuplicateMigration --- source/errors.go | 9 +++----- source/iofs/iofs.go | 44 ++++++++++++++++++++++------------------ source/iofs/iofs_test.go | 2 +- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/source/errors.go b/source/errors.go index 50203d934..93d66e0d4 100644 --- a/source/errors.go +++ b/source/errors.go @@ -1,18 +1,15 @@ package source +import "os" + // ErrDuplicateMigration is an error type for reporting duplicate migration // files. type ErrDuplicateMigration struct { Migration - FileInfo + os.FileInfo } // Error implements error interface. func (e ErrDuplicateMigration) Error() string { return "duplicate migration file: " + e.Name() } - -// FileInfo is the interface that extracts the minimum required function from os.FileInfo by ErrDuplicateMigration. -type FileInfo interface { - Name() string -} diff --git a/source/iofs/iofs.go b/source/iofs/iofs.go index f889997ed..ca79ba29f 100644 --- a/source/iofs/iofs.go +++ b/source/iofs/iofs.go @@ -14,34 +14,34 @@ import ( ) func init() { - source.Register("iofs", &Iofs{}) + source.Register("iofs", &IoFS{}) } -// Iofs is a source driver for io/fs#FS. -type Iofs struct { +// IoFS is a source driver for io/fs#FS. +type IoFS struct { migrations *source.Migrations - fsys fs.ReadDirFS + fsys fs.FS path string } -// Open by url does not supported with Iofs. -func (i *Iofs) Open(url string) (source.Driver, error) { +// Open by url does not supported with IoFS. +func (i *IoFS) Open(url string) (source.Driver, error) { return nil, errors.New("iofs driver does not support open by url") } // WithInstance wraps io/fs#FS as source.Driver. -func WithInstance(fsys fs.ReadDirFS, path string) (source.Driver, error) { - var i Iofs +func WithInstance(fsys fs.FS, path string) (source.Driver, error) { + var i IoFS if err := i.Init(fsys, path); err != nil { return nil, fmt.Errorf("failed to init driver with path %s: %w", path, err) } return &i, nil } -// Init prepares not initialized Iofs instance to read migrations from a -// fs.ReadDirFS instance and a relative path. -func (p *Iofs) Init(fsys fs.ReadDirFS, path string) error { - entries, err := fsys.ReadDir(path) +// Init prepares not initialized IoFS instance to read migrations from a +// fs.FS instance and a relative path. +func (p *IoFS) Init(fsys fs.FS, path string) error { + entries, err := fs.ReadDir(fsys, path) if err != nil { return err } @@ -55,10 +55,14 @@ func (p *Iofs) Init(fsys fs.ReadDirFS, path string) error { if err != nil { continue } + file, err := e.Info() + if err != nil { + continue + } if !ms.Append(m) { return source.ErrDuplicateMigration{ Migration: *m, - FileInfo: e, + FileInfo: file, } } } @@ -70,12 +74,12 @@ func (p *Iofs) Init(fsys fs.ReadDirFS, path string) error { } // Close is part of source.Driver interface implementation. This is a no-op. -func (p *Iofs) Close() error { +func (p *IoFS) Close() error { return nil } // First is part of source.Driver interface implementation. -func (p *Iofs) First() (version uint, err error) { +func (p *IoFS) First() (version uint, err error) { if version, ok := p.migrations.First(); ok { return version, nil } @@ -87,7 +91,7 @@ func (p *Iofs) First() (version uint, err error) { } // Prev is part of source.Driver interface implementation. -func (p *Iofs) Prev(version uint) (prevVersion uint, err error) { +func (p *IoFS) Prev(version uint) (prevVersion uint, err error) { if version, ok := p.migrations.Prev(version); ok { return version, nil } @@ -99,7 +103,7 @@ func (p *Iofs) Prev(version uint) (prevVersion uint, err error) { } // Next is part of source.Driver interface implementation. -func (p *Iofs) Next(version uint) (nextVersion uint, err error) { +func (p *IoFS) Next(version uint) (nextVersion uint, err error) { if version, ok := p.migrations.Next(version); ok { return version, nil } @@ -111,7 +115,7 @@ func (p *Iofs) Next(version uint) (nextVersion uint, err error) { } // ReadUp is part of source.Driver interface implementation. -func (p *Iofs) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { +func (p *IoFS) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { if m, ok := p.migrations.Up(version); ok { body, err := p.open(path.Join(p.path, m.Raw)) if err != nil { @@ -127,7 +131,7 @@ func (p *Iofs) ReadUp(version uint) (r io.ReadCloser, identifier string, err err } // ReadDown is part of source.Driver interface implementation. -func (p *Iofs) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { +func (p *IoFS) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { if m, ok := p.migrations.Down(version); ok { body, err := p.open(path.Join(p.path, m.Raw)) if err != nil { @@ -142,7 +146,7 @@ func (p *Iofs) ReadDown(version uint) (r io.ReadCloser, identifier string, err e } } -func (p *Iofs) open(path string) (fs.File, error) { +func (p *IoFS) open(path string) (fs.File, error) { f, err := p.fsys.Open(path) if err == nil { return f, nil diff --git a/source/iofs/iofs_test.go b/source/iofs/iofs_test.go index e2bfba2d8..6f1185f39 100644 --- a/source/iofs/iofs_test.go +++ b/source/iofs/iofs_test.go @@ -23,7 +23,7 @@ func Test(t *testing.T) { } func TestOpen(t *testing.T) { - i := new(iofs.Iofs) + i := new(iofs.IoFS) _, err := i.Open("") if err == nil { t.Fatal("iofs driver does not support open by url") From e9ae08da0c5abc15d567294012205073e92e35d5 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Sun, 15 Nov 2020 13:38:32 +0900 Subject: [PATCH 04/15] fix documents and migration directory for testing --- source/iofs/README.md | 37 +------------------ source/iofs/doc.go | 10 +++++ source/iofs/example_test.go | 28 ++++++++++++++ source/iofs/iofs_test.go | 7 ++-- .../{ => migrations}/1_foobar.down.sql | 0 .../testdata/{ => migrations}/1_foobar.up.sql | 0 .../testdata/{ => migrations}/3_foobar.up.sql | 0 .../{ => migrations}/4_foobar.down.sql | 0 .../testdata/{ => migrations}/4_foobar.up.sql | 0 .../{ => migrations}/5_foobar.down.sql | 0 .../{ => migrations}/7_foobar.down.sql | 0 .../testdata/{ => migrations}/7_foobar.up.sql | 0 12 files changed, 42 insertions(+), 40 deletions(-) create mode 100644 source/iofs/doc.go create mode 100644 source/iofs/example_test.go rename source/iofs/testdata/{ => migrations}/1_foobar.down.sql (100%) rename source/iofs/testdata/{ => migrations}/1_foobar.up.sql (100%) rename source/iofs/testdata/{ => migrations}/3_foobar.up.sql (100%) rename source/iofs/testdata/{ => migrations}/4_foobar.down.sql (100%) rename source/iofs/testdata/{ => migrations}/4_foobar.up.sql (100%) rename source/iofs/testdata/{ => migrations}/5_foobar.down.sql (100%) rename source/iofs/testdata/{ => migrations}/7_foobar.down.sql (100%) rename source/iofs/testdata/{ => migrations}/7_foobar.up.sql (100%) diff --git a/source/iofs/README.md b/source/iofs/README.md index c24745694..d75b328b9 100644 --- a/source/iofs/README.md +++ b/source/iofs/README.md @@ -1,38 +1,3 @@ # iofs -Driver with file system interface (`io/fs#FS`) supported from Go 1.16. - -This Driver cannot be used with Go versions 1.15 and below. - -## Usage - -Directory embedding example - -```go -package main - -import ( - "embed" - "log" - - "github.com/golang-migrate/migrate/v4" - _ "github.com/golang-migrate/migrate/v4/database/postgres" - "github.com/golang-migrate/migrate/v4/source/iofs" -) - -//go:embed migrations -var fs embed.FS - -func main() { - d, err := iofs.WithInstance(fs, "migrations") - if err != nil { - log.Fatal(err) - } - m, err := migrate.NewWithSourceInstance("iofs", d, "postgres://postgres@localhost/postgres?sslmode=disable") - if err != nil { - log.Fatal(err) - } - err = m.Up() - // ... -} -``` +https://pkg.go.dev/github.com/golang-migrate/migrate/v4/source/iofs diff --git a/source/iofs/doc.go b/source/iofs/doc.go new file mode 100644 index 000000000..6b2c862e0 --- /dev/null +++ b/source/iofs/doc.go @@ -0,0 +1,10 @@ +/* +Package iofs provides the Go 1.16+ io/fs#FS driver. + +It can accept various file systems (like embed.FS, archive/zip#Reader) implementing io/fs#FS. + +This driver cannot be used with Go versions 1.15 and below. + +Also, Opening with a URL scheme is not supported. +*/ +package iofs diff --git a/source/iofs/example_test.go b/source/iofs/example_test.go new file mode 100644 index 000000000..f8d1e7571 --- /dev/null +++ b/source/iofs/example_test.go @@ -0,0 +1,28 @@ +// +build go1.16 + +package iofs_test + +import ( + "embed" + "log" + + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/golang-migrate/migrate/v4/source/iofs" +) + +//go:embed testdata/migrations/*.sql +var fs embed.FS + +func Example() { + d, err := iofs.WithInstance(fs, "testdata/migrations") + if err != nil { + log.Fatal(err) + } + m, err := migrate.NewWithSourceInstance("iofs", d, "postgres://postgres@localhost/postgres?sslmode=disable") + if err != nil { + log.Fatal(err) + } + err = m.Up() + // ... +} diff --git a/source/iofs/iofs_test.go b/source/iofs/iofs_test.go index 6f1185f39..16f2055e6 100644 --- a/source/iofs/iofs_test.go +++ b/source/iofs/iofs_test.go @@ -10,11 +10,10 @@ import ( st "github.com/golang-migrate/migrate/v4/source/testing" ) -//go:embed testdata -var fs embed.FS - func Test(t *testing.T) { - d, err := iofs.WithInstance(fs, "testdata") + //go:embed testdata/migrations/*.sql + var fs embed.FS + d, err := iofs.WithInstance(fs, "testdata/migrations") if err != nil { t.Fatal(err) } diff --git a/source/iofs/testdata/1_foobar.down.sql b/source/iofs/testdata/migrations/1_foobar.down.sql similarity index 100% rename from source/iofs/testdata/1_foobar.down.sql rename to source/iofs/testdata/migrations/1_foobar.down.sql diff --git a/source/iofs/testdata/1_foobar.up.sql b/source/iofs/testdata/migrations/1_foobar.up.sql similarity index 100% rename from source/iofs/testdata/1_foobar.up.sql rename to source/iofs/testdata/migrations/1_foobar.up.sql diff --git a/source/iofs/testdata/3_foobar.up.sql b/source/iofs/testdata/migrations/3_foobar.up.sql similarity index 100% rename from source/iofs/testdata/3_foobar.up.sql rename to source/iofs/testdata/migrations/3_foobar.up.sql diff --git a/source/iofs/testdata/4_foobar.down.sql b/source/iofs/testdata/migrations/4_foobar.down.sql similarity index 100% rename from source/iofs/testdata/4_foobar.down.sql rename to source/iofs/testdata/migrations/4_foobar.down.sql diff --git a/source/iofs/testdata/4_foobar.up.sql b/source/iofs/testdata/migrations/4_foobar.up.sql similarity index 100% rename from source/iofs/testdata/4_foobar.up.sql rename to source/iofs/testdata/migrations/4_foobar.up.sql diff --git a/source/iofs/testdata/5_foobar.down.sql b/source/iofs/testdata/migrations/5_foobar.down.sql similarity index 100% rename from source/iofs/testdata/5_foobar.down.sql rename to source/iofs/testdata/migrations/5_foobar.down.sql diff --git a/source/iofs/testdata/7_foobar.down.sql b/source/iofs/testdata/migrations/7_foobar.down.sql similarity index 100% rename from source/iofs/testdata/7_foobar.down.sql rename to source/iofs/testdata/migrations/7_foobar.down.sql diff --git a/source/iofs/testdata/7_foobar.up.sql b/source/iofs/testdata/migrations/7_foobar.up.sql similarity index 100% rename from source/iofs/testdata/7_foobar.up.sql rename to source/iofs/testdata/migrations/7_foobar.up.sql From 416f878661f8409d2e111b267b0a14d66a1540a4 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Sun, 15 Nov 2020 17:48:53 +0900 Subject: [PATCH 05/15] Fix iofs.Driver so that it can be used by embedding it like httpfs.PartialDriver --- source/iofs/example_test.go | 2 +- source/iofs/iofs.go | 75 ++++++++++++++++++------------------- source/iofs/iofs_test.go | 10 +---- 3 files changed, 38 insertions(+), 49 deletions(-) diff --git a/source/iofs/example_test.go b/source/iofs/example_test.go index f8d1e7571..6b55f5e6e 100644 --- a/source/iofs/example_test.go +++ b/source/iofs/example_test.go @@ -15,7 +15,7 @@ import ( var fs embed.FS func Example() { - d, err := iofs.WithInstance(fs, "testdata/migrations") + d, err := iofs.NewDriver(fs, "testdata/migrations") if err != nil { log.Fatal(err) } diff --git a/source/iofs/iofs.go b/source/iofs/iofs.go index ca79ba29f..b90c008f1 100644 --- a/source/iofs/iofs.go +++ b/source/iofs/iofs.go @@ -13,34 +13,31 @@ import ( "github.com/golang-migrate/migrate/v4/source" ) -func init() { - source.Register("iofs", &IoFS{}) -} - -// IoFS is a source driver for io/fs#FS. -type IoFS struct { +// Driver is a source driver that wraps io/fs#FS. +type Driver struct { migrations *source.Migrations fsys fs.FS path string } -// Open by url does not supported with IoFS. -func (i *IoFS) Open(url string) (source.Driver, error) { - return nil, errors.New("iofs driver does not support open by url") -} - -// WithInstance wraps io/fs#FS as source.Driver. -func WithInstance(fsys fs.FS, path string) (source.Driver, error) { - var i IoFS +// NewDriver returns a new Driver from io/fs#FS and a relative path. +func NewDriver(fsys fs.FS, path string) (source.Driver, error) { + var i Driver if err := i.Init(fsys, path); err != nil { return nil, fmt.Errorf("failed to init driver with path %s: %w", path, err) } return &i, nil } +// Open is part of source.Driver interface implementation. +// Open panics when called directly. +func (i *Driver) Open(url string) (source.Driver, error) { + panic("iofs: Driver does not support open with url") +} + // Init prepares not initialized IoFS instance to read migrations from a -// fs.FS instance and a relative path. -func (p *IoFS) Init(fsys fs.FS, path string) error { +// io/fs#FS instance and a relative path. +func (i *Driver) Init(fsys fs.FS, path string) error { entries, err := fs.ReadDir(fsys, path) if err != nil { return err @@ -67,57 +64,57 @@ func (p *IoFS) Init(fsys fs.FS, path string) error { } } - p.fsys = fsys - p.path = path - p.migrations = ms + i.fsys = fsys + i.path = path + i.migrations = ms return nil } // Close is part of source.Driver interface implementation. This is a no-op. -func (p *IoFS) Close() error { +func (i *Driver) Close() error { return nil } // First is part of source.Driver interface implementation. -func (p *IoFS) First() (version uint, err error) { - if version, ok := p.migrations.First(); ok { +func (i *Driver) First() (version uint, err error) { + if version, ok := i.migrations.First(); ok { return version, nil } return 0, &fs.PathError{ Op: "first", - Path: p.path, + Path: i.path, Err: fs.ErrNotExist, } } // Prev is part of source.Driver interface implementation. -func (p *IoFS) Prev(version uint) (prevVersion uint, err error) { - if version, ok := p.migrations.Prev(version); ok { +func (i *Driver) Prev(version uint) (prevVersion uint, err error) { + if version, ok := i.migrations.Prev(version); ok { return version, nil } return 0, &fs.PathError{ Op: "prev for version " + strconv.FormatUint(uint64(version), 10), - Path: p.path, + Path: i.path, Err: fs.ErrNotExist, } } // Next is part of source.Driver interface implementation. -func (p *IoFS) Next(version uint) (nextVersion uint, err error) { - if version, ok := p.migrations.Next(version); ok { +func (i *Driver) Next(version uint) (nextVersion uint, err error) { + if version, ok := i.migrations.Next(version); ok { return version, nil } return 0, &fs.PathError{ Op: "next for version " + strconv.FormatUint(uint64(version), 10), - Path: p.path, + Path: i.path, Err: fs.ErrNotExist, } } // ReadUp is part of source.Driver interface implementation. -func (p *IoFS) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { - if m, ok := p.migrations.Up(version); ok { - body, err := p.open(path.Join(p.path, m.Raw)) +func (i *Driver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { + if m, ok := i.migrations.Up(version); ok { + body, err := i.open(path.Join(i.path, m.Raw)) if err != nil { return nil, "", err } @@ -125,15 +122,15 @@ func (p *IoFS) ReadUp(version uint) (r io.ReadCloser, identifier string, err err } return nil, "", &fs.PathError{ Op: "read up for version " + strconv.FormatUint(uint64(version), 10), - Path: p.path, + Path: i.path, Err: fs.ErrNotExist, } } // ReadDown is part of source.Driver interface implementation. -func (p *IoFS) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { - if m, ok := p.migrations.Down(version); ok { - body, err := p.open(path.Join(p.path, m.Raw)) +func (i *Driver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { + if m, ok := i.migrations.Down(version); ok { + body, err := i.open(path.Join(i.path, m.Raw)) if err != nil { return nil, "", err } @@ -141,13 +138,13 @@ func (p *IoFS) ReadDown(version uint) (r io.ReadCloser, identifier string, err e } return nil, "", &fs.PathError{ Op: "read down for version " + strconv.FormatUint(uint64(version), 10), - Path: p.path, + Path: i.path, Err: fs.ErrNotExist, } } -func (p *IoFS) open(path string) (fs.File, error) { - f, err := p.fsys.Open(path) +func (i *Driver) open(path string) (fs.File, error) { + f, err := i.fsys.Open(path) if err == nil { return f, nil } diff --git a/source/iofs/iofs_test.go b/source/iofs/iofs_test.go index 16f2055e6..1a440d8ce 100644 --- a/source/iofs/iofs_test.go +++ b/source/iofs/iofs_test.go @@ -13,18 +13,10 @@ import ( func Test(t *testing.T) { //go:embed testdata/migrations/*.sql var fs embed.FS - d, err := iofs.WithInstance(fs, "testdata/migrations") + d, err := iofs.NewDriver(fs, "testdata/migrations") if err != nil { t.Fatal(err) } st.Test(t, d) } - -func TestOpen(t *testing.T) { - i := new(iofs.IoFS) - _, err := i.Open("") - if err == nil { - t.Fatal("iofs driver does not support open by url") - } -} From 45ec3dc77310866e7119079620a8a3c1f078c340 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Sun, 15 Nov 2020 18:58:51 +0900 Subject: [PATCH 06/15] iofs.Driver closes the file system if possible --- source/iofs/iofs.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source/iofs/iofs.go b/source/iofs/iofs.go index b90c008f1..b95e757d4 100644 --- a/source/iofs/iofs.go +++ b/source/iofs/iofs.go @@ -70,9 +70,14 @@ func (i *Driver) Init(fsys fs.FS, path string) error { return nil } -// Close is part of source.Driver interface implementation. This is a no-op. -func (i *Driver) Close() error { - return nil +// Close is part of source.Driver interface implementation. +// Closes the file system if possible. +func (d *Driver) Close() error { + c, ok := d.fsys.(io.Closer) + if !ok { + return nil + } + return c.Close() } // First is part of source.Driver interface implementation. From 3af8acfc6c7f2bf11557a4f7b32a9b3301202a0b Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Sun, 15 Nov 2020 18:59:23 +0900 Subject: [PATCH 07/15] Rename receiver --- source/iofs/iofs.go | 48 ++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/source/iofs/iofs.go b/source/iofs/iofs.go index b95e757d4..63a2330ba 100644 --- a/source/iofs/iofs.go +++ b/source/iofs/iofs.go @@ -31,13 +31,13 @@ func NewDriver(fsys fs.FS, path string) (source.Driver, error) { // Open is part of source.Driver interface implementation. // Open panics when called directly. -func (i *Driver) Open(url string) (source.Driver, error) { +func (d *Driver) Open(url string) (source.Driver, error) { panic("iofs: Driver does not support open with url") } // Init prepares not initialized IoFS instance to read migrations from a // io/fs#FS instance and a relative path. -func (i *Driver) Init(fsys fs.FS, path string) error { +func (d *Driver) Init(fsys fs.FS, path string) error { entries, err := fs.ReadDir(fsys, path) if err != nil { return err @@ -64,9 +64,9 @@ func (i *Driver) Init(fsys fs.FS, path string) error { } } - i.fsys = fsys - i.path = path - i.migrations = ms + d.fsys = fsys + d.path = path + d.migrations = ms return nil } @@ -81,45 +81,45 @@ func (d *Driver) Close() error { } // First is part of source.Driver interface implementation. -func (i *Driver) First() (version uint, err error) { - if version, ok := i.migrations.First(); ok { +func (d *Driver) First() (version uint, err error) { + if version, ok := d.migrations.First(); ok { return version, nil } return 0, &fs.PathError{ Op: "first", - Path: i.path, + Path: d.path, Err: fs.ErrNotExist, } } // Prev is part of source.Driver interface implementation. -func (i *Driver) Prev(version uint) (prevVersion uint, err error) { - if version, ok := i.migrations.Prev(version); ok { +func (d *Driver) Prev(version uint) (prevVersion uint, err error) { + if version, ok := d.migrations.Prev(version); ok { return version, nil } return 0, &fs.PathError{ Op: "prev for version " + strconv.FormatUint(uint64(version), 10), - Path: i.path, + Path: d.path, Err: fs.ErrNotExist, } } // Next is part of source.Driver interface implementation. -func (i *Driver) Next(version uint) (nextVersion uint, err error) { - if version, ok := i.migrations.Next(version); ok { +func (d *Driver) Next(version uint) (nextVersion uint, err error) { + if version, ok := d.migrations.Next(version); ok { return version, nil } return 0, &fs.PathError{ Op: "next for version " + strconv.FormatUint(uint64(version), 10), - Path: i.path, + Path: d.path, Err: fs.ErrNotExist, } } // ReadUp is part of source.Driver interface implementation. -func (i *Driver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { - if m, ok := i.migrations.Up(version); ok { - body, err := i.open(path.Join(i.path, m.Raw)) +func (d *Driver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { + if m, ok := d.migrations.Up(version); ok { + body, err := d.open(path.Join(d.path, m.Raw)) if err != nil { return nil, "", err } @@ -127,15 +127,15 @@ func (i *Driver) ReadUp(version uint) (r io.ReadCloser, identifier string, err e } return nil, "", &fs.PathError{ Op: "read up for version " + strconv.FormatUint(uint64(version), 10), - Path: i.path, + Path: d.path, Err: fs.ErrNotExist, } } // ReadDown is part of source.Driver interface implementation. -func (i *Driver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { - if m, ok := i.migrations.Down(version); ok { - body, err := i.open(path.Join(i.path, m.Raw)) +func (d *Driver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { + if m, ok := d.migrations.Down(version); ok { + body, err := d.open(path.Join(d.path, m.Raw)) if err != nil { return nil, "", err } @@ -143,13 +143,13 @@ func (i *Driver) ReadDown(version uint) (r io.ReadCloser, identifier string, err } return nil, "", &fs.PathError{ Op: "read down for version " + strconv.FormatUint(uint64(version), 10), - Path: i.path, + Path: d.path, Err: fs.ErrNotExist, } } -func (i *Driver) open(path string) (fs.File, error) { - f, err := i.fsys.Open(path) +func (d *Driver) open(path string) (fs.File, error) { + f, err := d.fsys.Open(path) if err == nil { return f, nil } From 0e9937b5de9c146d3038db4310dc1fe86472cc6a Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Sun, 15 Nov 2020 19:04:40 +0900 Subject: [PATCH 08/15] Refactor file driver to use iofs.Driver --- source/file/file.go | 27 ++++++--------------------- source/file/file_go115.go | 32 ++++++++++++++++++++++++++++++++ source/file/file_go116.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 21 deletions(-) create mode 100644 source/file/file_go115.go create mode 100644 source/file/file_go116.go diff --git a/source/file/file.go b/source/file/file.go index ee10d08d1..b56e5ddbd 100644 --- a/source/file/file.go +++ b/source/file/file.go @@ -1,31 +1,24 @@ package file import ( - "net/http" nurl "net/url" "os" "path/filepath" "github.com/golang-migrate/migrate/v4/source" - "github.com/golang-migrate/migrate/v4/source/httpfs" ) func init() { source.Register("file", &File{}) } -type File struct { - httpfs.PartialDriver - url string - path string -} +type File = file -func (f *File) Open(url string) (source.Driver, error) { +func parseURL(url string) (string, error) { u, err := nurl.Parse(url) if err != nil { - return nil, err + return "", err } - // concat host and path to restore full path // host might be `.` p := u.Opaque @@ -37,7 +30,7 @@ func (f *File) Open(url string) (source.Driver, error) { // default to current directory if no path wd, err := os.Getwd() if err != nil { - return nil, err + return "", err } p = wd @@ -45,17 +38,9 @@ func (f *File) Open(url string) (source.Driver, error) { // make path absolute if relative abs, err := filepath.Abs(p) if err != nil { - return nil, err + return "", err } p = abs } - - nf := &File{ - url: url, - path: p, - } - if err := nf.Init(http.Dir(p), ""); err != nil { - return nil, err - } - return nf, nil + return p, nil } diff --git a/source/file/file_go115.go b/source/file/file_go115.go new file mode 100644 index 000000000..6fd502277 --- /dev/null +++ b/source/file/file_go115.go @@ -0,0 +1,32 @@ +// +build !go1.16 + +package file + +import ( + "net/http" + + "github.com/golang-migrate/migrate/v4/source" + "github.com/golang-migrate/migrate/v4/source/httpfs" +) + +type file struct { + httpfs.PartialDriver + url string + path string +} + +func (f *file) Open(url string) (source.Driver, error) { + p, err := parseURL(url) + if err != nil { + return nil, err + } + + nf := &File{ + url: url, + path: p, + } + if err := nf.Init(http.Dir(p), ""); err != nil { + return nil, err + } + return nf, nil +} diff --git a/source/file/file_go116.go b/source/file/file_go116.go new file mode 100644 index 000000000..eca1b1394 --- /dev/null +++ b/source/file/file_go116.go @@ -0,0 +1,31 @@ +// +build go1.16 + +package file + +import ( + "os" + + "github.com/golang-migrate/migrate/v4/source" + "github.com/golang-migrate/migrate/v4/source/iofs" +) + +type file struct { + iofs.Driver + url string + path string +} + +func (f *file) Open(url string) (source.Driver, error) { + p, err := parseURL(url) + if err != nil { + return nil, err + } + nf := &file{ + url: url, + path: p, + } + if err := nf.Init(os.DirFS(p), "."); err != nil { + return nil, err + } + return nf, nil +} From 7fb518d4d79380415b737130d9fa38e9b2b5562a Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Sun, 15 Nov 2020 19:04:54 +0900 Subject: [PATCH 09/15] iofs build constraint is not needed so remove it --- Makefile | 2 +- internal/cli/build_iofs.go | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 internal/cli/build_iofs.go diff --git a/Makefile b/Makefile index ddf03bead..152cdb7bd 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -SOURCE ?= file go_bindata github github_ee bitbucket aws_s3 google_cloud_storage godoc_vfs gitlab iofs +SOURCE ?= file go_bindata github github_ee bitbucket aws_s3 google_cloud_storage godoc_vfs gitlab DATABASE ?= postgres mysql redshift cassandra spanner cockroachdb clickhouse mongodb sqlserver firebird neo4j DATABASE_TEST ?= $(DATABASE) sqlite sqlcipher VERSION ?= $(shell git describe --tags 2>/dev/null | cut -c 2-) diff --git a/internal/cli/build_iofs.go b/internal/cli/build_iofs.go deleted file mode 100644 index 2a1346029..000000000 --- a/internal/cli/build_iofs.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build go1.16 -// +build iofs - -package cli - -import ( - _ "github.com/golang-migrate/migrate/v4/source/iofs" -) From 3c0290b8815f2dd7e00ff34358e15d700eab2314 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Sun, 15 Nov 2020 19:08:13 +0900 Subject: [PATCH 10/15] Properly return an error in a corner case --- source/iofs/iofs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/iofs/iofs.go b/source/iofs/iofs.go index 63a2330ba..048b75bcb 100644 --- a/source/iofs/iofs.go +++ b/source/iofs/iofs.go @@ -50,11 +50,11 @@ func (d *Driver) Init(fsys fs.FS, path string) error { } m, err := source.DefaultParse(e.Name()) if err != nil { - continue + return err } file, err := e.Info() if err != nil { - continue + return err } if !ms.Append(m) { return source.ErrDuplicateMigration{ From 2cd12a63b50b7ac48d12dd0adc1ec4f4b49c3b93 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Sun, 15 Nov 2020 21:54:00 +0900 Subject: [PATCH 11/15] Increase golangci-lint timeout --- .golangci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index f447ee1ce..916eb75c7 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,6 @@ run: # timeout for analysis, e.g. 30s, 5m, default is 1m - timeout: 2m + timeout: 5m linters: enable: #- golint From ca6628d88ee8b51143861a9bb7f5b49c2fc6895d Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Sun, 15 Nov 2020 21:54:35 +0900 Subject: [PATCH 12/15] Fix example code --- source/iofs/example_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/iofs/example_test.go b/source/iofs/example_test.go index 6b55f5e6e..957710cbf 100644 --- a/source/iofs/example_test.go +++ b/source/iofs/example_test.go @@ -24,5 +24,8 @@ func Example() { log.Fatal(err) } err = m.Up() + if err != nil { + // ... + } // ... } From 58e98376671528d02bab9efa82a8fd50b3348cf5 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Tue, 17 Nov 2020 19:57:37 +0900 Subject: [PATCH 13/15] Revert "Increase golangci-lint timeout" This reverts commit 2cd12a63b50b7ac48d12dd0adc1ec4f4b49c3b93. --- .golangci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 916eb75c7..f447ee1ce 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,6 +1,6 @@ run: # timeout for analysis, e.g. 30s, 5m, default is 1m - timeout: 5m + timeout: 2m linters: enable: #- golint From 321662e41ab5f2a8b55d35098f60f45fe5093c27 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Tue, 17 Nov 2020 20:15:14 +0900 Subject: [PATCH 14/15] unexport driver, add iofs.PartialDriver, remove type alias from file driver --- source/file/file.go | 2 -- source/file/file_go115.go | 4 ++-- source/file/file_go116.go | 8 +++---- source/iofs/example_test.go | 2 +- source/iofs/iofs.go | 47 ++++++++++++++++++++++--------------- source/iofs/iofs_test.go | 2 +- 6 files changed, 36 insertions(+), 29 deletions(-) diff --git a/source/file/file.go b/source/file/file.go index b56e5ddbd..82408b99a 100644 --- a/source/file/file.go +++ b/source/file/file.go @@ -12,8 +12,6 @@ func init() { source.Register("file", &File{}) } -type File = file - func parseURL(url string) (string, error) { u, err := nurl.Parse(url) if err != nil { diff --git a/source/file/file_go115.go b/source/file/file_go115.go index 6fd502277..e2857b364 100644 --- a/source/file/file_go115.go +++ b/source/file/file_go115.go @@ -9,13 +9,13 @@ import ( "github.com/golang-migrate/migrate/v4/source/httpfs" ) -type file struct { +type File struct { httpfs.PartialDriver url string path string } -func (f *file) Open(url string) (source.Driver, error) { +func (f *File) Open(url string) (source.Driver, error) { p, err := parseURL(url) if err != nil { return nil, err diff --git a/source/file/file_go116.go b/source/file/file_go116.go index eca1b1394..5203b4bdb 100644 --- a/source/file/file_go116.go +++ b/source/file/file_go116.go @@ -9,18 +9,18 @@ import ( "github.com/golang-migrate/migrate/v4/source/iofs" ) -type file struct { - iofs.Driver +type File struct { + iofs.PartialDriver url string path string } -func (f *file) Open(url string) (source.Driver, error) { +func (f *File) Open(url string) (source.Driver, error) { p, err := parseURL(url) if err != nil { return nil, err } - nf := &file{ + nf := &File{ url: url, path: p, } diff --git a/source/iofs/example_test.go b/source/iofs/example_test.go index 957710cbf..87d4e568b 100644 --- a/source/iofs/example_test.go +++ b/source/iofs/example_test.go @@ -15,7 +15,7 @@ import ( var fs embed.FS func Example() { - d, err := iofs.NewDriver(fs, "testdata/migrations") + d, err := iofs.New(fs, "testdata/migrations") if err != nil { log.Fatal(err) } diff --git a/source/iofs/iofs.go b/source/iofs/iofs.go index 048b75bcb..b558e79fe 100644 --- a/source/iofs/iofs.go +++ b/source/iofs/iofs.go @@ -13,16 +13,13 @@ import ( "github.com/golang-migrate/migrate/v4/source" ) -// Driver is a source driver that wraps io/fs#FS. -type Driver struct { - migrations *source.Migrations - fsys fs.FS - path string +type driver struct { + PartialDriver } -// NewDriver returns a new Driver from io/fs#FS and a relative path. -func NewDriver(fsys fs.FS, path string) (source.Driver, error) { - var i Driver +// New returns a new Driver from io/fs#FS and a relative path. +func New(fsys fs.FS, path string) (source.Driver, error) { + var i driver if err := i.Init(fsys, path); err != nil { return nil, fmt.Errorf("failed to init driver with path %s: %w", path, err) } @@ -31,13 +28,25 @@ func NewDriver(fsys fs.FS, path string) (source.Driver, error) { // Open is part of source.Driver interface implementation. // Open panics when called directly. -func (d *Driver) Open(url string) (source.Driver, error) { - panic("iofs: Driver does not support open with url") +func (d *driver) Open(url string) (source.Driver, error) { + panic("iofs: driver does not support open with url") +} + +// PartialDriver is a helper service for creating new source drivers working with +// io/fs.FS instances. It implements all source.Driver interface methods +// except for Open(). New driver could embed this struct and add missing Open() +// method. +// +// To prepare PartialDriver for use Init() function. +type PartialDriver struct { + migrations *source.Migrations + fsys fs.FS + path string } // Init prepares not initialized IoFS instance to read migrations from a // io/fs#FS instance and a relative path. -func (d *Driver) Init(fsys fs.FS, path string) error { +func (d *PartialDriver) Init(fsys fs.FS, path string) error { entries, err := fs.ReadDir(fsys, path) if err != nil { return err @@ -50,7 +59,7 @@ func (d *Driver) Init(fsys fs.FS, path string) error { } m, err := source.DefaultParse(e.Name()) if err != nil { - return err + continue } file, err := e.Info() if err != nil { @@ -72,7 +81,7 @@ func (d *Driver) Init(fsys fs.FS, path string) error { // Close is part of source.Driver interface implementation. // Closes the file system if possible. -func (d *Driver) Close() error { +func (d *PartialDriver) Close() error { c, ok := d.fsys.(io.Closer) if !ok { return nil @@ -81,7 +90,7 @@ func (d *Driver) Close() error { } // First is part of source.Driver interface implementation. -func (d *Driver) First() (version uint, err error) { +func (d *PartialDriver) First() (version uint, err error) { if version, ok := d.migrations.First(); ok { return version, nil } @@ -93,7 +102,7 @@ func (d *Driver) First() (version uint, err error) { } // Prev is part of source.Driver interface implementation. -func (d *Driver) Prev(version uint) (prevVersion uint, err error) { +func (d *PartialDriver) Prev(version uint) (prevVersion uint, err error) { if version, ok := d.migrations.Prev(version); ok { return version, nil } @@ -105,7 +114,7 @@ func (d *Driver) Prev(version uint) (prevVersion uint, err error) { } // Next is part of source.Driver interface implementation. -func (d *Driver) Next(version uint) (nextVersion uint, err error) { +func (d *PartialDriver) Next(version uint) (nextVersion uint, err error) { if version, ok := d.migrations.Next(version); ok { return version, nil } @@ -117,7 +126,7 @@ func (d *Driver) Next(version uint) (nextVersion uint, err error) { } // ReadUp is part of source.Driver interface implementation. -func (d *Driver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { +func (d *PartialDriver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { if m, ok := d.migrations.Up(version); ok { body, err := d.open(path.Join(d.path, m.Raw)) if err != nil { @@ -133,7 +142,7 @@ func (d *Driver) ReadUp(version uint) (r io.ReadCloser, identifier string, err e } // ReadDown is part of source.Driver interface implementation. -func (d *Driver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { +func (d *PartialDriver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { if m, ok := d.migrations.Down(version); ok { body, err := d.open(path.Join(d.path, m.Raw)) if err != nil { @@ -148,7 +157,7 @@ func (d *Driver) ReadDown(version uint) (r io.ReadCloser, identifier string, err } } -func (d *Driver) open(path string) (fs.File, error) { +func (d *PartialDriver) open(path string) (fs.File, error) { f, err := d.fsys.Open(path) if err == nil { return f, nil diff --git a/source/iofs/iofs_test.go b/source/iofs/iofs_test.go index 1a440d8ce..2e0def2c7 100644 --- a/source/iofs/iofs_test.go +++ b/source/iofs/iofs_test.go @@ -13,7 +13,7 @@ import ( func Test(t *testing.T) { //go:embed testdata/migrations/*.sql var fs embed.FS - d, err := iofs.NewDriver(fs, "testdata/migrations") + d, err := iofs.New(fs, "testdata/migrations") if err != nil { t.Fatal(err) } From 02da3b65068d842fe0da714547ff7a66fe966854 Mon Sep 17 00:00:00 2001 From: Mitsuo Heijo Date: Wed, 18 Nov 2020 18:54:47 +0900 Subject: [PATCH 15/15] do not panic in Open, return error --- source/iofs/iofs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/iofs/iofs.go b/source/iofs/iofs.go index b558e79fe..f76e44387 100644 --- a/source/iofs/iofs.go +++ b/source/iofs/iofs.go @@ -27,9 +27,9 @@ func New(fsys fs.FS, path string) (source.Driver, error) { } // Open is part of source.Driver interface implementation. -// Open panics when called directly. +// Open cannot be called on the iofs passthrough driver. func (d *driver) Open(url string) (source.Driver, error) { - panic("iofs: driver does not support open with url") + return nil, errors.New("Open() cannot be called on the iofs passthrough driver") } // PartialDriver is a helper service for creating new source drivers working with