Files
OpenFlare/internal/db/migrator/202606050001_bridge_legacy.go
T
2026-06-19 22:18:52 +08:00

205 lines
5.2 KiB
Go

// Copyright 2026 Arctel.net
// SPDX-License-Identifier: Apache-2.0
package migrator
import (
"context"
"database/sql"
"fmt"
"strings"
"github.com/pressly/goose/v3"
)
func init() {
goose.AddMigrationContext(up202606050001, down202606050001)
}
func up202606050001(ctx context.Context, tx *sql.Tx) error {
dialect := gooseDialect()
if dialect == dialectSqlite {
if err := fixSqliteGooseDbVersion(ctx, tx); err != nil {
return err
}
}
tableExistsQuery := tableExistsSQL(dialect)
checkTable := func(name string) (bool, error) {
var count int
err := tx.QueryRowContext(ctx, tableExistsQuery, name).Scan(&count)
return count > 0, err
}
// Check if this is a legacy database (by checking if the "options" table exists).
// On a clean install, "options" won't exist, so this will be a quick no-op.
hasOptions, err := checkTable("options")
if err != nil {
return fmt.Errorf("check table options failed: %w", err)
}
if !hasOptions {
// Clean install or already migrated, no action needed.
return nil
}
// Helper to drop table if it exists
dropTable := func(tableName string) error {
dropSQL := fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName)
if dialect == dialectPostgres {
dropSQL += cascadeSuffix
}
if _, err := tx.ExecContext(ctx, dropSQL); err != nil {
return fmt.Errorf("drop table %s failed: %w", tableName, err)
}
return nil
}
// 1. Drop legacy observability and log tables to free space and avoid conflict
obsPrefixes := []string{
"node_system_profiles",
"node_health_events",
"node_access_logs_",
"node_observation_openresties_",
"node_observation_frpcs_",
"node_observation_frps_",
"node_metric_snapshots_",
"node_request_reports_",
}
for _, prefix := range obsPrefixes {
if err := dropTablesWithPrefix(ctx, tx, tablesWithPrefixSQL(dialect), prefix, dropTable); err != nil {
return err
}
}
// 2. Rename custom and platform legacy tables to legacy_ prefix
tablesToRename := []string{
"users",
"auth_sources",
"external_accounts",
"options",
"origins",
"apply_logs",
"proxy_routes",
"nodes",
"waf_rule_groups",
"waf_rule_group_bindings",
"waf_ip_groups",
"tls_certificates",
"managed_domains",
"dns_accounts",
"acme_accounts",
"config_versions",
"pages_projects",
"pages_deployments",
"pages_deployment_files",
}
for _, oldName := range tablesToRename {
if err := renameLegacyTable(ctx, tx, oldName, checkTable, dropTable); err != nil {
return err
}
}
return nil
}
func dropTablesWithPrefix(ctx context.Context, tx *sql.Tx, query, prefix string, dropTable func(string) error) error {
rows, err := tx.QueryContext(ctx, query, prefix+"%")
if err != nil {
return fmt.Errorf("query legacy tables for prefix %s failed: %w", prefix, err)
}
defer func() {
_ = rows.Close()
}()
var tables []string
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
return err
}
tables = append(tables, name)
}
for _, table := range tables {
if err := dropTable(table); err != nil {
return err
}
}
return nil
}
func renameLegacyTable(ctx context.Context, tx *sql.Tx, oldName string, checkTable func(string) (bool, error), dropTable func(string) error) error {
exists, err := checkTable(oldName)
if err != nil {
return err
}
if !exists {
return nil
}
newName := "legacy_" + oldName
newExists, err := checkTable(newName)
if err != nil {
return err
}
if newExists {
if err := dropTable(newName); err != nil {
return err
}
}
renameSQL := fmt.Sprintf("ALTER TABLE %s RENAME TO %s", oldName, newName)
if _, err := tx.ExecContext(ctx, renameSQL); err != nil {
return fmt.Errorf("rename table %s to %s failed: %w", oldName, newName, err)
}
return nil
}
func down202606050001(_ context.Context, _ *sql.Tx) error {
// Down migration is a no-op as the legacy DB is rolled forward during migration.
return nil
}
func fixSqliteGooseDbVersion(ctx context.Context, tx *sql.Tx) error {
var createSQL string
err := tx.QueryRowContext(ctx, "SELECT sql FROM sqlite_master WHERE type='table' AND name='goose_db_version'").Scan(&createSQL)
if err != nil {
if err == sql.ErrNoRows {
return nil
}
return err
}
if strings.Contains(strings.ToUpper(createSQL), "AUTOINCREMENT") {
return nil // Already upgraded
}
if _, err := tx.ExecContext(ctx, "ALTER TABLE goose_db_version RENAME TO old_goose_db_version"); err != nil {
return fmt.Errorf("rename goose_db_version failed: %w", err)
}
newTableSQL := `CREATE TABLE goose_db_version (
id INTEGER PRIMARY KEY AUTOINCREMENT,
version_id INTEGER NOT NULL,
is_applied INTEGER NOT NULL,
tstamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);`
if _, err := tx.ExecContext(ctx, newTableSQL); err != nil {
return fmt.Errorf("create new goose_db_version failed: %w", err)
}
copySQL := `INSERT INTO goose_db_version (id, version_id, is_applied, tstamp)
SELECT id, version_id, is_applied, tstamp FROM old_goose_db_version;`
if _, err := tx.ExecContext(ctx, copySQL); err != nil {
return fmt.Errorf("copy goose_db_version data failed: %w", err)
}
if _, err := tx.ExecContext(ctx, "DROP TABLE old_goose_db_version"); err != nil {
return fmt.Errorf("drop old_goose_db_version failed: %w", err)
}
return nil
}