remove legacy migration conversion code

Pre-1.0 software with no installed base — no need to handle
converting from old TEXT-based schema_migrations format.

Removed: convertLegacyMigrations, ensureBootstrapVersion,
readLegacyVersions, rebuildMigrationsTable, and
TestApplyMigrationsLegacyConversion.

Simplified bootstrapMigrationsTable to only check existence
and run 000_migration.sql if missing.
This commit is contained in:
user
2026-03-26 08:37:44 -07:00
parent 22cc1a7fb1
commit 6591f77dac
2 changed files with 2 additions and 213 deletions

View File

@@ -83,9 +83,7 @@ func collectMigrations() ([]string, error) {
}
// bootstrapMigrationsTable ensures the schema_migrations table exists by
// applying 000_migration.sql if the table is missing. For databases with a
// legacy TEXT-based schema_migrations table, it converts to the new INTEGER
// format.
// applying 000_migration.sql if the table is missing.
func bootstrapMigrationsTable(ctx context.Context, db *sql.DB, log *slog.Logger) error {
var tableExists int
@@ -100,8 +98,7 @@ func bootstrapMigrationsTable(ctx context.Context, db *sql.DB, log *slog.Logger)
return applyBootstrapMigration(ctx, db, log)
}
// Table exists — check for and convert legacy TEXT-based versions.
return convertLegacyMigrations(ctx, db, log)
return nil
}
// applyBootstrapMigration reads and executes 000_migration.sql to create the
@@ -124,139 +121,6 @@ func applyBootstrapMigration(ctx context.Context, db *sql.DB, log *slog.Logger)
return nil
}
// convertLegacyMigrations converts a schema_migrations table that uses
// TEXT filename-based versions (e.g. "001_initial.sql") to INTEGER versions
// (e.g. 1). This is a one-time migration for existing databases.
func convertLegacyMigrations(ctx context.Context, db *sql.DB, log *slog.Logger) error {
// Check if any version looks like a legacy filename (contains underscore).
var legacyCount int
err := db.QueryRowContext(ctx,
"SELECT COUNT(*) FROM schema_migrations WHERE INSTR(CAST(version AS TEXT), '_') > 0",
).Scan(&legacyCount)
if err != nil {
return fmt.Errorf("failed to check for legacy migrations: %w", err)
}
if legacyCount == 0 {
return ensureBootstrapVersion(ctx, db)
}
if log != nil {
log.Info("converting legacy schema_migrations from TEXT to INTEGER format",
"legacy_entries", legacyCount)
}
intVersions, err := readLegacyVersions(ctx, db)
if err != nil {
return err
}
err = rebuildMigrationsTable(ctx, db, intVersions)
if err != nil {
return err
}
if log != nil {
log.Info("legacy migration conversion complete", "versions_converted", len(intVersions))
}
return nil
}
// ensureBootstrapVersion inserts version 0 if it is not already present.
func ensureBootstrapVersion(ctx context.Context, db *sql.DB) error {
_, err := db.ExecContext(ctx,
"INSERT OR IGNORE INTO schema_migrations (version) VALUES (0)")
if err != nil {
return fmt.Errorf("failed to ensure bootstrap version: %w", err)
}
return nil
}
// readLegacyVersions reads all version entries from the legacy schema_migrations
// table and parses them into integer versions.
func readLegacyVersions(ctx context.Context, db *sql.DB) ([]int, error) {
rows, err := db.QueryContext(ctx, "SELECT version FROM schema_migrations")
if err != nil {
return nil, fmt.Errorf("failed to read legacy migrations: %w", err)
}
defer func() { _ = rows.Close() }()
var intVersions []int
for rows.Next() {
var version string
scanErr := rows.Scan(&version)
if scanErr != nil {
return nil, fmt.Errorf("failed to scan legacy version: %w", scanErr)
}
v, parseErr := ParseMigrationVersion(version)
if parseErr != nil {
return nil, fmt.Errorf("failed to parse legacy version %q: %w", version, parseErr)
}
intVersions = append(intVersions, v)
}
rowsErr := rows.Err()
if rowsErr != nil {
return nil, fmt.Errorf("failed to iterate legacy versions: %w", rowsErr)
}
return intVersions, nil
}
// rebuildMigrationsTable drops the old schema_migrations table, recreates it
// via 000_migration.sql, and re-inserts the given integer versions. The entire
// operation runs in a single transaction so a crash cannot lose migration data.
func rebuildMigrationsTable(ctx context.Context, db *sql.DB, versions []int) error {
content, err := migrationsFS.ReadFile("migrations/000_migration.sql")
if err != nil {
return fmt.Errorf("failed to read bootstrap migration 000_migration.sql: %w", err)
}
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("failed to begin transaction for legacy conversion: %w", err)
}
defer func() {
if err != nil {
_ = tx.Rollback()
}
}()
_, err = tx.ExecContext(ctx, "DROP TABLE schema_migrations")
if err != nil {
return fmt.Errorf("failed to drop legacy migrations table: %w", err)
}
_, err = tx.ExecContext(ctx, string(content))
if err != nil {
return fmt.Errorf("failed to create new migrations table: %w", err)
}
for _, v := range versions {
_, err = tx.ExecContext(ctx,
"INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)", v)
if err != nil {
return fmt.Errorf("failed to insert converted version %d: %w", v, err)
}
}
err = tx.Commit()
if err != nil {
return fmt.Errorf("failed to commit legacy conversion: %w", err)
}
return nil
}
// ApplyMigrations applies all pending migrations to db. An optional logger
// may be provided for informational output; pass nil for silent operation.
// This is exported so tests can apply the real schema without the full fx

View File

@@ -115,78 +115,3 @@ func TestApplyMigrationsIdempotent(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, 8, count)
}
func TestApplyMigrationsLegacyConversion(t *testing.T) {
t.Parallel()
db, err := sql.Open("sqlite3", ":memory:?_foreign_keys=on")
require.NoError(t, err)
defer func() { _ = db.Close() }()
ctx := context.Background()
// Simulate the old TEXT-based schema_migrations table.
_, err = db.ExecContext(ctx, `
CREATE TABLE schema_migrations (
version TEXT PRIMARY KEY,
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
require.NoError(t, err)
// Insert legacy filename-based versions (as if migrations 001-007 were applied).
legacyVersions := []string{
"001_initial.sql",
"002_remove_container_id.sql",
"003_add_ports.sql",
"004_add_commit_url.sql",
"005_add_webhook_secret_hash.sql",
"006_add_previous_image_id.sql",
"007_add_resource_limits.sql",
}
for _, v := range legacyVersions {
_, err = db.ExecContext(ctx,
"INSERT INTO schema_migrations (version) VALUES (?)", v)
require.NoError(t, err)
}
// Also create the application tables that would exist on a real database
// so that the migrations (001-007) don't fail when re-applied.
// Actually, since the legacy versions will be converted and recognized
// as already-applied, the DDL migrations won't re-run. We just need
// to make sure ApplyMigrations succeeds.
// Run ApplyMigrations — this should convert legacy versions and
// skip all already-applied migrations.
err = database.ApplyMigrations(ctx, db, nil)
require.NoError(t, err)
// Verify version 0 is now present (from bootstrap).
var zeroVersion int
err = db.QueryRowContext(ctx,
"SELECT version FROM schema_migrations WHERE version = 0",
).Scan(&zeroVersion)
require.NoError(t, err)
assert.Equal(t, 0, zeroVersion)
// Verify all 8 versions are present (0 through 7).
var count int
err = db.QueryRowContext(ctx,
"SELECT COUNT(*) FROM schema_migrations",
).Scan(&count)
require.NoError(t, err)
assert.Equal(t, 8, count)
// Verify versions are stored as integers, not text filenames.
var maxVersion int
err = db.QueryRowContext(ctx,
"SELECT MAX(version) FROM schema_migrations",
).Scan(&maxVersion)
require.NoError(t, err)
assert.Equal(t, 7, maxVersion)
}