Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 67 additions & 15 deletions internal/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -1851,44 +1851,96 @@ func (s *Store) ResolveEdgesForNames(ctx context.Context, repoID int64, names []
return 0, nil
}

// Query shape: do a single unresolved-edge scan, then filter in Go.
// This avoids repeated SQL queries with many OR/L LIKE clauses (which are
// difficult for SQLite to optimize with indexes).
nameSet := make(map[string]struct{}, len(unique))
for _, name := range unique {
nameSet[name] = struct{}{}
}

// Candidate selection:
//
// 1) Use indexed exact matches for dst_name = <name> (covers the common case
// where unresolved edges reference the simple symbol name directly).
// 2) Only if needed, scan unresolved edges that contain a '.' and filter in Go
// for suffix matches (dst_name ends with ".<name>").
//
// This avoids scanning the full unresolved-edge set on large repos where many
// unresolved edges have simple (non-qualified) dst_name values.
targetByID := make(map[int64]edgeTarget, 64)

// Keep under sqliteDefaultMaxVariables (repoID + N names).
for start := 0; start < len(unique); start += sqliteInClauseBatchSize {
end := min(start+sqliteInClauseBatchSize, len(unique))
chunk := unique[start:end]

placeholders := strings.TrimRight(strings.Repeat("?,", len(chunk)), ",")
query := `SELECT id, dst_name FROM edges WHERE repo_id = ? AND dst_symbol_id IS NULL AND dst_name IN (` + placeholders + `)`
args := make([]any, 1+len(chunk))
args[0] = repoID
for i, name := range chunk {
args[i+1] = name
}

rows, err := s.db.QueryContext(ctx, query, args...)
if err != nil {
return 0, err
}
for rows.Next() {
var id int64
var dstName string
if err := rows.Scan(&id, &dstName); err != nil {
_ = rows.Close()
return 0, err
}
targetByID[id] = edgeTarget{edgeID: id, dstName: dstName}
}
if err := rows.Err(); err != nil {
_ = rows.Close()
return 0, err
}
if err := rows.Close(); err != nil {
return 0, err
}
}

// Suffix matching requires looking at qualified dst_name values. Keep this
// as a single pass over the qualified unresolved set (no repeated LIKE
// queries), but avoid scanning simple dst_name values entirely.
rows, err := s.db.QueryContext(ctx, `
SELECT id, dst_name
FROM edges
WHERE repo_id = ? AND dst_symbol_id IS NULL AND dst_name != ''
WHERE repo_id = ? AND dst_symbol_id IS NULL AND dst_name != '' AND instr(dst_name, '.') > 0
`, repoID)
if err != nil {
return 0, err
}
defer rows.Close()

nameSet := make(map[string]struct{}, len(unique))
for _, name := range unique {
nameSet[name] = struct{}{}
}

targets := make([]edgeTarget, 0, 64)
for rows.Next() {
var id int64
var dstName string
if err := rows.Scan(&id, &dstName); err != nil {
_ = rows.Close()
return 0, err
}
if _, ok := nameSet[dstName]; ok {
targets = append(targets, edgeTarget{edgeID: id, dstName: dstName})
if _, ok := targetByID[id]; ok {
continue
}
if dot := strings.LastIndexByte(dstName, '.'); dot >= 0 && dot+1 < len(dstName) {
if _, ok := nameSet[dstName[dot+1:]]; ok {
targets = append(targets, edgeTarget{edgeID: id, dstName: dstName})
targetByID[id] = edgeTarget{edgeID: id, dstName: dstName}
}
}
}
if err := rows.Err(); err != nil {
_ = rows.Close()
return 0, err
}
if err := rows.Close(); err != nil {
return 0, err
}

targets := make([]edgeTarget, 0, len(targetByID))
for _, target := range targetByID {
targets = append(targets, target)
}
if err := s.resolveEdgeTargets(ctx, repoID, targets); err != nil {
return 0, err
}
Expand Down
Loading