query

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 14, 2026 License: MIT Imports: 7 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type OrderedMap

type OrderedMap struct {
	Keys   []string
	Values map[string]any
}

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

func NewService(store *graph.Store) *Service

NewService constructs a Service bound to the given graph.Store.

func (*Service) FindCallers

func (s *Service) FindCallers(id string) ([]*model.CodeNode, error)

FindCallers returns nodes m where m -[calls]-> target. Mirrors QueryService.findCallers.

func (*Service) FindConsumers

func (s *Service) FindConsumers(id string) ([]*model.CodeNode, error)

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

func (s *Service) FindCycles(limit int) ([][]string, error)

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

func (s *Service) FindDeadCode(kinds []string, limit int) ([]*model.CodeNode, error)

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

func (s *Service) FindDependencies(id string) ([]*model.CodeNode, error)

FindDependencies returns nodes m where source -[depends_on]-> m. Mirrors QueryService.findDependencies.

func (*Service) FindDependents

func (s *Service) FindDependents(id string) ([]*model.CodeNode, error)

FindDependents returns nodes m where m -[depends_on]-> source. Mirrors QueryService.findDependents.

func (*Service) FindProducers

func (s *Service) FindProducers(id string) ([]*model.CodeNode, error)

FindProducers returns nodes m where m -[produces|publishes]-> target. Mirrors QueryService.findProducers.

func (*Service) FindShortestPath

func (s *Service) FindShortestPath(source, target string) ([]string, error)

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

func NewTopology(store *graph.Store) *Topology

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

func (t *Topology) FindBottlenecks() ([]map[string]any, error)

FindBottlenecks returns service-level connection-count rows (in / out / total). Mirrors TopologyService.findBottlenecks — sorted by total desc.

func (*Topology) FindCircular

func (t *Topology) FindCircular() ([][]string, error)

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

func (t *Topology) FindDeadServices() ([]map[string]any, error)

FindDeadServices returns SERVICE rows with no incoming runtime edges. Mirrors TopologyService.findDeadServices.

func (*Topology) FindPath

func (t *Topology) FindPath(source, target string) ([]map[string]any, error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL