package migration import ( "context" "github.com/pkg/errors" ) var ( ErrNoAvailableMigration = errors.New("no available migration") ErrMigrationNotFound = errors.New("migration not found") ) type Manager struct { migrations []Migration resolver VersionResolver } func (m *Manager) Up(ctx context.Context) error { currentVersion, err := m.resolver.Current(ctx) if err != nil { return errors.Wrap(err, "could not retrieve current version") } migrate := func(up Migration) error { if err := up.Up(ctx); err != nil { return errors.Wrapf(err, "could not apply '%s' up migration", up.Version()) } if err := m.resolver.Set(ctx, up.Version()); err != nil { return errors.Wrapf(err, "could not update schema version to '%s'", up.Version()) } return nil } if currentVersion == "" { up := m.migrations[0] return migrate(up) } for i, mi := range m.migrations { if mi.Version() != currentVersion && currentVersion != "" { continue } // Already at latest, do nothing if i >= len(m.migrations)-1 { return nil } up := m.migrations[i+1] return migrate(up) } return errors.WithStack(ErrMigrationNotFound) } func (m *Manager) Down(ctx context.Context) error { currentVersion, err := m.resolver.Current(ctx) if err != nil { return errors.Wrap(err, "could not retrieve current version") } for i, mi := range m.migrations { if mi.Version() != currentVersion { continue } if err := mi.Down(ctx); err != nil { return errors.Wrapf(err, "could not apply '%s' down migration", mi.Version()) } var version string // Already at oldest, do nothing if i != 0 { down := m.migrations[i-1] version = down.Version() } if err := m.resolver.Set(ctx, version); err != nil { return errors.Wrapf(err, "could not update schema version to '%s'", version) } return nil } return errors.WithStack(ErrMigrationNotFound) } func (m *Manager) Latest(ctx context.Context) error { for { isLatest, err := m.IsLatest(ctx) if err != nil { return errors.Wrap(err, "could not retrieve schema state") } if isLatest { return nil } if err := m.Up(ctx); err != nil { return errors.WithStack(err) } } } func (m *Manager) Register(migrations ...Migration) { m.migrations = migrations } func (m *Manager) CurrentVersion(ctx context.Context) (string, error) { return m.resolver.Current(ctx) } func (m *Manager) LatestVersion() (string, error) { if len(m.migrations) == 0 { return "", errors.WithStack(ErrNoAvailableMigration) } return m.migrations[len(m.migrations)-1].Version(), nil } func (m *Manager) IsLatest(ctx context.Context) (bool, error) { currentVersion, err := m.resolver.Current(ctx) if err != nil { return false, errors.Wrap(err, "could not retrieve current version") } latestVersion, err := m.LatestVersion() if err != nil { return false, errors.Wrap(err, "could not retrieve latest version") } return currentVersion == latestVersion, nil } func NewManager(resolver VersionResolver) *Manager { return &Manager{ resolver: resolver, migrations: make([]Migration, 0), } }