Documentation
¶
Overview ¶
Package query implements the codeiq Go port's query-side services. It wraps internal/graph.Store with task-level helpers and renders the JSON- ready shapes the Java side's QueryService / StatsService / TopologyService expose. These services are read-only; mutation paths live in analyzer/.
The package centres on three services:
- StatsService — pure functions over (nodes, edges) slices. Used when the enrich pipeline has the full graph in heap; the serve side uses graph.Store aggregations instead. Mirrors StatsService.java.
- Service — high-level read service backed by a graph.Store. Mirrors QueryService.java (consumers / producers / callers / cycles / dead).
- Topology — service-topology analyses. Mirrors TopologyService.java.
Index ¶
- type OrderedMap
- type Service
- func (s *Service) FindCallers(id string) ([]*model.CodeNode, error)
- func (s *Service) FindConsumers(id string) ([]*model.CodeNode, error)
- func (s *Service) FindCycles(limit int) ([][]string, error)
- func (s *Service) FindDeadCode(kinds []string, limit int) ([]*model.CodeNode, error)
- func (s *Service) FindDependencies(id string) ([]*model.CodeNode, error)
- func (s *Service) FindDependents(id string) ([]*model.CodeNode, error)
- func (s *Service) FindProducers(id string) ([]*model.CodeNode, error)
- func (s *Service) FindShortestPath(source, target string) ([]string, error)
- type StatsService
- type StoreStatsService
- type Topology
- func (t *Topology) BlastRadius(nodeID string, depth int) (*OrderedMap, error)
- func (t *Topology) FindBottlenecks() ([]map[string]any, error)
- func (t *Topology) FindCircular() ([][]string, error)
- func (t *Topology) FindDeadServices() ([]map[string]any, error)
- func (t *Topology) FindPath(source, target string) ([]map[string]any, error)
- func (t *Topology) GetTopology() (*OrderedMap, error)
- func (t *Topology) ServiceDependencies(serviceName string) (*OrderedMap, error)
- func (t *Topology) ServiceDependents(serviceName string) (*OrderedMap, error)
- func (t *Topology) ServiceDetail(serviceName string) (*OrderedMap, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type OrderedMap ¶
OrderedMap preserves insertion order — equivalent to Java's LinkedHashMap. Stats JSON output relies on a deterministic top-level key order matching the Java side for parity diffing.
func (*OrderedMap) MarshalJSON ¶
func (m *OrderedMap) MarshalJSON() ([]byte, error)
MarshalJSON emits keys in insertion order — the whole point of OrderedMap. Empty/zero maps emit `{}`. Nested OrderedMaps recurse correctly through json.Encoder's reflective path because MarshalJSON is declared on the pointer receiver and the package always passes *OrderedMap values around.
func (*OrderedMap) Put ¶
func (m *OrderedMap) Put(k string, v any)
Put records key in insertion order; an overwrite keeps the original position so that re-assignment is non-disruptive.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the high-level read service wrapping a graph.Store. Mirrors QueryService.java — consumers / producers / callers / dependencies / dependents / shortest-path / cycles / dead-code. The Java side uses Neo4j's single RELATES_TO edge wrapper with a `kind` property; on Kuzu we have one rel table per EdgeKind, so the queries below filter by `LABEL(r)` rather than `r.kind`.
Kuzu 0.7.1 feature gaps relevant here:
- Shortest path uses Kuzu's `[* SHORTEST n..m]` syntax, NOT Neo4j's `shortestPath((a)-[*..20]-(b))` function.
- Cycles use the recursive pattern `(n)-[*2..N]->(n)`; Kuzu requires an explicit upper bound (default 30 if omitted).
- There is no `TYPE(r)` — use `LABEL(r)` to get the rel table name.
func NewService ¶
NewService constructs a Service bound to the given graph.Store.
func (*Service) FindCallers ¶
FindCallers returns nodes m where m -[calls]-> target. Mirrors QueryService.findCallers.
func (*Service) FindConsumers ¶
FindConsumers returns nodes m where m -[consumes|listens]-> target. Mirrors QueryService.findConsumers + GraphStore.findConsumers on the Java side; the runtime-edge set is the consumer-direction subset.
func (*Service) FindCycles ¶
FindCycles returns up to `limit` cycles in the graph. Each cycle is a node-id slice where the first and last elements are equal. Mirrors QueryService.findCycles + GraphStore.findCycles.
Implementation note: Kuzu's recursive pattern requires an upper bound (default 30 if omitted). We cap at 10 to match the Java side's hop budget — same trade between completeness and query time.
func (*Service) FindDeadCode ¶
FindDeadCode returns nodes of the given kinds that have no incoming semantic edge and are not on the entry-point list. Mirrors QueryService.findDeadCode + GraphStore.findNodesWithoutIncomingSemantic.
Kuzu 0.7 cap: `NOT EXISTS { MATCH ... }` works (verified against docs). The semantic-edge filter is an `LABEL(r) IN [...]` predicate, not a rel-pattern alternation, so the existence check stays a single MATCH.
func (*Service) FindDependencies ¶
FindDependencies returns nodes m where source -[depends_on]-> m. Mirrors QueryService.findDependencies.
func (*Service) FindDependents ¶
FindDependents returns nodes m where m -[depends_on]-> source. Mirrors QueryService.findDependents.
func (*Service) FindProducers ¶
FindProducers returns nodes m where m -[produces|publishes]-> target. Mirrors QueryService.findProducers.
func (*Service) FindShortestPath ¶
FindShortestPath returns a list of node IDs forming the shortest directed path from source to target, inclusive of both endpoints. Returns an empty slice when no path exists. Mirrors QueryService.shortestPath on the Java side (which uses Neo4j shortestPath() — see Kuzu syntax note above).
Kuzu 0.7 requires:
- explicit upper bound on the recursive pattern
- rel pattern with named rel variable so nodes(p) can be extracted
We use `[* SHORTEST 1..20]` to match the Java cap (`*..20`).
type StatsService ¶
type StatsService struct{}
StatsService computes rich categorized statistics from in-memory node / edge slices. Stateless — the zero value is usable.
func (*StatsService) ComputeCategory ¶
func (s *StatsService) ComputeCategory(nodes []*model.CodeNode, edges []*model.CodeEdge, category string) *OrderedMap
ComputeCategory returns just one category. Names are matched case-insensitively. Returns nil for unknown categories — matches Java behaviour rather than returning an error envelope (the controller layer surfaces that as "Unknown category").
func (*StatsService) ComputeStats ¶
func (s *StatsService) ComputeStats(nodes []*model.CodeNode, edges []*model.CodeEdge) *OrderedMap
ComputeStats returns the seven-category breakdown: graph, languages, frameworks, infra, connections, auth, architecture. Order matches Java StatsService.computeStats line-for-line for parity.
type StoreStatsService ¶
type StoreStatsService struct {
// contains filtered or unexported fields
}
StoreStatsService is a thin store-backed wrapper around StatsService. It lazy-loads the full node + edge lists on the first call and reuses them for subsequent ComputeStats / ComputeCategory invocations. Use this when the caller has a graph.Store handle (e.g. the CLI / MCP) rather than pre-materialised slices.
The wrapper is a bridge for the read path while the targeted-Cypher rewrite is in flight — same `getCachedData()` snapshot pattern the Java side uses for the same reason (see the CLAUDE.md gotcha entry).
func NewStatsServiceFromStore ¶
func NewStatsServiceFromStore(loader func() ([]*model.CodeNode, []*model.CodeEdge, error)) *StoreStatsService
NewStatsServiceFromStore returns a StoreStatsService bound to the loader callback. The CLI passes `func() (...) { return store.LoadAllNodes(), ... }`. Decoupling from graph.Store avoids a query→graph import cycle (graph already imports model, but tests want to feed in arbitrary slices).
func (*StoreStatsService) ComputeCategory ¶
func (s *StoreStatsService) ComputeCategory(category string) *OrderedMap
ComputeCategory lazy-loads and forwards to StatsService.ComputeCategory. Returns nil for unknown categories (matches the in-memory API).
func (*StoreStatsService) ComputeStats ¶
func (s *StoreStatsService) ComputeStats() *OrderedMap
ComputeStats lazy-loads and forwards to StatsService.ComputeStats. Returns an empty *OrderedMap (not nil) on loader failure so the JSON output is always well-formed; callers that care about the underlying error must use LoadErr() after the call.
func (*StoreStatsService) LoadErr ¶
func (s *StoreStatsService) LoadErr() error
LoadErr returns the loader error, if any, captured during the first call to ComputeStats / ComputeCategory.
type Topology ¶
type Topology struct {
// contains filtered or unexported fields
}
Topology is the service-topology read service backed by a graph.Store. Mirrors TopologyService.java — but where the Java side ingests the full node + edge lists and walks them in heap, the Go side uses targeted Cypher queries against the structural CONTAINS edges that ServiceDetector emits from SERVICE nodes to their child files. This keeps peak memory flat regardless of graph size.
Conventions:
- SERVICE nodes have kind = "service" and label = service name.
- Each child node carries `service` property AND has an incoming CONTAINS edge from its SERVICE node — we pivot through CONTAINS in Cypher rather than parsing the JSON props column.
- Runtime edge kinds (the "service-to-service" connections) are the same list as TopologyService.RUNTIME_EDGES in Java.
func NewTopology ¶
NewTopology constructs a Topology read service.
func (*Topology) BlastRadius ¶
func (t *Topology) BlastRadius(nodeID string, depth int) (*OrderedMap, error)
BlastRadius returns nodes reachable from the start node via runtime edges, up to `depth` hops. Mirrors TopologyService.blastRadius. The affected node list excludes the source. `affected_services` is the distinct set of service names those nodes belong to.
func (*Topology) FindBottlenecks ¶
FindBottlenecks returns service-level connection-count rows (in / out / total). Mirrors TopologyService.findBottlenecks — sorted by total desc.
func (*Topology) FindCircular ¶
FindCircular returns service-level cycles. DFS over the cross-service adjacency; each cycle is normalized to start at its lexicographically smallest service. Mirrors TopologyService.findCircularDeps.
func (*Topology) FindDeadServices ¶
FindDeadServices returns SERVICE rows with no incoming runtime edges. Mirrors TopologyService.findDeadServices.
func (*Topology) FindPath ¶
FindPath returns a list of hops {from, to, type} forming the shortest path between two services. Returns nil when no path exists. Mirrors TopologyService.findPath. BFS over the cross-service adjacency.
func (*Topology) GetTopology ¶
func (t *Topology) GetTopology() (*OrderedMap, error)
GetTopology returns an OrderedMap with services / connections / service_count / connection_count, mirroring TopologyService.getTopology on the Java side. Service summaries carry build_tool / endpoint_count / entity_count / connections_in / connections_out.
func (*Topology) ServiceDependencies ¶
func (t *Topology) ServiceDependencies(serviceName string) (*OrderedMap, error)
ServiceDependencies returns the cross-service runtime connections that originate from serviceName, plus the distinct set of services it depends on. Mirrors TopologyService.serviceDependencies on the Java side — same key shape (service / depends_on / connections / count) so the JSON envelope is structurally identical.
func (*Topology) ServiceDependents ¶
func (t *Topology) ServiceDependents(serviceName string) (*OrderedMap, error)
ServiceDependents returns the cross-service runtime connections that terminate at serviceName, plus the distinct set of services that depend on it. Mirrors TopologyService.serviceDependents.
func (*Topology) ServiceDetail ¶
func (t *Topology) ServiceDetail(serviceName string) (*OrderedMap, error)
ServiceDetail returns endpoints / entities / guards / databases / queues for a specific service. Mirrors TopologyService.serviceDetail.