fofm

package module
v0.0.0-...-c443469 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 5, 2024 License: MIT Imports: 12 Imported by: 0

README

fofm (eff of em)

Functional migrations in Go. This library will allow you to run functions that exist on a struct as a migration.

Usage

  1. Define a struct that adheres to the FunctionalMigration interface.
package some_package

import "github.com/emehrkay/fofm"

type MyMigrationsManager struct {
    NeededResource *SomeLibrary
}

func (m MyMigrationsManager) GetPackageName() string {
	return "some_package"
}

func (m MyMigrationsManager) GetMigrationsPath() string {
	_, curFile, _, _ := runtime.Caller(0)
	parts := strings.Split(curFile, "/")

	return strings.Join(parts[0:len(parts)-1], "/")
}

Put this in its own package if you ever use the CreateMigration function as it will add files to that directory

  1. Add some migrations as functions that return an error. This can be done manually as long as the names adhere to the format Migration_$SOME_INTEGER_up (for every up migration there should be corresponding down migration)
func (m MyMigrationsManager) Migration_1_up() error {
    // do something once
}

func (m MyMigrationsManager) Migration_1_down() error {
    // undo the thing from Migration_1_up
}

You can also create migrations programatically.

db, _ := fofm.NewSQLite(":memory:")
myMig := MyMigrationsManager{
    NeededResource: SomeLibrary.New(),
}
manager, _ := fofm.New(myMig, db)

manager.CreateMigration()

This will add a new file migration_$unix_time.go with methods Migration_$unix_time_up and Migration_$unix_time_down for you to fill in

Every migration is ordered based on the integer in the method name -- Migration_1_up, Migration_2_up, ..., Migration_X_up etc.

  1. Run the migrations
manager.Latest() // to run all migrations in order including the latest one
manager.Up("Migration_10_up") // to run every migration in order up to "Migration_10_up"
manager.Down("1") // to run every migration in reverse order down to "Migration_1_down" 

Both the Up and Down methods can accept the full migration name Migration_1_up, a partial name Migration_1, or just the integer 1

Extending

As of now, sqlite is the only storage engine pacakged with fofm. Luckily, it adheres to the Store interface so rolling your own is pretty straight forward.

fofm really shines when it is used as a command line tool. Simply wrap its public methods behind your defined CLI interfaces and you're good to go. Here is how I used fofm with cobra


func init() {
	migs := myMigrationManager{}
	migrationsLocation := env.Get("MIGRATION_LOCATION", ":memory:")
	db, err := fofm.NewSQLite(migrationsLocation)
	if err != nil {
		panic(err)
	}

	manager, err := fofm.New(db, migs)
	if err != nil {
		panic(err)
	}

	var latest = &cobra.Command{
		Use: "migrate_latest",
		Run: func(cmd *cobra.Command, args []string) {
			manager.Latest()
		},
	}

	var create = &cobra.Command{
		Use: "migrate_create",
		Run: func(cmd *cobra.Command, args []string) {
			manager.CreateMigration()
		},
	}

	var status = &cobra.Command{
		Use: "migrate_status",
		Run: func(cmd *cobra.Command, args []string) {
			migStatus, err := manager.Status()
			if err != nil {
				panic(err)
			}

			headers := []string{"ORDER", "MIGRATION", "STATUS", "RUNS"}
			writer := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', tabwriter.AlignRight)
			fmt.Fprintln(writer, strings.Join(headers, "\t"))

			for i, mig := range migStatus.Migrations {
				order := fmt.Sprintf("%v", i)
				runs := []string{}

				for _, run := range mig.Runs {
					runs = append(runs, fmt.Sprintf(`(%s %s)`, run.Status, run.Timestamp))
				}

				var status string
				if len(mig.Runs) == 0 {
					status = "not run"
					runs = append(runs, "-")
				}

				row := []string{
					order,
					mig.Migration.Name,
					status,
					strings.Join(runs, " "),
				}

				fmt.Fprintln(writer, strings.Join(row, "\t"))
			}

			writer.Flush()
		},
	}

	RootCmd.AddCommand(latest, create, status)
}

Use Cases

Call manager.Latest() everytime your app starts up with confidence that it is up to date with any pre-defined one-time calls.

License MIT

Documentation

Index

Constants

View Source
const (
	STATUS_SUCCESS = "success"
	STATUS_FAILURE = "failure"
)

Variables

View Source
var DefaultSettings = []Setting{

	FileWriter,
}

Functions

func FileWriter

func FileWriter(ins *FOFM) error

FileWriter sets the writer to be the deafult file writer

func MigrationFileNameTime

func MigrationFileNameTime(name string) (timestamp time.Time, err error)

func MigrationNameParts

func MigrationNameParts(name string) (timestamp time.Time, direction string, err error)

Types

type BaseMigration

type BaseMigration struct{}

BaseMigration provides an embed struct to easily adhere to the FunctionalMigration interface

func (BaseMigration) GetMigrationsPath

func (b BaseMigration) GetMigrationsPath() string

type FOFM

type FOFM struct {
	DB             Store
	Migration      FunctionalMigration
	UpMigrations   MigrationStack
	DownMigrations MigrationStack

	Seeded bool
	Writer WriteFile
	// contains filtered or unexported fields
}

func New

func New(db Store, migrationInstance FunctionalMigration, settings ...Setting) (*FOFM, error)

New will creae a new instance of FOFM. It will apply the DefaultSettings which can be overwritten by passing in settings

func (*FOFM) ClearStore

func (m *FOFM) ClearStore() error

func (*FOFM) CreateMigration

func (m *FOFM) CreateMigration() (string, error)

CreateMigration will create a new migration template based on the current unix time and it will call the defined Writer (which is a file writer by default)

func (*FOFM) Down

func (m *FOFM) Down(name string) error

Down will run all migrations, in reverse order, up to and including the named one passed in

func (*FOFM) GetNextMigrationTemplate

func (m *FOFM) GetNextMigrationTemplate() (string, int64)

GetNextMigrationTemplate will return a migration template and its unix time

func (*FOFM) Latest

func (m *FOFM) Latest() error

Latest will run all up migrations between the last migration that was run and the latest defined migration. If the last migration was a failure, it will attempt to rerun it if the last run migration is the latest and it was successful, it will return an error. If that migration was a failure, it will attempt to rerun it

func (*FOFM) Status

func (m *FOFM) Status() (MigrationSetStatus, error)

Status returns a list of all migrations and all of the times when they've been run since migrations can be run mulitple times

func (*FOFM) Up

func (m *FOFM) Up(name string) error

UP will run all migrations, in order, up to and inclduing the named one passed in

type FunctionalMigration

type FunctionalMigration interface {
	// GetMigrationsPath this should return the directory where your migration
	// manager lives. This path is used when new migration files are created
	GetMigrationsPath() string

	// GetPackageName should return the name of the package where your migration
	// manager lives. This is sued when migration files are created
	GetPackageName() string
}

type Migration

type Migration struct {
	ID        int       `json:"id"`
	Name      string    `json:"name"`
	Direction string    `json:"direction"`
	Status    string    `json:"status"`
	Error     string    `json:"error"`
	Timestamp time.Time `json:"timestamp"`
	Created   time.Time `json:"created"`
	// contains filtered or unexported fields
}

func (*Migration) CreatedFromString

func (m *Migration) CreatedFromString(timeStr string) error

func (*Migration) Scan

func (m *Migration) Scan() []any

func (*Migration) TimestampFromString

func (m *Migration) TimestampFromString(timeStr string) error

type MigrationSet

type MigrationSet []Migration

func (MigrationSet) ToRuns

func (m MigrationSet) ToRuns() []Run

type MigrationSetStatus

type MigrationSetStatus struct {
	Migrations []Status `json:"migration_statuses"`
	// contains filtered or unexported fields
}

type MigrationStack

type MigrationStack []Migration

func (*MigrationStack) Add

func (m *MigrationStack) Add(name, direction string, timestamp time.Time) error

func (MigrationStack) After

func (m MigrationStack) After(after *Migration) MigrationStack

func (MigrationStack) BeforeName

func (m MigrationStack) BeforeName(name string) MigrationStack

func (MigrationStack) First

func (m MigrationStack) First() *Migration

func (MigrationStack) Last

func (m MigrationStack) Last() *Migration

func (MigrationStack) Names

func (m MigrationStack) Names() []string

func (MigrationStack) Order

func (m MigrationStack) Order()

func (MigrationStack) Reverse

func (m MigrationStack) Reverse()

type NoResultsError

type NoResultsError struct {
	OriginalError error
	// contains filtered or unexported fields
}

NoResultsError should be used in place of a store's no results error

func (NoResultsError) Error

func (re NoResultsError) Error() string

type Run

type Run struct {
	Timestamp time.Time `json:"timestamp"`
	Status    string    `json:"status"`
	// contains filtered or unexported fields
}

type SQLite

type SQLite struct {
	// contains filtered or unexported fields
}

func NewSQLite

func NewSQLite(filepath string) (*SQLite, error)

func NewSQLiteWithTableName

func NewSQLiteWithTableName(filepath, tablename string) (*SQLite, error)

func (*SQLite) ClearStore

func (s *SQLite) ClearStore() error

func (*SQLite) Close

func (s *SQLite) Close() error

func (*SQLite) Connect

func (s *SQLite) Connect() error

func (*SQLite) CreateStore

func (s *SQLite) CreateStore() error

func (*SQLite) GetAllByName

func (s *SQLite) GetAllByName(name string) (MigrationSet, error)

func (*SQLite) LastRun

func (s *SQLite) LastRun() (*Migration, error)

func (*SQLite) LastRunByName

func (s *SQLite) LastRunByName(name string) (*Migration, error)

func (*SQLite) LastStatusRun

func (s *SQLite) LastStatusRun(status string) (*Migration, error)

func (*SQLite) List

func (s *SQLite) List() (MigrationSet, error)

func (*SQLite) Save

func (s *SQLite) Save(current Migration, err error) error

type Setting

type Setting func(ins *FOFM) error

type Status

type Status struct {
	Migration Migration `json:"migration"`
	Runs      []Run     `json:"runs"`
	// contains filtered or unexported fields
}

type Store

type Store interface {
	// Connect will connect to the store
	// it is automatcically called by FOFM
	Connect() error

	// Close will close any connections related to the store
	Close() error

	// CreateStore should do the work of creating
	// the storage for the migrations. It
	// should be a "create if doesnt exist" type
	// operation
	CreateStore() error

	// ClearStore should reset the store to an empty state
	ClearStore() error

	// LastRun will return the last run migration
	LastRun() (*Migration, error)

	// LastStatusRun will get the last run with the
	// provided status
	LastStatusRun(status string) (*Migration, error)

	// LastRunByName will get the last run with the
	// provided name
	LastRunByName(name string) (*Migration, error)

	// GetAllByName will return all migrations with the
	// provided name
	GetAllByName(name string) (MigrationSet, error)

	// List will return a list of all migrations that
	// have been saved to the store
	List() (MigrationSet, error)

	// Save should insert a new record
	Save(current Migration, err error) error
}

type WriteFile

type WriteFile func(filename string, data []byte, perm fs.FileMode) error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL