2018-04-30 18:42:17 +05:00
package goose
import (
"database/sql"
"fmt"
"path/filepath"
"strconv"
"strings"
"time"
2019-10-13 13:55:38 +05:00
"github.com/pkg/errors"
2018-04-30 18:42:17 +05:00
)
// 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
}
2019-10-13 13:55:38 +05:00
log . Println ( "OK " , filepath . Base ( m . Source ) )
2018-04-30 18:42:17 +05:00
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
}
2019-10-13 13:55:38 +05:00
log . Println ( "OK " , filepath . Base ( m . Source ) )
2018-04-30 18:42:17 +05:00
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 {
2019-10-13 13:55:38 +05:00
return errors . Wrapf ( err , "failed to run SQL migration %q" , filepath . Base ( m . Source ) )
2018-04-30 18:42:17 +05:00
}
case ".go" :
if ! m . Registered {
2019-10-13 13:55:38 +05:00
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 )
2018-04-30 18:42:17 +05:00
}
tx , err := db . Begin ( )
if err != nil {
2019-10-13 13:55:38 +05:00
return errors . Wrap ( err , "failed to begin transaction" )
2018-04-30 18:42:17 +05:00
}
fn := m . UpFn
if ! direction {
fn = m . DownFn
}
if fn != nil {
if err := fn ( tx ) ; err != nil {
tx . Rollback ( )
2019-10-13 13:55:38 +05:00
return errors . Wrapf ( err , "failed to run Go migration %q" , filepath . Base ( m . Source ) )
2018-04-30 18:42:17 +05:00
}
}
2019-10-13 13:55:38 +05:00
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" )
2018-04-30 18:42:17 +05:00
}
2019-10-13 13:55:38 +05:00
return nil
2018-04-30 18:42:17 +05:00
}
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
}