150 lines
4.8 KiB
Go
150 lines
4.8 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/steveyegge/beads/internal/types"
|
|
)
|
|
|
|
// UpdateIssueID updates an issue ID and all its text fields in a single transaction
|
|
func (s *SQLiteStorage) UpdateIssueID(ctx context.Context, oldID, newID string, issue *types.Issue, actor string) error {
|
|
// Get exclusive connection to ensure PRAGMA applies
|
|
conn, err := s.db.Conn(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get connection: %w", err)
|
|
}
|
|
defer func() { _ = conn.Close() }()
|
|
|
|
// Disable foreign keys on this specific connection
|
|
_, err = conn.ExecContext(ctx, `PRAGMA foreign_keys = OFF`)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to disable foreign keys: %w", err)
|
|
}
|
|
|
|
tx, err := conn.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to begin transaction: %w", err)
|
|
}
|
|
defer func() { _ = tx.Rollback() }()
|
|
|
|
result, err := tx.ExecContext(ctx, `
|
|
UPDATE issues
|
|
SET id = ?, title = ?, description = ?, design = ?, acceptance_criteria = ?, notes = ?, updated_at = ?
|
|
WHERE id = ?
|
|
`, newID, issue.Title, issue.Description, issue.Design, issue.AcceptanceCriteria, issue.Notes, time.Now(), oldID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update issue ID: %w", err)
|
|
}
|
|
|
|
rows, err := result.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get rows affected: %w", err)
|
|
}
|
|
if rows == 0 {
|
|
return fmt.Errorf("issue not found: %s", oldID)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `UPDATE dependencies SET issue_id = ? WHERE issue_id = ?`, newID, oldID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update issue_id in dependencies: %w", err)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `UPDATE dependencies SET depends_on_id = ? WHERE depends_on_id = ?`, newID, oldID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update depends_on_id in dependencies: %w", err)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `UPDATE events SET issue_id = ? WHERE issue_id = ?`, newID, oldID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update events: %w", err)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `UPDATE labels SET issue_id = ? WHERE issue_id = ?`, newID, oldID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update labels: %w", err)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `UPDATE comments SET issue_id = ? WHERE issue_id = ?`, newID, oldID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update comments: %w", err)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `
|
|
UPDATE dirty_issues SET issue_id = ? WHERE issue_id = ?
|
|
`, newID, oldID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update dirty_issues: %w", err)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `UPDATE issue_snapshots SET issue_id = ? WHERE issue_id = ?`, newID, oldID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update issue_snapshots: %w", err)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `UPDATE compaction_snapshots SET issue_id = ? WHERE issue_id = ?`, newID, oldID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update compaction_snapshots: %w", err)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `
|
|
INSERT INTO dirty_issues (issue_id, marked_at)
|
|
VALUES (?, ?)
|
|
ON CONFLICT (issue_id) DO UPDATE SET marked_at = excluded.marked_at
|
|
`, newID, time.Now())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to mark issue dirty: %w", err)
|
|
}
|
|
|
|
_, err = tx.ExecContext(ctx, `
|
|
INSERT INTO events (issue_id, event_type, actor, old_value, new_value)
|
|
VALUES (?, 'renamed', ?, ?, ?)
|
|
`, newID, actor, oldID, newID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to record rename event: %w", err)
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// RenameDependencyPrefix updates the prefix in all dependency records
|
|
// GH#630: This was previously a no-op, causing dependencies to break after rename-prefix
|
|
func (s *SQLiteStorage) RenameDependencyPrefix(ctx context.Context, oldPrefix, newPrefix string) error {
|
|
// Update issue_id column
|
|
_, err := s.db.ExecContext(ctx, `
|
|
UPDATE dependencies
|
|
SET issue_id = ? || substr(issue_id, length(?) + 1)
|
|
WHERE issue_id LIKE ? || '%'
|
|
`, newPrefix, oldPrefix, oldPrefix)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update issue_id in dependencies: %w", err)
|
|
}
|
|
|
|
// Update depends_on_id column
|
|
_, err = s.db.ExecContext(ctx, `
|
|
UPDATE dependencies
|
|
SET depends_on_id = ? || substr(depends_on_id, length(?) + 1)
|
|
WHERE depends_on_id LIKE ? || '%'
|
|
`, newPrefix, oldPrefix, oldPrefix)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update depends_on_id in dependencies: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RenameCounterPrefix is a no-op with hash-based IDs (bd-8e05)
|
|
// Kept for backward compatibility with rename-prefix command
|
|
func (s *SQLiteStorage) RenameCounterPrefix(ctx context.Context, oldPrefix, newPrefix string) error {
|
|
// Hash-based IDs don't use counters, so nothing to update
|
|
return nil
|
|
}
|
|
|
|
// ResetCounter is a no-op with hash-based IDs (bd-8e05)
|
|
// Kept for backward compatibility
|
|
func (s *SQLiteStorage) ResetCounter(ctx context.Context, prefix string) error {
|
|
// Hash-based IDs don't use counters, so nothing to reset
|
|
return nil
|
|
}
|