remove legacy migration conversion code
All checks were successful
Check / check (pull_request) Successful in 3m15s
All checks were successful
Check / check (pull_request) Successful in 3m15s
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:
@@ -83,9 +83,7 @@ func collectMigrations() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// bootstrapMigrationsTable ensures the schema_migrations table exists by
|
// bootstrapMigrationsTable ensures the schema_migrations table exists by
|
||||||
// applying 000_migration.sql if the table is missing. For databases with a
|
// applying 000_migration.sql if the table is missing.
|
||||||
// legacy TEXT-based schema_migrations table, it converts to the new INTEGER
|
|
||||||
// format.
|
|
||||||
func bootstrapMigrationsTable(ctx context.Context, db *sql.DB, log *slog.Logger) error {
|
func bootstrapMigrationsTable(ctx context.Context, db *sql.DB, log *slog.Logger) error {
|
||||||
var tableExists int
|
var tableExists int
|
||||||
|
|
||||||
@@ -100,8 +98,7 @@ func bootstrapMigrationsTable(ctx context.Context, db *sql.DB, log *slog.Logger)
|
|||||||
return applyBootstrapMigration(ctx, db, log)
|
return applyBootstrapMigration(ctx, db, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Table exists — check for and convert legacy TEXT-based versions.
|
return nil
|
||||||
return convertLegacyMigrations(ctx, db, log)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyBootstrapMigration reads and executes 000_migration.sql to create the
|
// 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
|
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
|
// ApplyMigrations applies all pending migrations to db. An optional logger
|
||||||
// may be provided for informational output; pass nil for silent operation.
|
// 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
|
// This is exported so tests can apply the real schema without the full fx
|
||||||
|
|||||||
@@ -115,78 +115,3 @@ func TestApplyMigrationsIdempotent(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, 8, count)
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user