120 lines
3.3 KiB
Go
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
|
|
}
|