Documentation
¶
Overview ¶
Package search owns S28's search surface. Postgres FTS (`tsvector` + `pg_trgm`) backs everything; no external search engine. Visibility scoping flows through `policy.VisibilityPredicate` so every query is gated by the same rule the rest of the runtime uses.
Entry points are:
SearchRepos / SearchIssues / SearchUsers / SearchCode — per-type
queries returning slices of result rows + the total count.
ParseQuery — splits a user query string into the FTS query plus
operator filters (repo:, is:, author:, state:).
Index ¶
Constants ¶
const MaxQueryBytes = 256
MaxQueryBytes caps incoming query strings. Tighter than the markdown 1 MiB ceiling because no legitimate search ever needs even 1 KiB.
const PageSize = 20
PageSize is the per-type result count for the full results page. Quick-dropdown uses a smaller cap (see QuickResultsLimit).
const QuickResultsLimit = 5
QuickResultsLimit is the per-type cap for the top-bar quick dropdown. Same shape as GitHub's: tight for keystroke speed.
Variables ¶
var (
ErrEmptyQuery = errors.New("search: query is empty")
)
Errors surfaced to handlers.
Functions ¶
This section is empty.
Types ¶
type CodeResult ¶
type CodeResult struct {
RepoID int64
OwnerUsername string
RepoName string
RefName string
Path string
// PreviewLine is a single line of content extracted near the
// match, when content matched. Empty for path-only hits.
PreviewLine string
Rank float64
}
CodeResult is one row from SearchCode. Either Path or Content (or both) is populated depending on which subquery hit.
func SearchCode ¶
func SearchCode(ctx context.Context, deps Deps, actor policy.Actor, q ParsedQuery, limit, offset int) ([]CodeResult, int64, error)
SearchCode runs a code search across paths and content. Visibility gates the underlying repo set; only repos the actor can read appear.
We run two unioned subqueries:
paths — `tsv @@ plainto_tsquery(...)` on the indexed path
string. Always populated (size cap doesn't apply).
content — `content_tsv @@ plainto_tsquery(...)` OR
`content_trgm % $tsText` (trigram similarity for
camelCase / snake_case identifiers).
Path matches always rank above content matches at equal ts_rank. Within content matches, ts_rank dominates trigram similarity.
type IssueResult ¶
type IssueResult struct {
ID int64
RepoID int64
OwnerUsername string
RepoName string
Number int64
Title string
State string
Kind string // "issue" | "pr"
AuthorName string
UpdatedAt time.Time
Rank float64
}
IssueResult is one row from SearchIssues.
func SearchIssues ¶
func SearchIssues(ctx context.Context, deps Deps, actor policy.Actor, q ParsedQuery, kindFilter string, limit, offset int) ([]IssueResult, int64, error)
SearchIssues runs an issue search visible to actor. Issues inherit visibility from their repo via the same predicate.
Ranking: `ts_rank_cd * state_weight` where open weighs 1.5× over closed (the spec doesn't pin a number; 1.5 is a sensible default that surfaces actionable issues first without burying the closed history).
kindFilter is optional: pass "issue", "pr", or "" for both. The dropdown / Issues tab passes "issue"; the PR tab would pass "pr".
type ParsedQuery ¶
type ParsedQuery struct {
Text string // free-text query (what tsvector matches against)
Phrase string // when a quoted phrase was supplied; empty when not
RepoFilter *RepoFilter
StateFilter string // "open" | "closed" | ""
AuthorFilter string // username or empty
}
ParsedQuery is the result of running a raw user-typed query through ParseQuery. The free-text portion is what flows into `plainto_tsquery` / `phraseto_tsquery`; the operator fields are what compose the SQL `WHERE` filters.
`RepoFilter` carries the `owner/name` pair when the user typed `repo:owner/name`; both halves must be present for the filter to take effect (a bare `repo:foo` without slash is treated as free text).
func ParseQuery ¶
func ParseQuery(raw string) ParsedQuery
ParseQuery splits a raw query string into the free-text portion + recognised operators. v1 supports `repo:`, `is:`, `state:`, `author:`. `is:` and `state:` are aliases.
A quoted run of tokens becomes the Phrase field (one quoted span per query in v1). The Text field excludes quoted phrases and operator tokens.
Operators and free-text tokens are space-delimited; the parser is intentionally tolerant — unknown prefixes (e.g. `language:Go`) fall through as free text so future operator additions don't break old queries.
func (ParsedQuery) HasContent ¶
func (p ParsedQuery) HasContent() bool
HasContent reports whether the parsed query contains anything searchable (free text, phrase, or any operator).
type RepoFilter ¶
RepoFilter splits the `repo:owner/name` operator value.
type RepoResult ¶
type RepoResult struct {
ID int64
OwnerUsername string
Name string
Description string
Visibility string
StarCount int64
UpdatedAt time.Time
Rank float64
}
RepoResult is one row from SearchRepos.
func SearchRepos ¶
func SearchRepos(ctx context.Context, deps Deps, actor policy.Actor, q ParsedQuery, limit, offset int) ([]RepoResult, int64, error)
SearchRepos runs a repo search visible to actor. limit / offset drive paging.
Ranking: `ts_rank_cd * (1 + ln(1 + star_count)) * recency_decay` where recency_decay is `1 / (1 + days_since_update / 30)`. The whole rank computation lives in SQL so Postgres can short-circuit on the GIN index.
type UserResult ¶
UserResult is one row from SearchUsers.
func SearchUsers ¶
func SearchUsers(ctx context.Context, deps Deps, q ParsedQuery, limit, offset int) ([]UserResult, int64, error)
SearchUsers runs a user search. No visibility predicate — user profiles are public by definition; suspended/deleted accounts are excluded so they don't taint search results (matches the spec pitfall: "search on suspended user content").