Files
kra-new/internal/data/swmigration/framework.go
T

120 lines
3.3 KiB
Go

package swmigration
import (
"context"
"database/sql"
"fmt"
"log/slog"
"sort"
"strings"
"gorm.io/gorm"
scripts "kra/deploy/migrations"
)
func migrateFramework(ctx context.Context, db *gorm.DB, conn *sql.Conn, logger *slog.Logger) error {
tables, err := tableSet(db)
if err != nil {
return err
}
if tables["sw_kra_state"] {
var previous struct{ Version, Phase string }
if err := db.Table("sw_kra_state").Where("id=1").Take(&previous).Error; err != nil {
return err
}
if previous.Version != "20261103_framework_migration_v5" {
return fmt.Errorf("unsupported legacy migration version %q", previous.Version)
}
switch previous.Phase {
case "completed":
return nil
case "data_validated":
return finishArchives(ctx, db, conn)
case "backup":
// All framework data changes and the data_validated marker commit
// together. In this phase only empty target DDL may survive a crash.
if err := resetIncompleteFramework(ctx, db, conn, tables); err != nil {
return err
}
default:
return fmt.Errorf("unsupported partial migration phase %q", previous.Phase)
}
} else if !tables["sys_user"] {
return fmt.Errorf("legacy source users and migration evidence are missing")
}
return executeScript(ctx, conn, scripts.FrameworkSQL(), false, logger)
}
func resetIncompleteFramework(ctx context.Context, db *gorm.DB, conn *sql.Conn, tables map[string]bool) error {
for source, archive := range scripts.LegacyArchives() {
if !tables[source] || tables[archive] {
return fmt.Errorf("incomplete framework sources are not recoverable: %s", source)
}
}
created := scripts.FrameworkCreatedTables()
sort.SliceStable(created, func(i, j int) bool {
priority := func(name string) int {
if name == "sw_kra_state" {
return 2
}
if name == "sw_kra_checks" {
return 1
}
return 0
}
return priority(created[i]) < priority(created[j])
})
for _, name := range created {
if !tables[name] || strings.HasPrefix(name, "bak_sw2kra_") || name == "sw_kra_state" || name == "sw_kra_checks" {
continue
}
var count int64
if err := db.Table(name).Count(&count).Error; err != nil {
return err
}
if count != 0 {
return fmt.Errorf("partial framework target %s contains committed data; automatic reset refused", name)
}
}
for _, name := range created {
if tables[name] {
if _, err := conn.ExecContext(ctx, "DROP TABLE "+quoteName(name)); err != nil {
return err
}
}
}
return nil
}
func finishArchives(ctx context.Context, db *gorm.DB, conn *sql.Conn) error {
tables, err := tableSet(db)
if err != nil {
return err
}
for _, name := range scripts.FrameworkCreatedTables() {
if !tables[name] {
return fmt.Errorf("validated framework table is missing: %s", name)
}
}
if err := verifyCheckRows(db); err != nil {
return err
}
var moves []string
for source, archive := range scripts.LegacyArchives() {
if tables[source] == tables[archive] {
return fmt.Errorf("ambiguous legacy archive state: %s", source)
}
if tables[source] {
moves = append(moves, quoteName(source)+" TO "+quoteName(archive))
}
}
sort.Strings(moves)
if len(moves) > 0 {
if _, err := conn.ExecContext(ctx, "RENAME TABLE "+strings.Join(moves, ",")); err != nil {
return err
}
}
_, err = conn.ExecContext(ctx, "UPDATE sw_kra_state SET phase='completed',completed_at=NOW(3) WHERE id=1")
return err
}