125 lines
3.0 KiB
Go
125 lines
3.0 KiB
Go
|
package goose
|
||
|
|
||
|
import (
|
||
|
"database/sql"
|
||
|
"fmt"
|
||
|
"path/filepath"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
// MigrationRecord struct.
|
||
|
type MigrationRecord struct {
|
||
|
VersionID int64
|
||
|
TStamp time.Time
|
||
|
IsApplied bool // was this a result of up() or down()
|
||
|
}
|
||
|
|
||
|
// Migration struct.
|
||
|
type Migration struct {
|
||
|
Version int64
|
||
|
Next int64 // next version, or -1 if none
|
||
|
Previous int64 // previous version, -1 if none
|
||
|
Source string // path to .sql script
|
||
|
Registered bool
|
||
|
UpFn func(*sql.Tx) error // Up go migration function
|
||
|
DownFn func(*sql.Tx) error // Down go migration function
|
||
|
}
|
||
|
|
||
|
func (m *Migration) String() string {
|
||
|
return fmt.Sprintf(m.Source)
|
||
|
}
|
||
|
|
||
|
// Up runs an up migration.
|
||
|
func (m *Migration) Up(db *sql.DB) error {
|
||
|
if err := m.run(db, true); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
log.Println("OK ", filepath.Base(m.Source))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Down runs a down migration.
|
||
|
func (m *Migration) Down(db *sql.DB) error {
|
||
|
if err := m.run(db, false); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
log.Println("OK ", filepath.Base(m.Source))
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (m *Migration) run(db *sql.DB, direction bool) error {
|
||
|
switch filepath.Ext(m.Source) {
|
||
|
case ".sql":
|
||
|
if err := runSQLMigration(db, m.Source, m.Version, direction); err != nil {
|
||
|
return errors.Wrapf(err, "failed to run SQL migration %q", filepath.Base(m.Source))
|
||
|
}
|
||
|
|
||
|
case ".go":
|
||
|
if !m.Registered {
|
||
|
return errors.Errorf("failed to run Go migration %q: Go functions must be registered and built into a custom binary (see https://github.com/pressly/goose/tree/master/examples/go-migrations)", m.Source)
|
||
|
}
|
||
|
tx, err := db.Begin()
|
||
|
if err != nil {
|
||
|
return errors.Wrap(err, "failed to begin transaction")
|
||
|
}
|
||
|
|
||
|
fn := m.UpFn
|
||
|
if !direction {
|
||
|
fn = m.DownFn
|
||
|
}
|
||
|
if fn != nil {
|
||
|
if err := fn(tx); err != nil {
|
||
|
tx.Rollback()
|
||
|
return errors.Wrapf(err, "failed to run Go migration %q", filepath.Base(m.Source))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if direction {
|
||
|
if _, err := tx.Exec(GetDialect().insertVersionSQL(), m.Version, direction); err != nil {
|
||
|
tx.Rollback()
|
||
|
return errors.Wrap(err, "failed to execute transaction")
|
||
|
}
|
||
|
} else {
|
||
|
if _, err := tx.Exec(GetDialect().deleteVersionSQL(), m.Version); err != nil {
|
||
|
tx.Rollback()
|
||
|
return errors.Wrap(err, "failed to execute transaction")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err := tx.Commit(); err != nil {
|
||
|
return errors.Wrap(err, "failed to commit transaction")
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// NumericComponent looks for migration scripts with names in the form:
|
||
|
// XXX_descriptivename.ext where XXX specifies the version number
|
||
|
// and ext specifies the type of migration
|
||
|
func NumericComponent(name string) (int64, error) {
|
||
|
base := filepath.Base(name)
|
||
|
|
||
|
if ext := filepath.Ext(base); ext != ".go" && ext != ".sql" {
|
||
|
return 0, errors.New("not a recognized migration file type")
|
||
|
}
|
||
|
|
||
|
idx := strings.Index(base, "_")
|
||
|
if idx < 0 {
|
||
|
return 0, errors.New("no separator found")
|
||
|
}
|
||
|
|
||
|
n, e := strconv.ParseInt(base[:idx], 10, 64)
|
||
|
if e == nil && n <= 0 {
|
||
|
return 0, errors.New("migration IDs must be greater than zero")
|
||
|
}
|
||
|
|
||
|
return n, e
|
||
|
}
|