diff --git a/internal/storage/sqlite/migrations/028_tombstone_closed_at.go b/internal/storage/sqlite/migrations/028_tombstone_closed_at.go index 7b2eff4e..32d7b66c 100644 --- a/internal/storage/sqlite/migrations/028_tombstone_closed_at.go +++ b/internal/storage/sqlite/migrations/028_tombstone_closed_at.go @@ -3,6 +3,7 @@ package migrations import ( "database/sql" "fmt" + "strings" ) // MigrateTombstoneClosedAt updates the closed_at constraint to allow tombstones @@ -22,8 +23,20 @@ func MigrateTombstoneClosedAt(db *sql.DB) error { // SQLite doesn't support ALTER TABLE to modify CHECK constraints // We must recreate the table with the new constraint + // Idempotency check: see if the new CHECK constraint already exists + // The new constraint contains "status = 'tombstone'" which the old one didn't + var tableSql string + err := db.QueryRow(`SELECT sql FROM sqlite_master WHERE type='table' AND name='issues'`).Scan(&tableSql) + if err != nil { + return fmt.Errorf("failed to get issues table schema: %w", err) + } + // If the schema already has the tombstone clause, migration is already applied + if strings.Contains(tableSql, "status = 'tombstone'") || strings.Contains(tableSql, `status = "tombstone"`) { + return nil + } + // Step 0: Drop views that depend on the issues table - _, err := db.Exec(`DROP VIEW IF EXISTS ready_issues`) + _, err = db.Exec(`DROP VIEW IF EXISTS ready_issues`) if err != nil { return fmt.Errorf("failed to drop ready_issues view: %w", err) }