-
-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added custom iofs driver for gorm-migrate (#534)
- Loading branch information
Showing
2 changed files
with
209 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
package db | ||
|
||
/* | ||
* This file is a part of golang-migrate package. | ||
* Copied from https://github.com/golang-migrate/migrate/blob/14f6d3a92ff4c2581e60ca036375d737de047335/source/iofs/iofs.go | ||
* Commit: 14f6d3a92ff4c2581e60ca036375d737de047335 | ||
* Upstream code Licensed Under MIT License: https://github.com/golang-migrate/migrate/tree/14f6d3a92ff4c2581e60ca036375d737de047335?tab=License-1-ov-file | ||
* It has been modified to work with arm-based systems without breaking the backward compatibility of swiftwave due to integer overflow issue. | ||
* https://github.com/golang-migrate/migrate/issues/213 | ||
* We are converting the datetime format to unix timestamp to avoid integer overflow issue. | ||
*/ | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"github.com/fatih/color" | ||
"github.com/golang-migrate/migrate/v4/source" | ||
"io" | ||
"io/fs" | ||
"path" | ||
"regexp" | ||
"strconv" | ||
"time" | ||
) | ||
|
||
type driver struct { | ||
PartialDriver | ||
} | ||
|
||
var IOFSFileRegex = regexp.MustCompile(`^([0-9]+)_(.*)\.(` + string(source.Down) + `|` + string(source.Up) + `)\.(.*)$`) | ||
var versionTimeLayout = "20060102150405" | ||
|
||
// NewCustomIOFSDriver returns a new Driver from io/fs#FS and a relative path. | ||
func NewCustomIOFSDriver(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 cannot be called on the iofs passthrough driver. | ||
func (d *driver) Open(url string) (source.Driver, error) { | ||
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 | ||
// 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 *PartialDriver) Init(fsys fs.FS, path string) error { | ||
entries, err := fs.ReadDir(fsys, path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
ms := source.NewMigrations() | ||
for _, e := range entries { | ||
if e.IsDir() { | ||
continue | ||
} | ||
m, err := Parse(e.Name()) | ||
if err != nil { | ||
continue | ||
} | ||
file, err := e.Info() | ||
if err != nil { | ||
return err | ||
} | ||
if !ms.Append(m) { | ||
return source.ErrDuplicateMigration{ | ||
Migration: *m, | ||
FileInfo: file, | ||
} | ||
} | ||
} | ||
|
||
d.fsys = fsys | ||
d.path = path | ||
d.migrations = ms | ||
return nil | ||
} | ||
|
||
// Close is part of source.Driver interface implementation. | ||
// Closes the file system if possible. | ||
func (d *PartialDriver) Close() error { | ||
c, ok := d.fsys.(io.Closer) | ||
if !ok { | ||
return nil | ||
} | ||
return c.Close() | ||
} | ||
|
||
// First is part of source.Driver interface implementation. | ||
func (d *PartialDriver) First() (version uint, err error) { | ||
if version, ok := d.migrations.First(); ok { | ||
return version, nil | ||
} | ||
return 0, &fs.PathError{ | ||
Op: "first", | ||
Path: d.path, | ||
Err: fs.ErrNotExist, | ||
} | ||
} | ||
|
||
// Prev is part of source.Driver interface implementation. | ||
func (d *PartialDriver) 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: d.path, | ||
Err: fs.ErrNotExist, | ||
} | ||
} | ||
|
||
// Next is part of source.Driver interface implementation. | ||
func (d *PartialDriver) 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: d.path, | ||
Err: fs.ErrNotExist, | ||
} | ||
} | ||
|
||
// ReadUp is part of source.Driver interface implementation. | ||
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 { | ||
return nil, "", err | ||
} | ||
return body, m.Identifier, nil | ||
} | ||
return nil, "", &fs.PathError{ | ||
Op: "read up for version " + strconv.FormatUint(uint64(version), 10), | ||
Path: d.path, | ||
Err: fs.ErrNotExist, | ||
} | ||
} | ||
|
||
// ReadDown is part of source.Driver interface implementation. | ||
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 { | ||
return nil, "", err | ||
} | ||
return body, m.Identifier, nil | ||
} | ||
return nil, "", &fs.PathError{ | ||
Op: "read down for version " + strconv.FormatUint(uint64(version), 10), | ||
Path: d.path, | ||
Err: fs.ErrNotExist, | ||
} | ||
} | ||
|
||
func (d *PartialDriver) open(path string) (fs.File, error) { | ||
f, err := d.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 | ||
} | ||
|
||
// Parse returns Migration for matching IOFSFileRegex pattern. | ||
func Parse(raw string) (*source.Migration, error) { | ||
m := IOFSFileRegex.FindStringSubmatch(raw) | ||
if len(m) == 5 { | ||
t, err := time.Parse(versionTimeLayout, m[1]) | ||
if err != nil { | ||
color.Red("Invalid version format in migration file name") | ||
return nil, err | ||
} | ||
return &source.Migration{ | ||
Version: uint(t.Unix()), | ||
Identifier: m[2], | ||
Direction: source.Direction(m[3]), | ||
Raw: raw, | ||
}, nil | ||
} | ||
return nil, source.ErrParse | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters