fix: migration 028 handles missing created_by column
The migration was using SELECT * which fails when migrating databases that predate the created_by column (34 columns vs 35). Now explicitly lists columns and provides empty default for created_by if missing. Also fixes missed Wisp→Ephemeral rename in multirepo_test.go. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -95,10 +95,73 @@ func MigrateTombstoneClosedAt(db *sql.DB) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Copy data from old table to new table
|
// Step 2: Copy data from old table to new table
|
||||||
_, err = db.Exec(`
|
// We need to check if created_by column exists in the old table
|
||||||
INSERT INTO issues_new
|
// If not, we insert a default empty string for it
|
||||||
SELECT * FROM issues
|
var hasCreatedBy bool
|
||||||
`)
|
rows, err := db.Query(`PRAGMA table_info(issues)`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get table info: %w", err)
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var cid int
|
||||||
|
var name, ctype string
|
||||||
|
var notnull, pk int
|
||||||
|
var dflt interface{}
|
||||||
|
if err := rows.Scan(&cid, &name, &ctype, ¬null, &dflt, &pk); err != nil {
|
||||||
|
rows.Close()
|
||||||
|
return fmt.Errorf("failed to scan table info: %w", err)
|
||||||
|
}
|
||||||
|
if name == "created_by" {
|
||||||
|
hasCreatedBy = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
|
||||||
|
var insertSQL string
|
||||||
|
if hasCreatedBy {
|
||||||
|
// Old table has created_by, copy all columns directly
|
||||||
|
insertSQL = `
|
||||||
|
INSERT INTO issues_new (
|
||||||
|
id, content_hash, title, description, design, acceptance_criteria, notes,
|
||||||
|
status, priority, issue_type, assignee, estimated_minutes, created_at,
|
||||||
|
created_by, updated_at, closed_at, external_ref, source_repo, compaction_level,
|
||||||
|
compacted_at, compacted_at_commit, original_size, deleted_at, deleted_by,
|
||||||
|
delete_reason, original_type, sender, ephemeral, close_reason, pinned,
|
||||||
|
is_template, await_type, await_id, timeout_ns, waiters
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id, content_hash, title, description, design, acceptance_criteria, notes,
|
||||||
|
status, priority, issue_type, assignee, estimated_minutes, created_at,
|
||||||
|
created_by, updated_at, closed_at, external_ref, source_repo, compaction_level,
|
||||||
|
compacted_at, compacted_at_commit, original_size, deleted_at, deleted_by,
|
||||||
|
delete_reason, original_type, sender, ephemeral, close_reason, pinned,
|
||||||
|
is_template, await_type, await_id, timeout_ns, waiters
|
||||||
|
FROM issues
|
||||||
|
`
|
||||||
|
} else {
|
||||||
|
// Old table doesn't have created_by, use empty string default
|
||||||
|
insertSQL = `
|
||||||
|
INSERT INTO issues_new (
|
||||||
|
id, content_hash, title, description, design, acceptance_criteria, notes,
|
||||||
|
status, priority, issue_type, assignee, estimated_minutes, created_at,
|
||||||
|
created_by, updated_at, closed_at, external_ref, source_repo, compaction_level,
|
||||||
|
compacted_at, compacted_at_commit, original_size, deleted_at, deleted_by,
|
||||||
|
delete_reason, original_type, sender, ephemeral, close_reason, pinned,
|
||||||
|
is_template, await_type, await_id, timeout_ns, waiters
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id, content_hash, title, description, design, acceptance_criteria, notes,
|
||||||
|
status, priority, issue_type, assignee, estimated_minutes, created_at,
|
||||||
|
'', updated_at, closed_at, external_ref, source_repo, compaction_level,
|
||||||
|
compacted_at, compacted_at_commit, original_size, deleted_at, deleted_by,
|
||||||
|
delete_reason, original_type, sender, ephemeral, close_reason, pinned,
|
||||||
|
is_template, await_type, await_id, timeout_ns, waiters
|
||||||
|
FROM issues
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Exec(insertSQL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to copy issues data: %w", err)
|
return fmt.Errorf("failed to copy issues data: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -909,7 +909,7 @@ func TestUpsertPreservesGateFields(t *testing.T) {
|
|||||||
Status: types.StatusOpen,
|
Status: types.StatusOpen,
|
||||||
Priority: 1,
|
Priority: 1,
|
||||||
IssueType: types.TypeGate,
|
IssueType: types.TypeGate,
|
||||||
Wisp: true,
|
Ephemeral: true,
|
||||||
AwaitType: "gh:run",
|
AwaitType: "gh:run",
|
||||||
AwaitID: "123456789",
|
AwaitID: "123456789",
|
||||||
Timeout: 30 * 60 * 1000000000, // 30 minutes in nanoseconds
|
Timeout: 30 * 60 * 1000000000, // 30 minutes in nanoseconds
|
||||||
|
|||||||
Reference in New Issue
Block a user