Fix bd-5ots: Eliminate N+1 query in scanIssues with batch label loading
This commit is contained in:
@@ -650,6 +650,9 @@ func (s *SQLiteStorage) DetectCycles(ctx context.Context) ([][]*types.Issue, err
|
|||||||
// Helper function to scan issues from rows
|
// Helper function to scan issues from rows
|
||||||
func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*types.Issue, error) {
|
func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*types.Issue, error) {
|
||||||
var issues []*types.Issue
|
var issues []*types.Issue
|
||||||
|
var issueIDs []string
|
||||||
|
|
||||||
|
// First pass: scan all issues
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var issue types.Issue
|
var issue types.Issue
|
||||||
var contentHash sql.NullString
|
var contentHash sql.NullString
|
||||||
@@ -689,14 +692,21 @@ func (s *SQLiteStorage) scanIssues(ctx context.Context, rows *sql.Rows) ([]*type
|
|||||||
issue.SourceRepo = sourceRepo.String
|
issue.SourceRepo = sourceRepo.String
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch labels for this issue
|
|
||||||
labels, err := s.GetLabels(ctx, issue.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get labels for issue %s: %w", issue.ID, err)
|
|
||||||
}
|
|
||||||
issue.Labels = labels
|
|
||||||
|
|
||||||
issues = append(issues, &issue)
|
issues = append(issues, &issue)
|
||||||
|
issueIDs = append(issueIDs, issue.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: batch-load labels for all issues
|
||||||
|
labelsMap, err := s.GetLabelsForIssues(ctx, issueIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to batch get labels: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign labels to issues
|
||||||
|
for _, issue := range issues {
|
||||||
|
if labels, ok := labelsMap[issue.ID]; ok {
|
||||||
|
issue.Labels = labels
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return issues, nil
|
return issues, nil
|
||||||
|
|||||||
@@ -93,6 +93,56 @@ func (s *SQLiteStorage) GetLabels(ctx context.Context, issueID string) ([]string
|
|||||||
return labels, nil
|
return labels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLabelsForIssues fetches labels for multiple issues in a single query
|
||||||
|
// Returns a map of issue_id -> []labels
|
||||||
|
func (s *SQLiteStorage) GetLabelsForIssues(ctx context.Context, issueIDs []string) (map[string][]string, error) {
|
||||||
|
if len(issueIDs) == 0 {
|
||||||
|
return make(map[string][]string), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build placeholders for IN clause
|
||||||
|
placeholders := make([]interface{}, len(issueIDs))
|
||||||
|
for i, id := range issueIDs {
|
||||||
|
placeholders[i] = id
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT issue_id, label
|
||||||
|
FROM labels
|
||||||
|
WHERE issue_id IN (%s)
|
||||||
|
ORDER BY issue_id, label
|
||||||
|
`, buildPlaceholders(len(issueIDs)))
|
||||||
|
|
||||||
|
rows, err := s.db.QueryContext(ctx, query, placeholders...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to batch get labels: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = rows.Close() }()
|
||||||
|
|
||||||
|
result := make(map[string][]string)
|
||||||
|
for rows.Next() {
|
||||||
|
var issueID, label string
|
||||||
|
if err := rows.Scan(&issueID, &label); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result[issueID] = append(result[issueID], label)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildPlaceholders creates a comma-separated list of SQL placeholders
|
||||||
|
func buildPlaceholders(count int) string {
|
||||||
|
if count == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
result := "?"
|
||||||
|
for i := 1; i < count; i++ {
|
||||||
|
result += ",?"
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// GetIssuesByLabel returns issues with a specific label
|
// GetIssuesByLabel returns issues with a specific label
|
||||||
func (s *SQLiteStorage) GetIssuesByLabel(ctx context.Context, label string) ([]*types.Issue, error) {
|
func (s *SQLiteStorage) GetIssuesByLabel(ctx context.Context, label string) ([]*types.Issue, error) {
|
||||||
rows, err := s.db.QueryContext(ctx, `
|
rows, err := s.db.QueryContext(ctx, `
|
||||||
|
|||||||
Reference in New Issue
Block a user