github.com/gobuffalo/pop/v6@v6.1.2-0.20230426125638-01ebd5b92a24/migration_box.go (about)

     1  package pop
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/fs"
     7  	"strings"
     8  
     9  	"github.com/gobuffalo/pop/v6/logging"
    10  )
    11  
    12  // MigrationBox is a wrapper around fs.FS and Migrator.
    13  // This will allow you to run migrations from a fs.FS
    14  // inside of a compiled binary.
    15  type MigrationBox struct {
    16  	Migrator
    17  	FS fs.FS
    18  }
    19  
    20  // NewMigrationBox from a fs.FS and a Connection.
    21  func NewMigrationBox(fsys fs.FS, c *Connection) (MigrationBox, error) {
    22  	fm := MigrationBox{
    23  		Migrator: NewMigrator(c),
    24  		FS:       fsys,
    25  	}
    26  
    27  	runner := func(r io.Reader) func(mf Migration, tx *Connection) error {
    28  		return func(mf Migration, tx *Connection) error {
    29  			content, err := MigrationContent(mf, tx, r, true)
    30  			if err != nil {
    31  				return fmt.Errorf("error processing %s: %w", mf.Path, err)
    32  			}
    33  			if content == "" {
    34  				return nil
    35  			}
    36  			_, err = tx.Store.Exec(content)
    37  			if err != nil {
    38  				return fmt.Errorf("error executing %s, sql: %s: %w", mf.Path, content, err)
    39  			}
    40  			return nil
    41  		}
    42  	}
    43  
    44  	err := fm.findMigrations(runner)
    45  	if err != nil {
    46  		return fm, err
    47  	}
    48  
    49  	return fm, nil
    50  }
    51  
    52  func (fm *MigrationBox) findMigrations(runner func(r io.Reader) func(mf Migration, tx *Connection) error) error {
    53  	return fs.WalkDir(fm.FS, ".", func(path string, d fs.DirEntry, err error) error {
    54  		if err != nil {
    55  			return err
    56  		}
    57  
    58  		if d.IsDir() {
    59  			return nil
    60  		}
    61  
    62  		info, err := d.Info()
    63  		if err != nil {
    64  			return err
    65  		}
    66  
    67  		match, err := ParseMigrationFilename(info.Name())
    68  		if err != nil {
    69  			if strings.HasPrefix(err.Error(), "unsupported dialect") {
    70  				log(logging.Warn, "ignoring migration file with %s", err.Error())
    71  				return nil
    72  			}
    73  			return err
    74  		}
    75  		if match == nil {
    76  			log(logging.Warn, "ignoring file %s because it does not match the migration file pattern", info.Name())
    77  			return nil
    78  		}
    79  
    80  		f, err := fm.FS.Open(path)
    81  		if err != nil {
    82  			return err
    83  		}
    84  
    85  		mf := Migration{
    86  			Path:      path,
    87  			Version:   match.Version,
    88  			Name:      match.Name,
    89  			DBType:    match.DBType,
    90  			Direction: match.Direction,
    91  			Type:      match.Type,
    92  			Runner:    runner(f),
    93  		}
    94  		switch mf.Direction {
    95  		case "up":
    96  			fm.UpMigrations.Migrations = append(fm.UpMigrations.Migrations, mf)
    97  		case "down":
    98  			fm.DownMigrations.Migrations = append(fm.DownMigrations.Migrations, mf)
    99  		default:
   100  			// the regex only matches `(up|down)` for direction, so a panic here is appropriate
   101  			panic("got unknown migration direction " + mf.Direction)
   102  		}
   103  		return nil
   104  	})
   105  }