feat: provide foreign-key-free fresh install baseline
This commit is contained in:
@@ -19,6 +19,15 @@ import (
|
||||
//go:embed migrations/*.up.sql
|
||||
var migrationFiles embed.FS
|
||||
|
||||
// freshInstallBaseline is the current schema without the historical foreign
|
||||
// key DDL. Existing databases keep using the immutable forward migration
|
||||
// chain; only an empty database takes this path.
|
||||
//
|
||||
//go:embed baseline.sql
|
||||
var freshInstallBaseline string
|
||||
|
||||
const freshInstallBaselineVersion uint64 = 28
|
||||
|
||||
type Migration struct {
|
||||
Version uint64
|
||||
Name string
|
||||
@@ -95,6 +104,16 @@ func Migrate(ctx context.Context, db *sql.DB) error {
|
||||
_ = conn.Raw(func(any) error { return driver.ErrBadConn })
|
||||
}
|
||||
}()
|
||||
var ledgerExists, businessTables int
|
||||
if err := conn.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='orbit_schema_migrations'`).Scan(&ledgerExists); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := conn.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND LEFT(TABLE_NAME,6) IN ('orbit_','pulse_','weave_') AND TABLE_NAME<>'orbit_schema_migrations'`).Scan(&businessTables); err != nil {
|
||||
return err
|
||||
}
|
||||
if ledgerExists == 0 && businessTables > 0 {
|
||||
return errors.New("Orbit tables exist without migration ledger; refuse to guess schema history")
|
||||
}
|
||||
if _, err := conn.ExecContext(ctx, `CREATE TABLE IF NOT EXISTS orbit_schema_migrations (
|
||||
version BIGINT UNSIGNED PRIMARY KEY,
|
||||
name VARCHAR(191) NOT NULL,
|
||||
@@ -122,6 +141,12 @@ func Migrate(ctx context.Context, db *sql.DB) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(applied) == 0 && businessTables > 0 {
|
||||
return errors.New("Orbit tables exist with an empty migration ledger; reconcile schema history before proceeding")
|
||||
}
|
||||
if len(applied) == 0 && businessTables == 0 {
|
||||
return applyFreshInstallBaseline(ctx, conn, migrations)
|
||||
}
|
||||
known := map[uint64]Migration{}
|
||||
for _, m := range migrations {
|
||||
known[m.Version] = m
|
||||
@@ -166,6 +191,41 @@ func Migrate(ctx context.Context, db *sql.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyFreshInstallBaseline creates a new database directly at the current
|
||||
// schema version. It records every historical migration as dirty before DDL so
|
||||
// an interrupted baseline remains blocked for operator reconciliation, just
|
||||
// like an interrupted ordinary migration. The baseline is intentionally
|
||||
// limited to an empty database; it never rewrites or reinterprets an upgrade.
|
||||
func applyFreshInstallBaseline(ctx context.Context, conn *sql.Conn, migrations []Migration) error {
|
||||
if len(migrations) == 0 || migrations[len(migrations)-1].Version != freshInstallBaselineVersion {
|
||||
return errors.New("fresh-install baseline is out of date; regenerate it before release")
|
||||
}
|
||||
statements, err := splitSQL(freshInstallBaseline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fresh-install baseline: %w", err)
|
||||
}
|
||||
for _, statement := range statements {
|
||||
upper := strings.ToUpper(statement)
|
||||
if strings.Contains(upper, "FOREIGN KEY") || strings.Contains(upper, " REFERENCES ") {
|
||||
return errors.New("fresh-install baseline contains forbidden foreign-key DDL")
|
||||
}
|
||||
}
|
||||
for _, migration := range migrations {
|
||||
if _, err := conn.ExecContext(ctx, "INSERT INTO orbit_schema_migrations (version,name,checksum,dirty) VALUES (?,?,?,TRUE)", migration.Version, migration.Name, migration.Checksum); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i, statement := range statements {
|
||||
if _, err := conn.ExecContext(ctx, statement); err != nil {
|
||||
return fmt.Errorf("fresh-install baseline statement %d failed (migration ledger remains dirty): %w", i+1, err)
|
||||
}
|
||||
}
|
||||
if _, err := conn.ExecContext(ctx, "UPDATE orbit_schema_migrations SET dirty=FALSE, applied_at=UTC_TIMESTAMP(6)"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Status(ctx context.Context, db *sql.DB) ([]MigrationStatus, error) {
|
||||
rows, err := db.QueryContext(ctx, "SELECT version,name,checksum,dirty FROM orbit_schema_migrations ORDER BY version")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user