2024-02-18 10:42:21 +00:00
package migrate
import (
"bytes"
"context"
"database/sql"
"embed"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/go-gorp/gorp/v3"
"github.com/rubenv/sql-migrate/sqlparse"
)
type MigrationDirection int
const (
Up MigrationDirection = iota
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Down
)
// MigrationSet provides database parameters for a migration execution
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
type MigrationSet struct {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// TableName name of the table used to store migration info.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
TableName string
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// SchemaName schema that the migration table be referenced.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
SchemaName string
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// IgnoreUnknown skips the check to see if there is a migration
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// ran in the database that is not in MigrationSource.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// This should be used sparingly as it is removing a safety check.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
IgnoreUnknown bool
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// DisableCreateTable disable the creation of the migration table
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
DisableCreateTable bool
}
var migSet = MigrationSet { }
// NewMigrationSet returns a parametrized Migration object
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ( ms MigrationSet ) getTableName ( ) string {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if ms . TableName == "" {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return "gorp_migrations"
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ms . TableName
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
var numberPrefixRegex = regexp . MustCompile ( ` ^(\d+).*$ ` )
// PlanError happens where no migration plan could be created between the sets
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// of already applied migrations and the currently found. For example, when the database
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// contains a migration which is not among the migrations list found for an operation.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
type PlanError struct {
2024-07-24 23:45:04 +00:00
Migration * Migration
2024-02-18 10:42:21 +00:00
ErrorMessage string
}
func newPlanError ( migration * Migration , errorMessage string ) error {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return & PlanError {
2024-07-24 23:45:04 +00:00
Migration : migration ,
2024-02-18 10:42:21 +00:00
ErrorMessage : errorMessage ,
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ( p * PlanError ) Error ( ) string {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return fmt . Sprintf ( "Unable to create migration plan because of %s: %s" ,
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
p . Migration . Id , p . ErrorMessage )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// TxError is returned when any error is encountered during a database
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// transaction. It contains the relevant *Migration and notes it's Id in the
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Error function output.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
type TxError struct {
Migration * Migration
2024-07-24 23:45:04 +00:00
Err error
2024-02-18 10:42:21 +00:00
}
func newTxError ( migration * PlannedMigration , err error ) error {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return & TxError {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Migration : migration . Migration ,
2024-07-24 23:45:04 +00:00
Err : err ,
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ( e * TxError ) Error ( ) string {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return e . Err . Error ( ) + " handling " + e . Migration . Id
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Set the name of the table used to store migration info.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Should be called before any other call such as (Exec, ExecMax, ...).
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func SetTable ( name string ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if name != "" {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migSet . TableName = name
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// SetSchema sets the name of a schema that the migration table be referenced.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func SetSchema ( name string ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if name != "" {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migSet . SchemaName = name
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// SetDisableCreateTable sets the boolean to disable the creation of the migration table
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func SetDisableCreateTable ( disable bool ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migSet . DisableCreateTable = disable
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// SetIgnoreUnknown sets the flag that skips database check to see if there is a
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// migration in the database that is not in migration source.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// This should be used sparingly as it is removing a safety check.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func SetIgnoreUnknown ( v bool ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migSet . IgnoreUnknown = v
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
type Migration struct {
2024-07-24 23:45:04 +00:00
Id string
Up [ ] string
2024-02-18 10:42:21 +00:00
Down [ ] string
2024-07-24 23:45:04 +00:00
DisableTransactionUp bool
2024-02-18 10:42:21 +00:00
DisableTransactionDown bool
}
func ( m Migration ) Less ( other * Migration ) bool {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
switch {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
case m . isNumeric ( ) && other . isNumeric ( ) && m . VersionInt ( ) != other . VersionInt ( ) :
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return m . VersionInt ( ) < other . VersionInt ( )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
case m . isNumeric ( ) && ! other . isNumeric ( ) :
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return true
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
case ! m . isNumeric ( ) && other . isNumeric ( ) :
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return false
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
default :
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return m . Id < other . Id
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ( m Migration ) isNumeric ( ) bool {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return len ( m . NumberPrefixMatches ( ) ) > 0
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ( m Migration ) NumberPrefixMatches ( ) [ ] string {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return numberPrefixRegex . FindStringSubmatch ( m . Id )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ( m Migration ) VersionInt ( ) int64 {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
v := m . NumberPrefixMatches ( ) [ 1 ]
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
value , err := strconv . ParseInt ( v , 10 , 64 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
panic ( fmt . Sprintf ( "Could not parse %q into int64: %s" , v , err ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return value
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
type PlannedMigration struct {
* Migration
DisableTransaction bool
2024-07-24 23:45:04 +00:00
Queries [ ] string
2024-02-18 10:42:21 +00:00
}
type byId [ ] * Migration
2024-07-24 23:45:04 +00:00
func ( b byId ) Len ( ) int { return len ( b ) }
func ( b byId ) Swap ( i , j int ) { b [ i ] , b [ j ] = b [ j ] , b [ i ] }
2024-02-18 10:42:21 +00:00
func ( b byId ) Less ( i , j int ) bool { return b [ i ] . Less ( b [ j ] ) }
type MigrationRecord struct {
2024-07-24 23:45:04 +00:00
Id string ` db:"id" `
2024-02-18 10:42:21 +00:00
AppliedAt time . Time ` db:"applied_at" `
}
type OracleDialect struct {
gorp . OracleDialect
}
func ( OracleDialect ) IfTableNotExists ( command , _ , _ string ) string {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return command
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ( OracleDialect ) IfSchemaNotExists ( command , _ string ) string {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return command
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ( OracleDialect ) IfTableExists ( command , _ , _ string ) string {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return command
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
var MigrationDialects = map [ string ] gorp . Dialect {
2024-07-24 23:45:04 +00:00
"sqlite3" : gorp . SqliteDialect { } ,
"postgres" : gorp . PostgresDialect { } ,
"mysql" : gorp . MySQLDialect { Engine : "InnoDB" , Encoding : "UTF8" } ,
"mssql" : gorp . SqlServerDialect { } ,
"oci8" : OracleDialect { } ,
"godror" : OracleDialect { } ,
2024-02-18 10:42:21 +00:00
"snowflake" : gorp . SnowflakeDialect { } ,
}
type MigrationSource interface {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Finds the migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// The resulting slice of migrations should be sorted by Id.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
FindMigrations ( ) ( [ ] * Migration , error )
}
// A hardcoded set of migrations, in-memory.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
type MemoryMigrationSource struct {
Migrations [ ] * Migration
}
var _ MigrationSource = ( * MemoryMigrationSource ) ( nil )
func ( m MemoryMigrationSource ) FindMigrations ( ) ( [ ] * Migration , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Make sure migrations are sorted. In order to make the MemoryMigrationSource safe for
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// concurrent use we should not mutate it in place. So `FindMigrations` would sort a copy
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// of the m.Migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migrations := make ( [ ] * Migration , len ( m . Migrations ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
copy ( migrations , m . Migrations )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
sort . Sort ( byId ( migrations ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return migrations , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// A set of migrations loaded from an http.FileServer
type HttpFileSystemMigrationSource struct {
FileSystem http . FileSystem
}
var _ MigrationSource = ( * HttpFileSystemMigrationSource ) ( nil )
func ( f HttpFileSystemMigrationSource ) FindMigrations ( ) ( [ ] * Migration , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return findMigrations ( f . FileSystem , "/" )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// A set of migrations loaded from a directory.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
type FileMigrationSource struct {
Dir string
}
var _ MigrationSource = ( * FileMigrationSource ) ( nil )
func ( f FileMigrationSource ) FindMigrations ( ) ( [ ] * Migration , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
filesystem := http . Dir ( f . Dir )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return findMigrations ( filesystem , "/" )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func findMigrations ( dir http . FileSystem , root string ) ( [ ] * Migration , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migrations := make ( [ ] * Migration , 0 )
file , err := dir . Open ( root )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
files , err := file . Readdir ( 0 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
for _ , info := range files {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if strings . HasSuffix ( info . Name ( ) , ".sql" ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migration , err := migrationFromFile ( dir , root , info )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
migrations = append ( migrations , migration )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Make sure migrations are sorted
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
sort . Sort ( byId ( migrations ) )
return migrations , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func migrationFromFile ( dir http . FileSystem , root string , info os . FileInfo ) ( * Migration , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
path := path . Join ( root , info . Name ( ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
file , err := dir . Open ( path )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , fmt . Errorf ( "Error while opening %s: %w" , info . Name ( ) , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
defer func ( ) { _ = file . Close ( ) } ( )
migration , err := ParseMigration ( info . Name ( ) , file )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , fmt . Errorf ( "Error while parsing %s: %w" , info . Name ( ) , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return migration , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Migrations from a bindata asset set.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
type AssetMigrationSource struct {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Asset should return content of file in path if exists
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Asset func ( path string ) ( [ ] byte , error )
// AssetDir should return list of files in the path
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
AssetDir func ( path string ) ( [ ] string , error )
// Path in the bindata to use.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Dir string
}
var _ MigrationSource = ( * AssetMigrationSource ) ( nil )
func ( a AssetMigrationSource ) FindMigrations ( ) ( [ ] * Migration , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migrations := make ( [ ] * Migration , 0 )
files , err := a . AssetDir ( a . Dir )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
for _ , name := range files {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if strings . HasSuffix ( name , ".sql" ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
file , err := a . Asset ( path . Join ( a . Dir , name ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
migration , err := ParseMigration ( name , bytes . NewReader ( file ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
migrations = append ( migrations , migration )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Make sure migrations are sorted
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
sort . Sort ( byId ( migrations ) )
return migrations , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// A set of migrations loaded from an go1.16 embed.FS
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
type EmbedFileSystemMigrationSource struct {
FileSystem embed . FS
Root string
}
var _ MigrationSource = ( * EmbedFileSystemMigrationSource ) ( nil )
func ( f EmbedFileSystemMigrationSource ) FindMigrations ( ) ( [ ] * Migration , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return findMigrations ( http . FS ( f . FileSystem ) , f . Root )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Avoids pulling in the packr library for everyone, mimicks the bits of
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// packr.Box that we need.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
type PackrBox interface {
List ( ) [ ] string
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Find ( name string ) ( [ ] byte , error )
}
// Migrations from a packr box.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
type PackrMigrationSource struct {
Box PackrBox
// Path in the box to use.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Dir string
}
var _ MigrationSource = ( * PackrMigrationSource ) ( nil )
func ( p PackrMigrationSource ) FindMigrations ( ) ( [ ] * Migration , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migrations := make ( [ ] * Migration , 0 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
items := p . Box . List ( )
prefix := ""
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
dir := path . Clean ( p . Dir )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if dir != "." {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
prefix = fmt . Sprintf ( "%s/" , dir )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
for _ , item := range items {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if ! strings . HasPrefix ( item , prefix ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
continue
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
name := strings . TrimPrefix ( item , prefix )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if strings . Contains ( name , "/" ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
continue
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
if strings . HasSuffix ( name , ".sql" ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
file , err := p . Box . Find ( item )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
migration , err := ParseMigration ( name , bytes . NewReader ( file ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
migrations = append ( migrations , migration )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Make sure migrations are sorted
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
sort . Sort ( byId ( migrations ) )
return migrations , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Migration parsing
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ParseMigration ( id string , r io . ReadSeeker ) ( * Migration , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
m := & Migration {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Id : id ,
}
parsed , err := sqlparse . ParseMigration ( r )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , fmt . Errorf ( "Error parsing migration (%s): %w" , id , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
m . Up = parsed . UpStatements
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
m . Down = parsed . DownStatements
m . DisableTransactionUp = parsed . DisableTransactionUp
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
m . DisableTransactionDown = parsed . DisableTransactionDown
return m , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
type SqlExecutor interface {
Exec ( query string , args ... interface { } ) ( sql . Result , error )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Insert ( list ... interface { } ) error
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Delete ( list ... interface { } ) ( int64 , error )
}
// Execute a set of migrations
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func Exec ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ExecMaxContext ( context . Background ( ) , db , dialect , m , dir , 0 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ( ms MigrationSet ) Exec ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ms . ExecMaxContext ( context . Background ( ) , db , dialect , m , dir , 0 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Execute a set of migrations with an input context.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ExecContext ( ctx context . Context , db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ExecMaxContext ( ctx , db , dialect , m , dir , 0 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ( ms MigrationSet ) ExecContext ( ctx context . Context , db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ms . ExecMaxContext ( ctx , db , dialect , m , dir , 0 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Execute a set of migrations
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Will apply at most `max` migrations. Pass 0 for no limit (or use Exec).
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ExecMax ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , max int ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return migSet . ExecMax ( db , dialect , m , dir , max )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Execute a set of migrations with an input context.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Will apply at most `max` migrations. Pass 0 for no limit (or use Exec).
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ExecMaxContext ( ctx context . Context , db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , max int ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return migSet . ExecMaxContext ( ctx , db , dialect , m , dir , max )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Execute a set of migrations
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Will apply at the target `version` of migration. Cannot be a negative value.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ExecVersion ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , version int64 ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ExecVersionContext ( context . Background ( ) , db , dialect , m , dir , version )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Execute a set of migrations with an input context.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Will apply at the target `version` of migration. Cannot be a negative value.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ExecVersionContext ( ctx context . Context , db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , version int64 ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if version < 0 {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return 0 , fmt . Errorf ( "target version %d should not be negative" , version )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return migSet . ExecVersionContext ( ctx , db , dialect , m , dir , version )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ( ms MigrationSet ) ExecMax ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , max int ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ms . ExecMaxContext ( context . Background ( ) , db , dialect , m , dir , max )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Returns the number of applied migrations, but applies with an input context.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ( ms MigrationSet ) ExecMaxContext ( ctx context . Context , db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , max int ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migrations , dbMap , err := ms . PlanMigration ( db , dialect , m , dir , max )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return 0 , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ms . applyMigrations ( ctx , dir , migrations , dbMap )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ( ms MigrationSet ) ExecVersion ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , version int64 ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ms . ExecVersionContext ( context . Background ( ) , db , dialect , m , dir , version )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ( ms MigrationSet ) ExecVersionContext ( ctx context . Context , db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , version int64 ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migrations , dbMap , err := ms . PlanMigrationToVersion ( db , dialect , m , dir , version )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return 0 , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ms . applyMigrations ( ctx , dir , migrations , dbMap )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Applies the planned migrations and returns the number of applied migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ( MigrationSet ) applyMigrations ( ctx context . Context , dir MigrationDirection , migrations [ ] * PlannedMigration , dbMap * gorp . DbMap ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
applied := 0
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for _ , migration := range migrations {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
var executor SqlExecutor
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
var err error
if migration . DisableTransaction {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
executor = dbMap . WithContext ( ctx )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
} else {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
e , err := dbMap . Begin ( )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return applied , newTxError ( migration , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
executor = e . WithContext ( ctx )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
for _ , stmt := range migration . Queries {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// remove the semicolon from stmt, fix ORA-00922 issue in database oracle
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
stmt = strings . TrimSuffix ( stmt , "\n" )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
stmt = strings . TrimSuffix ( stmt , " " )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
stmt = strings . TrimSuffix ( stmt , ";" )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if _ , err := executor . Exec ( stmt ) ; err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if trans , ok := executor . ( * gorp . Transaction ) ; ok {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
_ = trans . Rollback ( )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
return applied , newTxError ( migration , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
switch dir {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
case Up :
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
err = executor . Insert ( & MigrationRecord {
2024-07-24 23:45:04 +00:00
Id : migration . Id ,
2024-02-18 10:42:21 +00:00
AppliedAt : time . Now ( ) ,
} )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if trans , ok := executor . ( * gorp . Transaction ) ; ok {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
_ = trans . Rollback ( )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
return applied , newTxError ( migration , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
case Down :
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
_ , err := executor . Delete ( & MigrationRecord {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Id : migration . Id ,
} )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if trans , ok := executor . ( * gorp . Transaction ) ; ok {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
_ = trans . Rollback ( )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
return applied , newTxError ( migration , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
default :
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
panic ( "Not possible" )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
if trans , ok := executor . ( * gorp . Transaction ) ; ok {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err := trans . Commit ( ) ; err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return applied , newTxError ( migration , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
applied ++
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
return applied , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Plan a migration.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func PlanMigration ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , max int ) ( [ ] * PlannedMigration , * gorp . DbMap , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return migSet . PlanMigration ( db , dialect , m , dir , max )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Plan a migration to version.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func PlanMigrationToVersion ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , version int64 ) ( [ ] * PlannedMigration , * gorp . DbMap , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return migSet . PlanMigrationToVersion ( db , dialect , m , dir , version )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Plan a migration.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ( ms MigrationSet ) PlanMigration ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , max int ) ( [ ] * PlannedMigration , * gorp . DbMap , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ms . planMigrationCommon ( db , dialect , m , dir , max , - 1 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Plan a migration to version.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ( ms MigrationSet ) PlanMigrationToVersion ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , version int64 ) ( [ ] * PlannedMigration , * gorp . DbMap , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return ms . planMigrationCommon ( db , dialect , m , dir , 0 , version )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// A common method to plan a migration.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ( ms MigrationSet ) planMigrationCommon ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , max int , version int64 ) ( [ ] * PlannedMigration , * gorp . DbMap , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
dbMap , err := ms . getMigrationDbMap ( db , dialect )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
migrations , err := m . FindMigrations ( )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
var migrationRecords [ ] MigrationRecord
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
_ , err = dbMap . Select ( & migrationRecords , fmt . Sprintf ( "SELECT * FROM %s" , dbMap . Dialect . QuotedTableForQuery ( ms . SchemaName , ms . getTableName ( ) ) ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Sort migrations that have been run by Id.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
var existingMigrations [ ] * Migration
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for _ , migrationRecord := range migrationRecords {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
existingMigrations = append ( existingMigrations , & Migration {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Id : migrationRecord . Id ,
} )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
sort . Sort ( byId ( existingMigrations ) )
// Make sure all migrations in the database are among the found migrations which
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// are to be applied.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if ! ms . IgnoreUnknown {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migrationsSearch := make ( map [ string ] struct { } )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for _ , migration := range migrations {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migrationsSearch [ migration . Id ] = struct { } { }
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for _ , existingMigration := range existingMigrations {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if _ , ok := migrationsSearch [ existingMigration . Id ] ; ! ok {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , nil , newPlanError ( existingMigration , "unknown migration in database" )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Get last migration that was run
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
record := & Migration { }
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if len ( existingMigrations ) > 0 {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
record = existingMigrations [ len ( existingMigrations ) - 1 ]
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
result := make ( [ ] * PlannedMigration , 0 )
// Add missing migrations up to the last run migration.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// This can happen for example when merges happened.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if len ( existingMigrations ) > 0 {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
result = append ( result , ToCatchup ( migrations , existingMigrations , record ) ... )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Figure out which migrations to apply
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
toApply := ToApply ( migrations , record . Id , dir )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
toApplyCount := len ( toApply )
if version >= 0 {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
targetIndex := 0
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for targetIndex < len ( toApply ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
tempVersion := toApply [ targetIndex ] . VersionInt ( )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if dir == Up && tempVersion > version || dir == Down && tempVersion < version {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , nil , newPlanError ( & Migration { } , fmt . Errorf ( "unknown migration with version id %d in database" , version ) . Error ( ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if tempVersion == version {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
toApplyCount = targetIndex + 1
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
break
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
targetIndex ++
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if targetIndex == len ( toApply ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , nil , newPlanError ( & Migration { } , fmt . Errorf ( "unknown migration with version id %d in database" , version ) . Error ( ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
} else if max > 0 && max < toApplyCount {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
toApplyCount = max
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for _ , v := range toApply [ 0 : toApplyCount ] {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if dir == Up {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
result = append ( result , & PlannedMigration {
2024-07-24 23:45:04 +00:00
Migration : v ,
Queries : v . Up ,
2024-02-18 10:42:21 +00:00
DisableTransaction : v . DisableTransactionUp ,
} )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
} else if dir == Down {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
result = append ( result , & PlannedMigration {
2024-07-24 23:45:04 +00:00
Migration : v ,
Queries : v . Down ,
2024-02-18 10:42:21 +00:00
DisableTransaction : v . DisableTransactionDown ,
} )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
return result , dbMap , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Skip a set of migrations
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Will skip at most `max` migrations. Pass 0 for no limit.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
//
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Returns the number of skipped migrations.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func SkipMax ( db * sql . DB , dialect string , m MigrationSource , dir MigrationDirection , max int ) ( int , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
migrations , dbMap , err := PlanMigration ( db , dialect , m , dir , max )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return 0 , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Skip migrations
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
applied := 0
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for _ , migration := range migrations {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
var executor SqlExecutor
if migration . DisableTransaction {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
executor = dbMap
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
} else {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
executor , err = dbMap . Begin ( )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return applied , newTxError ( migration , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
err = executor . Insert ( & MigrationRecord {
2024-07-24 23:45:04 +00:00
Id : migration . Id ,
2024-02-18 10:42:21 +00:00
AppliedAt : time . Now ( ) ,
} )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if trans , ok := executor . ( * gorp . Transaction ) ; ok {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
_ = trans . Rollback ( )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
return applied , newTxError ( migration , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
if trans , ok := executor . ( * gorp . Transaction ) ; ok {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err := trans . Commit ( ) ; err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return applied , newTxError ( migration , err )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
applied ++
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
return applied , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Filter a slice of migrations into ones that should be applied.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
func ToApply ( migrations [ ] * Migration , current string , direction MigrationDirection ) [ ] * Migration {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
index := - 1
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if current != "" {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for index < len ( migrations ) - 1 {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
index ++
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if migrations [ index ] . Id == current {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
break
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
if direction == Up {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return migrations [ index + 1 : ]
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
} else if direction == Down {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if index == - 1 {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return [ ] * Migration { }
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Add in reverse order
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
toApply := make ( [ ] * Migration , index + 1 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for i := 0 ; i < index + 1 ; i ++ {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
toApply [ index - i ] = migrations [ i ]
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return toApply
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
panic ( "Not possible" )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ToCatchup ( migrations , existingMigrations [ ] * Migration , lastRun * Migration ) [ ] * PlannedMigration {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
missing := make ( [ ] * PlannedMigration , 0 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for _ , migration := range migrations {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
found := false
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
for _ , existing := range existingMigrations {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if existing . Id == migration . Id {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
found = true
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
break
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if ! found && migration . Less ( lastRun ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
missing = append ( missing , & PlannedMigration {
2024-07-24 23:45:04 +00:00
Migration : migration ,
Queries : migration . Up ,
2024-02-18 10:42:21 +00:00
DisableTransaction : migration . DisableTransactionUp ,
} )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return missing
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func GetMigrationRecords ( db * sql . DB , dialect string ) ( [ ] * MigrationRecord , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return migSet . GetMigrationRecords ( db , dialect )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ( ms MigrationSet ) GetMigrationRecords ( db * sql . DB , dialect string ) ( [ ] * MigrationRecord , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
dbMap , err := ms . getMigrationDbMap ( db , dialect )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
var records [ ] * MigrationRecord
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
query := fmt . Sprintf ( "SELECT * FROM %s ORDER BY %s ASC" , dbMap . Dialect . QuotedTableForQuery ( ms . SchemaName , ms . getTableName ( ) ) , dbMap . Dialect . QuoteField ( "id" ) )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
_ , err = dbMap . Select ( & records , query )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
return records , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
func ( ms MigrationSet ) getMigrationDbMap ( db * sql . DB , dialect string ) ( * gorp . DbMap , error ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
d , ok := MigrationDialects [ dialect ]
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if ! ok {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , fmt . Errorf ( "Unknown dialect: %s" , dialect )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// When using the mysql driver, make sure that the parseTime option is
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// configured, otherwise it won't map time columns to time.Time. See
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// https://github.com/rubenv/sql-migrate/issues/2
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if dialect == "mysql" {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
var out * time . Time
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
err := db . QueryRow ( "SELECT NOW()" ) . Scan ( & out )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err . Error ( ) == "sql: Scan error on column index 0: unsupported driver -> Scan pair: []uint8 -> *time.Time" ||
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
err . Error ( ) == "sql: Scan error on column index 0: unsupported Scan, storing driver.Value type []uint8 into type *time.Time" ||
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
err . Error ( ) == "sql: Scan error on column index 0, name \"NOW()\": unsupported Scan, storing driver.Value type []uint8 into type *time.Time" {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , errors . New ( ` Cannot parse dates .
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Make sure that the parseTime option is supplied to your database connection .
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
Check https : //github.com/go-sql-driver/mysql#parsetime for more info.`)
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// Create migration database map
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
dbMap := & gorp . DbMap { Db : db , Dialect : d }
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
table := dbMap . AddTableWithNameAndSchema ( MigrationRecord { } , ms . SchemaName , ms . getTableName ( ) ) . SetKeys ( false , "Id" )
if dialect == "oci8" || dialect == "godror" {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
table . ColMap ( "Id" ) . SetMaxSize ( 4000 )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
if ms . DisableCreateTable {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return dbMap , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
err := dbMap . CreateTablesIfNotExists ( )
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if err != nil {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// Oracle database does not support `if not exists`, so use `ORA-00955:` error code
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
// to check if the table exists.
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
if ( dialect == "oci8" || dialect == "godror" ) && strings . Contains ( err . Error ( ) , "ORA-00955:" ) {
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return dbMap , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
return nil , err
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
return dbMap , nil
2024-07-24 23:45:04 +00:00
2024-02-18 10:42:21 +00:00
}
// TODO: Run migration + record insert in transaction.