diff --git a/internal/storage/sqlite/schema.go b/internal/storage/sqlite/schema.go index 74167ceb..43d43ba2 100644 --- a/internal/storage/sqlite/schema.go +++ b/internal/storage/sqlite/schema.go @@ -39,6 +39,7 @@ CREATE TABLE IF NOT EXISTS dependencies ( CREATE INDEX IF NOT EXISTS idx_dependencies_issue ON dependencies(issue_id); CREATE INDEX IF NOT EXISTS idx_dependencies_depends_on ON dependencies(depends_on_id); +CREATE INDEX IF NOT EXISTS idx_dependencies_depends_on_type ON dependencies(depends_on_id, type); -- Labels table CREATE TABLE IF NOT EXISTS labels ( diff --git a/internal/storage/sqlite/sqlite.go b/internal/storage/sqlite/sqlite.go index 89c51222..1d27fe6b 100644 --- a/internal/storage/sqlite/sqlite.go +++ b/internal/storage/sqlite/sqlite.go @@ -60,6 +60,11 @@ func New(path string) (*SQLiteStorage, error) { return nil, fmt.Errorf("failed to migrate external_ref column: %w", err) } + // Migrate existing databases to add composite index on dependencies + if err := migrateCompositeIndexes(db); err != nil { + return nil, fmt.Errorf("failed to migrate composite indexes: %w", err) + } + return &SQLiteStorage{ db: db, }, nil @@ -201,6 +206,36 @@ func migrateExternalRefColumn(db *sql.DB) error { return nil } +// migrateCompositeIndexes checks if composite indexes exist and creates them if missing. +// This ensures existing databases get performance optimizations from new indexes. +func migrateCompositeIndexes(db *sql.DB) error { + // Check if idx_dependencies_depends_on_type exists + var indexName string + err := db.QueryRow(` + SELECT name FROM sqlite_master + WHERE type='index' AND name='idx_dependencies_depends_on_type' + `).Scan(&indexName) + + if err == sql.ErrNoRows { + // Index doesn't exist, create it + _, err := db.Exec(` + CREATE INDEX idx_dependencies_depends_on_type ON dependencies(depends_on_id, type) + `) + if err != nil { + return fmt.Errorf("failed to create composite index idx_dependencies_depends_on_type: %w", err) + } + // Index created successfully + return nil + } + + if err != nil { + return fmt.Errorf("failed to check for composite index: %w", err) + } + + // Index exists, no migration needed + return nil +} + // getNextIDForPrefix atomically generates the next ID for a given prefix // Uses the issue_counters table for atomic, cross-process ID generation func (s *SQLiteStorage) getNextIDForPrefix(ctx context.Context, prefix string) (int, error) {