github.com/mmrath/gobase@v0.0.1/apps/db_migration/internal/dir_migrate.go (about)

     1  package internal
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	nurl "net/url"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strconv"
    13  
    14  	"github.com/golang-migrate/migrate/v4/source"
    15  )
    16  
    17  // Regex matches the following pattern:
    18  //  123_name.up.ext
    19  //  123_name.down.ext
    20  var Regex = regexp.MustCompile(`^([0-9]+)_(.*)`)
    21  
    22  // Parse returns Migration for matching Regex pattern.
    23  func Parse(raw string) (*uint, *string, error) {
    24  	m := Regex.FindStringSubmatch(raw)
    25  	if len(m) == 3 {
    26  		versionUint64, err := strconv.ParseUint(m[1], 10, 64)
    27  		if err != nil {
    28  			return nil, nil, err
    29  		}
    30  		version := uint(versionUint64)
    31  		return &version, &m[2], nil
    32  	}
    33  	return nil, nil, source.ErrParse
    34  }
    35  
    36  func init() {
    37  	source.Register("dir", &File{})
    38  }
    39  
    40  type File struct {
    41  	url        string
    42  	path       string
    43  	migrations *source.Migrations
    44  }
    45  
    46  func (f *File) Open(url string) (source.Driver, error) {
    47  	u, err := nurl.Parse(url)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	// concat host and path to restore full path
    53  	// host might be `.`
    54  	p := u.Host + u.Path
    55  
    56  	if len(p) == 0 {
    57  		// default to current directory if no path
    58  		wd, err := os.Getwd()
    59  		if err != nil {
    60  			return nil, err
    61  		}
    62  		p = wd
    63  
    64  	} else if p[0:1] == "." || p[0:1] != "/" {
    65  		// make path absolute if relative
    66  		abs, err := filepath.Abs(p)
    67  		if err != nil {
    68  			return nil, err
    69  		}
    70  		p = abs
    71  	}
    72  
    73  	// scan directory
    74  	files, err := ioutil.ReadDir(p)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	nf := &File{
    80  		url:        url,
    81  		path:       p,
    82  		migrations: source.NewMigrations(),
    83  	}
    84  
    85  	for _, fi := range files {
    86  		if fi.IsDir() {
    87  			v, id, err := Parse(fi.Name())
    88  			if err != nil {
    89  				continue // ignore files that we can't parse
    90  			}
    91  
    92  			//Up file exists
    93  			up := &source.Migration{
    94  				Version:    *v,
    95  				Identifier: *id,
    96  				Direction:  source.Up,
    97  				Raw:        filepath.Join(fi.Name(), "up.sql"),
    98  			}
    99  
   100  			down := &source.Migration{
   101  				Version:    *v,
   102  				Identifier: *id,
   103  				Direction:  source.Down,
   104  				Raw:        filepath.Join(fi.Name(), "down.sql"),
   105  			}
   106  
   107  			_, err = nf.appendMigration(up)
   108  			if err != nil {
   109  				return nil, err
   110  			}
   111  			_, err = nf.appendMigration(down)
   112  			if err != nil {
   113  				return nil, err
   114  			}
   115  
   116  		}
   117  	}
   118  	return nf, nil
   119  }
   120  
   121  func (f *File) appendMigration(m *source.Migration) (bool, error) {
   122  	if stat, err := os.Stat(path.Join(f.path, m.Raw)); err == nil && !stat.IsDir() {
   123  		if !f.migrations.Append(m) {
   124  			return false, fmt.Errorf("unable to parse file %v", m)
   125  		}
   126  	}
   127  	return true, nil
   128  }
   129  
   130  func (f *File) Close() error {
   131  	// nothing do to here
   132  	return nil
   133  }
   134  
   135  func (f *File) First() (version uint, err error) {
   136  	if v, ok := f.migrations.First(); !ok {
   137  		return 0, &os.PathError{Op: "first", Path: f.path, Err: os.ErrNotExist}
   138  	} else {
   139  		return v, nil
   140  	}
   141  }
   142  
   143  func (f *File) Prev(version uint) (prevVersion uint, err error) {
   144  	if v, ok := f.migrations.Prev(version); !ok {
   145  		return 0, &os.PathError{Op: fmt.Sprintf("prev for version %v", version), Path: f.path, Err: os.ErrNotExist}
   146  	} else {
   147  		return v, nil
   148  	}
   149  }
   150  
   151  func (f *File) Next(version uint) (nextVersion uint, err error) {
   152  	if v, ok := f.migrations.Next(version); !ok {
   153  		return 0, &os.PathError{Op: fmt.Sprintf("next for version %v", version), Path: f.path, Err: os.ErrNotExist}
   154  	} else {
   155  		return v, nil
   156  	}
   157  }
   158  
   159  func (f *File) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {
   160  	if m, ok := f.migrations.Up(version); ok {
   161  		r, err := os.Open(path.Join(f.path, m.Raw))
   162  		if err != nil {
   163  			return nil, "", err
   164  		}
   165  		return r, m.Identifier, nil
   166  	}
   167  	return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: f.path, Err: os.ErrNotExist}
   168  }
   169  
   170  func (f *File) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {
   171  	if m, ok := f.migrations.Down(version); ok {
   172  		r, err := os.Open(path.Join(f.path, m.Raw))
   173  		if err != nil {
   174  			return nil, "", err
   175  		}
   176  		return r, m.Identifier, nil
   177  	}
   178  	return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: f.path, Err: os.ErrNotExist}
   179  }