Documentation
¶
Overview ¶
Package search implements the SearchService: validate → plan → execute → shape, applying the mode filter (default: service, PD-1) with no mode semantics in any caller (RS-3, SPEC-04).
M1: package skeleton only (this doc.go). Full implementation lands in M10. The SearchService depends only on the read-side SearchIndex interface; it has no dependency on any collector, egress, or P2 package (ADR-0010 Search ≠ Scan; AC-3.1 import-graph test).
Index ¶
Constants ¶
const ( FacetPort = "port" FacetProtocol = "protocol" FacetTechnology = "technology" FacetService = "service" FacetCountry = "country" FacetSource = "source" FacetFreshness = "freshness" )
Facet dimension names. The set is fixed (facet SQL is per-dimension by design); unknown names are rejected with E_QUERY_FACET.
const ( DefaultFacetValues = 10 MaxFacetValues = 50 )
Defaults for facet cardinality (PD-consistent with the search caps).
Variables ¶
var FacetNames = []string{FacetPort, FacetProtocol, FacetTechnology, FacetService, FacetCountry, FacetSource, FacetFreshness}
FacetNames lists the facet dimensions in API order.
Functions ¶
func AsUnavailable ¶
Types ¶
type Facet ¶
type Facet struct {
Name string `json:"facet"`
Values []FacetValue `json:"values"`
Truncated bool `json:"truncated"` // more distinct values exist than returned
}
Facet is one computed dimension.
type FacetValue ¶
FacetValue is one bucket.
type FacetsInfo ¶
type FacetsInfo struct {
GQL string `json:"gql"`
Mode string `json:"mode"`
TookMs int64 `json:"took_ms"`
MaxValues int `json:"max_values"`
}
FacetsInfo describes the executed facet request.
type FacetsQuery ¶
type FacetsQuery struct {
// GQL is the raw filter expression (required; same language as
// search — an empty filter is NOT allowed, facets always narrow).
GQL string
// Mode: "service" (default) or "asset" — mirrors search.
Mode string
// Timeout: 0 = the service default.
Timeout time.Duration
// MaxValues per facet: 0 = DefaultFacetValues; hard cap
// MaxFacetValues.
MaxValues int
// Facets: which dimensions to compute; empty = all of them.
Facets []string
}
FacetsQuery is one faceted-search request.
type FacetsResult ¶
type FacetsResult struct {
Facets []Facet `json:"facets"`
Query FacetsInfo `json:"query"`
}
FacetsResult is the response of one Facets call.
type IndexMeta ¶
type IndexMeta struct {
Assets int64 `json:"assets"`
Services int64 `json:"services"`
Observations int64 `json:"observations"`
FixtureObs int64 `json:"fixture_observations"`
LastObserved *time.Time `json:"last_observed_at,omitempty"`
FixtureOnly bool `json:"fixture_only"`
Empty bool `json:"empty"`
}
IndexMeta is the freshness snapshot attached to results (and served by the API's /api/v1/stats).
type Query ¶
type Query struct {
// GQL is the raw query text (required).
GQL string
// Mode: "service" (default, PD-1) or "asset".
Mode string
// Sort: comma-separated "field:dir" pairs (dir asc|desc, optional).
// Empty = server default (last_seen DESC).
Sort string
// Limit: 0 = default.
Limit int
// Timeout: 0 = default.
Timeout time.Duration
// Cursor: opaque pagination cursor from a prior Result.
Cursor string
// Fields: optional projection (canonical paths or column names).
Fields []string
// WithStats: attach index freshness metadata to the Result.
WithStats bool
}
Query is one search request. Zero fields fall back to the service defaults; Mode/Sort strings are the user-facing spellings.
type QueryError ¶
QueryError is a user-query failure (bad GQL, bad options). The CLI maps it to ExitInvalidInput (2); the API to 400.
func AsQuery ¶
func AsQuery(err error) (*QueryError, bool)
AsQuery extracts a *QueryError (surfaces render Code/Detail/Next).
func (*QueryError) Error ¶
func (e *QueryError) Error() string
type QueryInfo ¶
type QueryInfo struct {
GQL string `json:"gql"`
Mode string `json:"mode"`
Branch string `json:"branch"`
Limit int `json:"limit"`
TimeoutMs int64 `json:"timeout_ms"`
Sort []planner.SortSpec `json:"sort"`
TookMs int64 `json:"took_ms"`
RowCount int `json:"row_count"`
// Fixtures is true when the index the query ran against is
// fixture-labeled (copied from Index when WithStats was set).
Fixtures bool `json:"fixtures"`
}
QueryInfo describes the executed query.
type Result ¶
type Result struct {
Rows []Row
// NextCursor is the cursor for the next page, "" when exhausted.
// Derived from the LAST row (sort value + composite row key), so
// callers must not build it themselves.
NextCursor string
// HasMore is len(rows) == the effective limit (keyset pagination
// heuristic; one extra round-trip on the tail page is avoided).
HasMore bool
// Query describes how the query was compiled (mode, branch, took).
Query QueryInfo
// Index carries freshness metadata when Query.WithStats was set.
Index *IndexMeta
// Explain carries the rendered plan when requested (Query flag via
// the Explain result field on the CLI path; see Plan()).
Explain string
// Notice is a caller-facing notice (e.g. empty index guidance,
// fixture-data label). Never empty when the index needs context.
Notice string
}
Result is one search response.
type Row ¶
type Row struct {
RowID int64 `json:"row_id"`
RowKind string `json:"row_kind"` // service | asset-only
AssetID int64 `json:"asset_id"`
AssetKind string `json:"asset_kind"` // ip | domain | hostname
AssetValue string `json:"asset_value"`
IP *string `json:"ip,omitempty"`
Domain *string `json:"domain,omitempty"`
Hostname *string `json:"hostname,omitempty"`
ASN *int `json:"asn,omitempty"`
ASNOrg *string `json:"asn_org,omitempty"`
GeoCountry *string `json:"geo_country,omitempty"`
GeoRegion *string `json:"geo_region,omitempty"`
GeoCity *string `json:"geo_city,omitempty"`
Port *int `json:"port,omitempty"`
Transport *string `json:"transport,omitempty"`
Protocol *string `json:"protocol,omitempty"`
Name *string `json:"name,omitempty"`
Product *string `json:"product,omitempty"`
Version *string `json:"version,omitempty"`
HTTPStatus *int `json:"http_status,omitempty"`
HTTPTitle *string `json:"http_title,omitempty"`
HTTPServer *string `json:"http_server,omitempty"`
TLSVersion *string `json:"tls_version,omitempty"`
TLSCertSubjectCN *string `json:"tls_cert_subject_cn,omitempty"`
TLSCertIssuerCN *string `json:"tls_cert_issuer_cn,omitempty"`
TLSCertExpiry *time.Time `json:"tls_cert_expiry,omitempty"`
Technologies []string `json:"technologies,omitempty"`
Sources []string `json:"sources,omitempty"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
Stale bool `json:"stale"`
// contains filtered or unexported fields
}
Row is one search_index row (the v0.0.1 service-granular result). JSON tags are the API wire shape (SPEC-07).
type Service ¶
type Service struct {
// Defaults (PD-1 and SPEC-03 §3); a Query's explicit values win.
DefaultMode planner.Mode
DefaultLimit int
DefaultTimeout time.Duration
// contains filtered or unexported fields
}
Service executes GQL searches against the search_index view.
func (*Service) Facets ¶
func (s *Service) Facets(ctx context.Context, q FacetsQuery) (*FacetsResult, error)
Facets computes the requested dimensions over the GQL-filtered row set. Every dimension runs inside ONE transaction with the query timeout applied transaction-locally (same pattern as Search.exec — AC-2.8: the timeout never leaks into pooled session state).
func (*Service) Plan ¶
Plan compiles a query WITHOUT executing it — the `--validate` and `--explain` paths. Returns the compiled plan (or the first lexer/parser/validator/planner error).
type TimeoutError ¶
type TimeoutError struct{ Err error }
TimeoutError is a query that exceeded its statement_timeout. The transaction was rolled back; the timeout applied only inside it. CLI → ExitUnavailable (4); API → 503 with API_E_QUERY_TIMEOUT.
func (*TimeoutError) Error ¶
func (e *TimeoutError) Error() string
func (*TimeoutError) Unwrap ¶
func (e *TimeoutError) Unwrap() error
type UnavailableError ¶
UnavailableError is a search that could not reach the database (the pool is down, the connection broke). CLI → ExitUnavailable (4) with a doctor pointer; API → 503 with API_E_DB_UNAVAILABLE.
func (*UnavailableError) Error ¶
func (e *UnavailableError) Error() string
func (*UnavailableError) Unwrap ¶
func (e *UnavailableError) Unwrap() error