项目文件夹

文件
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

161 行
6.0 KiB
Go

package search
import (
"strings"
"sync/atomic"
"github.com/zzet/gortex/internal/graph"
)
// SymbolSearcherBackend adapts a graph.SymbolSearcher into the
// search.Backend the daemon's search-symbols path consumes.
// Engine.gatherBackendCandidates and the rerank pipeline don't need
// to know whether the backend is BM25 / Bleve / native FTS — they
// see a plain search.Backend and call Search on it.
//
// Production wiring: when the indexer detects that the backing
// graph.Store also implements graph.SymbolSearcher, it constructs
// this adapter as the initial
// search.Backend wrapped by search.NewSwappable. The in-process
// Bleve / BM25 build path is then bypassed entirely.
//
// Add / Remove are no-ops on the adapter because the indexer
// already drives the SymbolSearcher writes directly:
//
// - cold-load: BulkUpsertSymbolFTS at shadow-drain commit (see
// internal/indexer.go IndexCtx defer)
// - incremental: UpsertSymbolFTS alongside the parallel
// idx.search.Add in the per-file path
//
// The adapter therefore only carries the read side. Callers that
// invoke Add / Remove still get the right behaviour because the
// indexer is the only entity that ever creates this adapter, and
// it doesn't rely on Add / Remove updating the FTS — those calls
// happen through the direct SymbolSearcher surface.
type SymbolSearcherBackend struct {
s graph.SymbolSearcher
// count tracks the indexer's incremental Add / Remove deltas
// only — it does NOT report the actual size of the backend
// FTS index (which lives in the disk store and is queryable
// via the SymbolSearcher's own primitives). Used for the
// search.Backend.Count() contract by callers that just want a
// rough magnitude (no caller currently treats this as
// authoritative).
count atomic.Int64
}
// NewSymbolSearcherBackend wraps a SymbolSearcher in the
// search.Backend contract. The caller is responsible for keeping
// the underlying SymbolSearcher alive — Close on this adapter is
// a no-op and never touches the wrapped store.
func NewSymbolSearcherBackend(s graph.SymbolSearcher) *SymbolSearcherBackend {
return &SymbolSearcherBackend{s: s}
}
// SymbolBundle re-exports graph.SymbolBundle so callers (the query
// engine, the rerank seed path) can construct + consume bundles
// without re-importing the graph package next to the search
// package import — symmetric with how SearchResult sits in
// search/.
type SymbolBundle = graph.SymbolBundle
// SearchSymbolBundles is the bundled-search hot path: it forwards
// to the wrapped graph.SymbolBundleSearcher when the underlying
// store implements that capability, returning the matched node +
// score + in/out edges in one engine round-trip. When the store
// only implements SymbolSearcher (no Bundle support), this method
// returns nil — callers MUST check the result and fall back to the
// per-call Search → GetNodesByIDs → GetIn/OutEdgesByNodeIDs path.
//
// Exposed on SymbolSearcherBackend (the production search.Backend
// adapter used in production) so the engine can type-assert through
// the search.Backend chain via SymbolBundleSearcherBackend without
// touching the daemon's wiring.
func (b *SymbolSearcherBackend) SearchSymbolBundles(query string, limit int) []SymbolBundle {
if b == nil || b.s == nil || strings.TrimSpace(query) == "" {
return nil
}
bs, ok := b.s.(graph.SymbolBundleSearcher)
if !ok {
return nil
}
bundles, err := bs.SearchSymbolBundles(query, limit)
if err != nil {
return nil
}
return bundles
}
// SymbolBundleSearcherBackend is the interface the engine type-asserts
// on a search.Backend to detect bundle support. Both
// *SymbolSearcherBackend and *HybridBackend implement this; Swappable
// forwards.
type SymbolBundleSearcherBackend interface {
SearchSymbolBundles(query string, limit int) []SymbolBundle
}
// Search forwards to SymbolSearcher.SearchSymbols and translates
// the per-hit (NodeID, Score) into search.SearchResult so callers
// don't see the graph package at all.
//
// An error from the backend is downgraded to an empty result — the
// daemon's search_symbols path already tolerates an empty primary
// hit set (it falls through to the exact-name / substring tiers in
// query.Engine.gatherBackendCandidates), so returning an error
// surface here would force every caller to grow its own fallback.
func (b *SymbolSearcherBackend) Search(query string, limit int) []SearchResult {
if b == nil || b.s == nil || strings.TrimSpace(query) == "" {
return nil
}
hits, err := b.s.SearchSymbols(query, limit)
if err != nil || len(hits) == 0 {
return nil
}
out := make([]SearchResult, len(hits))
for i, h := range hits {
out[i] = SearchResult{ID: h.NodeID, Score: h.Score}
}
return out
}
// Add is a no-op — the indexer drives UpsertSymbolFTS on the wrapped
// SymbolSearcher directly. count is bumped so the Count() figure
// tracks the deltas-since-construction (best-effort, not
// authoritative — the disk index may be larger from a prior cold
// load).
func (b *SymbolSearcherBackend) Add(id string, _ ...string) {
if b == nil || id == "" {
return
}
b.count.Add(1)
}
// Remove is a no-op for the same reason as Add — the per-call
// removal path (when one lands) routes through SymbolSearcher
// directly, not through the search.Backend contract. count is
// decremented so the Count() figure stays roughly consistent.
func (b *SymbolSearcherBackend) Remove(id string) {
if b == nil || id == "" {
return
}
b.count.Add(-1)
}
// Count returns the running delta-since-construction. Used for
// observability / "is the index populated?" gates — never as a
// load-bearing decision input. The authoritative size lives in
// the disk FTS index, which is queryable via the
// SymbolSearcher's native primitives if needed.
func (b *SymbolSearcherBackend) Count() int {
if b == nil {
return 0
}
return int(b.count.Load())
}
// Close is a no-op. The wrapped SymbolSearcher is owned by the
// graph.Store; closing it from the search adapter would race the
// indexer's own lifecycle.
func (b *SymbolSearcherBackend) Close() {}