117 lines
2.7 KiB
Go
117 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
type MigrationGraph struct {
|
|
// Reference to the root of the graph.
|
|
Root *Migration
|
|
// Name to struct mapping of all migrations part of the graph.
|
|
Migrations map[string]Migration
|
|
// Mapping of all migrations to their parent, if any.
|
|
parentage map[string]*Migration
|
|
}
|
|
|
|
func NewMigrationGraph() MigrationGraph {
|
|
return MigrationGraph{Migrations: map[string]Migration{}, parentage: map[string]*Migration{}}
|
|
}
|
|
|
|
func NewMigrationGraphFromDirectory(pathRoot string) (MigrationGraph, error) {
|
|
migrationGraph := NewMigrationGraph()
|
|
|
|
files, err := os.ReadDir(pathRoot)
|
|
|
|
if err != nil {
|
|
log.Printf("Failed to read migration directory: %s", pathRoot)
|
|
return migrationGraph, err
|
|
}
|
|
for _, file := range files {
|
|
filename := file.Name()
|
|
|
|
if filepath.Ext(filename) != ".sql" {
|
|
continue
|
|
}
|
|
|
|
migrationPath := filepath.Join(pathRoot, file.Name())
|
|
|
|
file, _ := os.Open(migrationPath)
|
|
|
|
headers := gatherMigrationHeaders(file)
|
|
|
|
requirements := ""
|
|
|
|
if len(headers.Requirements) > 0 {
|
|
requirements = headers.Requirements[0]
|
|
}
|
|
|
|
migration := NewMigration(migrationPath, filepath.Base(file.Name()), requirements)
|
|
migrationGraph.AddMigration(migration)
|
|
}
|
|
|
|
return migrationGraph, nil
|
|
}
|
|
|
|
// Adds a migration to the graph.
|
|
//
|
|
// This also adds the migration to the parentage mappings to link it
|
|
// to its parent. If the migration added has no parent, then it's also
|
|
// set to be the root of the graph.
|
|
func (g *MigrationGraph) AddMigration(m Migration) {
|
|
if m.Requires == "" {
|
|
g.addRoot(m)
|
|
}
|
|
|
|
g.Migrations[m.Name] = m
|
|
g.parentage[m.Requires] = &m
|
|
}
|
|
|
|
// Builds the linear history of migrations.
|
|
func (g *MigrationGraph) GetLinearHistory() ([]Migration, error) {
|
|
if g.Root == nil {
|
|
return []Migration{}, errors.New("Cannot get linear history, the graph has no root.")
|
|
}
|
|
|
|
ordered := []Migration{}
|
|
visited := map[string]bool{}
|
|
unordered := []Migration{*g.Root}
|
|
|
|
for len(unordered) > 0 {
|
|
migration := unordered[0]
|
|
unordered = unordered[1:]
|
|
|
|
if _, hasVisited := visited[migration.Name]; hasVisited {
|
|
return []Migration{}, errors.New("Cycle detected, cannot generate linear history.")
|
|
}
|
|
|
|
child, hasChildren := g.parentage[migration.Name]
|
|
|
|
ordered = append(ordered, migration)
|
|
|
|
if hasChildren {
|
|
unordered = append(unordered, *child)
|
|
}
|
|
|
|
visited[migration.Name] = true
|
|
}
|
|
|
|
if len(ordered) != len(g.Migrations) {
|
|
return ordered, errors.New("Not all migrations in the graph are part of the history.")
|
|
}
|
|
|
|
return ordered, nil
|
|
}
|
|
|
|
func (g *MigrationGraph) addRoot(m Migration) error {
|
|
if g.Root != nil {
|
|
return errors.New(fmt.Sprintf("Cannot have more than one root, tried to add %#v but already knew about %#v.", m, g.Root))
|
|
}
|
|
|
|
g.Root = &m
|
|
|
|
return nil
|
|
}
|