accounts/internal/migrate/cleaner.go
Haitao Pan 07e31ff6bd 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

139 lines
3.5 KiB
Go

package migrate
import (
"context"
"database/sql"
"errors"
"fmt"
)
// Cleaner removes leftover structures such as invalid indexes or disabled
// triggers while keeping pglogical untouched.
type Cleaner struct{}
func NewCleaner() *Cleaner {
return &Cleaner{}
}
func (c *Cleaner) Clean(ctx context.Context, dsn string, force bool) error {
if !force {
return errors.New("clean requires --force confirmation")
}
db, err := openDB(ctx, dsn)
if err != nil {
return err
}
defer db.Close()
tx, err := db.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return err
}
defer tx.Rollback()
if err := dropInvalidIndexes(ctx, tx); err != nil {
return err
}
if err := dropDisabledTriggers(ctx, tx); err != nil {
return err
}
if err := dropTemporaryTables(ctx, tx); err != nil {
return err
}
if err := tx.Commit(); err != nil {
return err
}
fmt.Println("✅ Database clean-up completed")
return nil
}
func dropInvalidIndexes(ctx context.Context, tx *sql.Tx) error {
rows, err := tx.QueryContext(ctx, `
SELECT quote_ident(n.nspname) || '.' || quote_ident(c.relname)
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname NOT IN ('pglogical', 'pg_catalog', 'information_schema')
AND (NOT i.indisvalid OR NOT i.indisready)
`)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var identifier string
if err := rows.Scan(&identifier); err != nil {
return err
}
fmt.Printf("→ Dropping invalid index %s\n", identifier)
if _, err := tx.ExecContext(ctx, fmt.Sprintf("DROP INDEX IF EXISTS %s", identifier)); err != nil {
return err
}
fmt.Printf("✅ Dropped index %s\n", identifier)
}
return rows.Err()
}
func dropDisabledTriggers(ctx context.Context, tx *sql.Tx) error {
rows, err := tx.QueryContext(ctx, `
SELECT quote_ident(n.nspname) || '.' || quote_ident(rel.relname) AS tbl,
quote_ident(t.tgname) AS trigger_name
FROM pg_trigger t
JOIN pg_class rel ON rel.oid = t.tgrelid
JOIN pg_namespace n ON n.oid = rel.relnamespace
WHERE t.tgenabled = 'D'
AND t.tgisinternal = false
AND n.nspname NOT IN ('pglogical', 'pg_catalog', 'information_schema')
`)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var table, trigger string
if err := rows.Scan(&table, &trigger); err != nil {
return err
}
fmt.Printf("→ Dropping disabled trigger %s on %s\n", trigger, table)
if _, err := tx.ExecContext(ctx, fmt.Sprintf("DROP TRIGGER IF EXISTS %s ON %s", trigger, table)); err != nil {
return err
}
fmt.Printf("✅ Dropped trigger %s on %s\n", trigger, table)
}
return rows.Err()
}
func dropTemporaryTables(ctx context.Context, tx *sql.Tx) error {
rows, err := tx.QueryContext(ctx, `
SELECT quote_ident(table_schema) || '.' || quote_ident(table_name)
FROM information_schema.tables
WHERE table_schema NOT IN ('pglogical', 'pg_catalog', 'information_schema')
AND (table_name LIKE 'tmp_%' OR table_name LIKE 'temp_%' OR table_name LIKE 'backup_%')
`)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var table string
if err := rows.Scan(&table); err != nil {
return err
}
fmt.Printf("→ Dropping temporary table %s\n", table)
if _, err := tx.ExecContext(ctx, fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE", table)); err != nil {
return err
}
fmt.Printf("✅ Dropped table %s\n", table)
}
return rows.Err()
}