Files
fn_registry/functions/infra/migration_create.go
T
egutierrez 35a49174ca feat: funciones impuras migration_create, migration_up, migration_down, migration_status
Fase 2 del issue 0015. MigrationCreate (crea archivo .sql template con version
auto-calculada), MigrationUp (aplica migraciones pendientes en transacciones
individuales), MigrationDown (revierte ultimas N via down_sql de _migrations),
MigrationGetStatus (cruza disco con BD, detecta orphaned). Tests de integracion:
ciclo completo create->up->status->down->status. 26 tests, todos pasan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 02:01:40 +02:00

84 lines
2.0 KiB
Go

package infra
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
var migrationFilePattern = regexp.MustCompile(`^(\d+)_[a-zA-Z0-9_]+\.sql$`)
var migrationNamePattern = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]*$`)
// MigrationCreate creates a new migration file in dir with the given name.
// It calculates the next version by scanning existing .sql files in dir.
// The filename follows the pattern NNN_name.sql (e.g. 003_add_index.sql).
// Returns the absolute path of the created file.
func MigrationCreate(dir, name string) (string, error) {
if !migrationNamePattern.MatchString(name) {
return "", fmt.Errorf("migration_create: name %q must match [a-zA-Z][a-zA-Z0-9_]*", name)
}
if err := os.MkdirAll(dir, 0o755); err != nil {
return "", fmt.Errorf("migration_create: cannot create directory %q: %w", dir, err)
}
next, err := nextMigrationVersion(dir)
if err != nil {
return "", fmt.Errorf("migration_create: %w", err)
}
filename := fmt.Sprintf("%03d_%s.sql", next, name)
path := filepath.Join(dir, filename)
template := fmt.Sprintf("-- %s\n\n-- +up\n\n\n-- +down\n\n", filename)
if err := os.WriteFile(path, []byte(template), 0o644); err != nil {
return "", fmt.Errorf("migration_create: cannot write file %q: %w", path, err)
}
return path, nil
}
// nextMigrationVersion returns the next version number by scanning .sql files in dir.
// Returns 1 if the directory is empty or has no migration files.
func nextMigrationVersion(dir string) (int, error) {
entries, err := os.ReadDir(dir)
if err != nil {
if os.IsNotExist(err) {
return 1, nil
}
return 0, fmt.Errorf("cannot read directory: %w", err)
}
max := 0
for _, e := range entries {
if e.IsDir() {
continue
}
name := e.Name()
if !strings.HasSuffix(strings.ToLower(name), ".sql") {
continue
}
if !migrationFilePattern.MatchString(name) {
continue
}
idx := strings.Index(name, "_")
if idx < 0 {
continue
}
v, err := strconv.Atoi(name[:idx])
if err != nil {
continue
}
if v > max {
max = v
}
}
return max + 1, nil
}