accounts/internal/migrate/checker.go
Haitao Pan ee6e1a6363 feat: move account service to repo root
# Conflicts:
#	account/Makefile
#	account/go.mod
#	docs/account-admin-settings.md
#	docs/account-svc-plus.md
2026-01-16 16:15:23 +08:00

103 lines
2.4 KiB
Go

package migrate
import (
"context"
"errors"
"fmt"
"strings"
"account/internal/utils"
)
// Checker compares two PostgreSQL schemas (CN vs Global) and optionally fixes
// missing structures on the CN side.
type Checker struct{}
func NewChecker() *Checker {
return &Checker{}
}
func (c *Checker) Check(ctx context.Context, cnDSN, globalDSN string, autoFix bool) error {
if cnDSN == "" || globalDSN == "" {
return errors.New("both --cn and --global DSNs are required")
}
cnDump, err := utils.RunPgDump(ctx, cnDSN)
if err != nil {
return fmt.Errorf("dump CN schema: %w", err)
}
globalDump, err := utils.RunPgDump(ctx, globalDSN)
if err != nil {
return fmt.Errorf("dump Global schema: %w", err)
}
cnStatements := utils.NormalizeStatements(cnDump)
globalStatements := utils.NormalizeStatements(globalDump)
onlyCN, onlyGlobal := utils.CompareStatements(cnStatements, globalStatements)
if len(onlyCN) == 0 && len(onlyGlobal) == 0 {
fmt.Println("✅ CN and Global schemas are consistent")
return nil
}
if len(onlyGlobal) > 0 {
fmt.Println("⚠️ Statements missing on CN (present on Global):")
for _, stmt := range onlyGlobal {
fmt.Printf(" + %s;\n", stmt)
}
}
if len(onlyCN) > 0 {
fmt.Println("⚠️ Statements only found on CN:")
for _, stmt := range onlyCN {
fmt.Printf(" - %s;\n", stmt)
}
}
if autoFix && len(onlyGlobal) > 0 {
if err := applyStatements(ctx, cnDSN, onlyGlobal); err != nil {
return fmt.Errorf("apply auto-fix: %w", err)
}
fmt.Println("✅ Auto-fix applied on CN database — please re-run check to confirm")
// After auto-fix we still return an error if CN has extra statements.
if len(onlyCN) > 0 {
return errors.New("schema differences remain on CN after auto-fix")
}
return nil
}
return errors.New("schema differences detected")
}
func applyStatements(ctx context.Context, dsn string, statements []string) error {
db, err := openDB(ctx, dsn)
if err != nil {
return err
}
defer db.Close()
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
for _, stmt := range statements {
if strings.Contains(stmt, "pglogical") {
continue
}
sql := stmt
if !strings.HasSuffix(sql, ";") {
sql += ";"
}
fmt.Printf("→ Applying fix: %s\n", sql)
if _, err := tx.ExecContext(ctx, sql); err != nil {
return err
}
fmt.Printf("✅ Applied fix\n")
}
return tx.Commit()
}