diff --git a/internal/repository/migrate/apply.go b/internal/repository/migrate/apply.go index f902da0..bbbfcef 100644 --- a/internal/repository/migrate/apply.go +++ b/internal/repository/migrate/apply.go @@ -2,14 +2,18 @@ package migrate import ( "database/sql" + "errors" "fmt" "strings" "time" + + "github.com/go-sql-driver/mysql" ) // EnsureSchema applies all "up" migrations when the database has no tables. func EnsureSchema(db *sql.DB, dir string) error { - if err := ensureSchemaMigrations(db); err != nil { + columnName, err := ensureSchemaMigrations(db) + if err != nil { return err } @@ -28,14 +32,14 @@ func EnsureSchema(db *sql.DB, dir string) error { if err := execStatements(db, migration.SQL); err != nil { return fmt.Errorf("apply %s: %w", migration.Name, err) } - if err := recordMigration(db, migration.Name); err != nil { + if err := recordMigration(db, columnName, migration.Name); err != nil { return err } } return nil } - applied, err := loadAppliedMigrations(db) + applied, err := loadAppliedMigrations(db, columnName) if err != nil { return err } @@ -46,7 +50,7 @@ func EnsureSchema(db *sql.DB, dir string) error { if err := execStatements(db, migration.SQL); err != nil { return fmt.Errorf("apply %s: %w", migration.Name, err) } - if err := recordMigration(db, migration.Name); err != nil { + if err := recordMigration(db, columnName, migration.Name); err != nil { return err } } @@ -70,23 +74,71 @@ func execStatements(db *sql.DB, sqlText string) error { continue } if _, err := db.Exec(stmt); err != nil { + var mysqlErr *mysql.MySQLError + if errors.As(err, &mysqlErr) && (mysqlErr.Number == 1060 || mysqlErr.Number == 1061) { + continue + } return err } } return nil } -func ensureSchemaMigrations(db *sql.DB) error { - _, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations ( +func ensureSchemaMigrations(db *sql.DB) (string, error) { + column, err := schemaMigrationsColumn(db) + if err != nil { + return "", err + } + if column != "" { + return column, nil + } + + if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS schema_migrations ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL UNIQUE, applied_at TIMESTAMP NOT NULL - )`) - return err + )`); err != nil { + return "", err + } + + return "name", nil } -func loadAppliedMigrations(db *sql.DB) (map[string]bool, error) { - rows, err := db.Query(`SELECT name FROM schema_migrations`) +func schemaMigrationsColumn(db *sql.DB) (string, error) { + var nameCount int + if err := db.QueryRow(` + SELECT COUNT(*) + FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = 'schema_migrations' + AND column_name = 'name' + `).Scan(&nameCount); err != nil { + return "", err + } + if nameCount > 0 { + return "name", nil + } + + var versionCount int + if err := db.QueryRow(` + SELECT COUNT(*) + FROM information_schema.columns + WHERE table_schema = DATABASE() + AND table_name = 'schema_migrations' + AND column_name = 'version' + `).Scan(&versionCount); err != nil { + return "", err + } + if versionCount > 0 { + return "version", nil + } + + return "", nil +} + +func loadAppliedMigrations(db *sql.DB, column string) (map[string]bool, error) { + query := fmt.Sprintf("SELECT %s FROM schema_migrations", column) + rows, err := db.Query(query) if err != nil { return nil, err } @@ -106,7 +158,8 @@ func loadAppliedMigrations(db *sql.DB) (map[string]bool, error) { return applied, nil } -func recordMigration(db *sql.DB, name string) error { - _, err := db.Exec(`INSERT INTO schema_migrations (name, applied_at) VALUES (?, ?)`, name, time.Now().UTC()) +func recordMigration(db *sql.DB, column, name string) error { + query := fmt.Sprintf("INSERT INTO schema_migrations (%s, applied_at) VALUES (?, ?)", column) + _, err := db.Exec(query, name, time.Now().UTC()) return err }