Add auto-migration for dirty_issues table

Implement automatic database migration to add the dirty_issues table
for existing databases that were created before the incremental export
feature (bd-39) was implemented.

Changes:
- Add migrateDirtyIssuesTable() function in sqlite.go
- Check for dirty_issues table existence on database initialization
- Create table and index if missing (silent migration)
- Call migration after schema initialization in New()

The migration:
- Queries sqlite_master to check if dirty_issues table exists
- If missing, creates the table with proper schema and index
- Happens automatically on first database access after upgrade
- No user intervention required
- Fails safely if table already exists (no-op)

Testing:
- Created test database without dirty_issues table
- Verified table was auto-created on first command
- Verified issue was properly marked dirty
- All existing tests pass

This makes the incremental export feature (bd-39) work seamlessly
with existing databases without requiring manual migration steps.

Closes bd-51

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-10-14 00:19:10 -07:00
parent bafb2801c5
commit f3a61a6458

View File

@@ -48,6 +48,11 @@ func New(path string) (*SQLiteStorage, error) {
return nil, fmt.Errorf("failed to initialize schema: %w", err)
}
// Migrate existing databases to add dirty_issues table if missing
if err := migrateDirtyIssuesTable(db); err != nil {
return nil, fmt.Errorf("failed to migrate dirty_issues table: %w", err)
}
// Get next ID
nextID := getNextID(db)
@@ -57,6 +62,41 @@ func New(path string) (*SQLiteStorage, error) {
}, nil
}
// migrateDirtyIssuesTable checks if the dirty_issues table exists and creates it if missing.
// This ensures existing databases created before the incremental export feature get migrated automatically.
func migrateDirtyIssuesTable(db *sql.DB) error {
// Check if dirty_issues table exists
var tableName string
err := db.QueryRow(`
SELECT name FROM sqlite_master
WHERE type='table' AND name='dirty_issues'
`).Scan(&tableName)
if err == sql.ErrNoRows {
// Table doesn't exist, create it
_, err := db.Exec(`
CREATE TABLE dirty_issues (
issue_id TEXT PRIMARY KEY,
marked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE
);
CREATE INDEX idx_dirty_issues_marked_at ON dirty_issues(marked_at);
`)
if err != nil {
return fmt.Errorf("failed to create dirty_issues table: %w", err)
}
// Table created successfully - no need to log, happens silently
return nil
}
if err != nil {
return fmt.Errorf("failed to check for dirty_issues table: %w", err)
}
// Table exists, no migration needed
return nil
}
// getNextID determines the next issue ID to use
func getNextID(db *sql.DB) int {
// Get prefix from config, default to "bd"