Files
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

363 lines
9.3 KiB
Go

package store_sqlite
import (
"github.com/zzet/gortex/internal/graph"
)
// The graph-traversal and subgraph-reader optional capabilities for the
// SQLite backend. Each method mirrors the in-memory *graph.Graph
// reference implementation exactly so both satisfy the same conformance
// suite (internal/graph/storetest). The walks use the same per-node /
// batched edge readers the in-memory store uses (GetOutEdges /
// GetInEdges / GetFileNodes / GetNodesByIDs / GetIn|OutEdgesByNodeIDs),
// which on SQLite hit the (from_id,kind) / (to_id,kind) / file_path
// indexes — no new prepared statements needed.
var (
_ graph.ReachableForwardByKinds = (*Store)(nil)
_ graph.ClassHierarchyTraverser = (*Store)(nil)
_ graph.FrontierExpander = (*Store)(nil)
_ graph.FileEditingContext = (*Store)(nil)
_ graph.FileSubGraphReader = (*Store)(nil)
_ graph.FileSubGraphCountReader = (*Store)(nil)
)
// ReachableForwardByKinds computes the set of node IDs reachable from
// the seed frontier via outgoing edges whose Kind is in kinds, via a
// layer-by-layer forward BFS. Empty seeds returns nil; empty kinds
// returns the seed set unchanged. The returned map keys are the
// reachable IDs (seeds included); every value is true.
func (s *Store) ReachableForwardByKinds(seeds []string, kinds []graph.EdgeKind) map[string]bool {
if len(seeds) == 0 {
return nil
}
covered := make(map[string]bool, len(seeds))
frontier := make([]string, 0, len(seeds))
for _, id := range seeds {
if id == "" || covered[id] {
continue
}
covered[id] = true
frontier = append(frontier, id)
}
if len(kinds) == 0 {
return covered
}
allowed := make(map[graph.EdgeKind]struct{}, len(kinds))
for _, k := range kinds {
allowed[k] = struct{}{}
}
for len(frontier) > 0 {
next := frontier[:0:0]
for _, id := range frontier {
for _, e := range s.GetOutEdges(id) {
if e == nil {
continue
}
if _, ok := allowed[e.Kind]; !ok {
continue
}
if !covered[e.To] {
covered[e.To] = true
next = append(next, e.To)
}
}
}
frontier = next
}
return covered
}
// ClassHierarchyTraverse walks the inheritance subgraph rooted at
// seedID, following only edges whose Kind is in kinds, up to depth hops.
// direction "up" follows outgoing edges; "down" follows incoming. Empty
// kinds, depth <= 0, an unknown direction, or an unknown seed return
// nil. Each returned row carries the full Path (node IDs from the seed,
// exclusive) and per-hop EdgeKinds for one terminal node.
func (s *Store) ClassHierarchyTraverse(
seedID string,
direction string,
kinds []graph.EdgeKind,
depth int,
) []graph.ClassHierarchyRow {
if seedID == "" || depth <= 0 || len(kinds) == 0 {
return nil
}
kset := make(map[graph.EdgeKind]struct{}, len(kinds))
for _, k := range kinds {
if k == "" {
continue
}
kset[k] = struct{}{}
}
if len(kset) == 0 {
return nil
}
if s.GetNode(seedID) == nil {
return nil
}
walkUp := direction == "up"
walkDown := direction == "down"
if !walkUp && !walkDown {
return nil
}
type travQueued struct {
id string
path []string
edgeKinds []graph.EdgeKind
hops int
}
visited := map[string]struct{}{seedID: {}}
queue := []travQueued{{id: seedID, path: nil, edgeKinds: nil, hops: 0}}
var out []graph.ClassHierarchyRow
for len(queue) > 0 {
cur := queue[0]
queue = queue[1:]
if cur.hops >= depth {
continue
}
var edges []*graph.Edge
if walkUp {
edges = s.GetOutEdges(cur.id)
} else {
edges = s.GetInEdges(cur.id)
}
for _, e := range edges {
if e == nil {
continue
}
if _, ok := kset[e.Kind]; !ok {
continue
}
var nb string
if walkUp {
nb = e.To
} else {
nb = e.From
}
if nb == "" {
continue
}
if _, ok := visited[nb]; ok {
continue
}
visited[nb] = struct{}{}
newPath := append([]string(nil), cur.path...)
newPath = append(newPath, nb)
newKinds := append([]graph.EdgeKind(nil), cur.edgeKinds...)
newKinds = append(newKinds, e.Kind)
out = append(out, graph.ClassHierarchyRow{
Path: newPath,
EdgeKinds: newKinds,
})
queue = append(queue, travQueued{id: nb, path: newPath, edgeKinds: newKinds, hops: cur.hops + 1})
}
}
return out
}
// ExpandFrontier returns, for the given source IDs, their adjacent edges
// of the requested kinds plus the neighbour node at each edge's far end.
// forward=true follows outgoing edges (neighbour = edge target);
// forward=false follows incoming (neighbour = edge source). Empty ids or
// empty kinds return nil; limit > 0 caps the total number of hops.
func (s *Store) ExpandFrontier(ids []string, forward bool, kinds []graph.EdgeKind, limit int) []graph.FrontierHop {
if len(ids) == 0 || len(kinds) == 0 {
return nil
}
kset := make(map[graph.EdgeKind]struct{}, len(kinds))
for _, k := range kinds {
kset[k] = struct{}{}
}
var out []graph.FrontierHop
for _, id := range ids {
var edges []*graph.Edge
if forward {
edges = s.GetOutEdges(id)
} else {
edges = s.GetInEdges(id)
}
for _, e := range edges {
if e == nil {
continue
}
if _, ok := kset[e.Kind]; !ok {
continue
}
var nbID string
if forward {
nbID = e.To
} else {
nbID = e.From
}
nb := s.GetNode(nbID)
if nb == nil {
continue
}
out = append(out, graph.FrontierHop{Edge: e, Neighbor: nb})
if limit > 0 && len(out) >= limit {
return out
}
}
}
return out
}
// FileEditingContext returns the get_editing_context payload for
// filePath: the file node, the symbols defined in it, the file node's
// import out-edges, and the 1-hop callers / callees (via EdgeCalls) of
// the defined call-target symbols, filtered to symbols outside the file.
// kinds is the set of node kinds treated as call targets (function +
// method). Empty path or a file with no nodes returns nil.
func (s *Store) FileEditingContext(filePath string, kinds []graph.NodeKind) *graph.FileEditingContextResult {
if filePath == "" {
return nil
}
nodes := s.GetFileNodes(filePath)
if len(nodes) == 0 {
return nil
}
kset := make(map[graph.NodeKind]struct{}, len(kinds))
for _, k := range kinds {
if k == "" {
continue
}
kset[k] = struct{}{}
}
res := &graph.FileEditingContextResult{}
var fileNodeID string
var defNodeIDs []string
for _, n := range nodes {
if n == nil {
continue
}
if n.Kind == graph.KindFile {
res.FileNode = n
fileNodeID = n.ID
continue
}
res.Defines = append(res.Defines, n)
if _, ok := kset[n.Kind]; ok {
defNodeIDs = append(defNodeIDs, n.ID)
}
}
if fileNodeID != "" {
for _, e := range s.GetOutEdges(fileNodeID) {
if e == nil {
continue
}
if e.Kind == graph.EdgeImports {
res.Imports = append(res.Imports, e)
}
}
}
if len(defNodeIDs) == 0 {
return res
}
inEdges := s.GetInEdgesByNodeIDs(defNodeIDs)
outEdges := s.GetOutEdgesByNodeIDs(defNodeIDs)
callerIDSet := make(map[string]struct{})
calleeIDSet := make(map[string]struct{})
for _, id := range defNodeIDs {
for _, e := range inEdges[id] {
if e == nil || e.Kind != graph.EdgeCalls {
continue
}
if e.From == "" {
continue
}
callerIDSet[e.From] = struct{}{}
}
for _, e := range outEdges[id] {
if e == nil || e.Kind != graph.EdgeCalls {
continue
}
if e.To == "" {
continue
}
calleeIDSet[e.To] = struct{}{}
}
}
callerIDs := make([]string, 0, len(callerIDSet))
for id := range callerIDSet {
callerIDs = append(callerIDs, id)
}
calleeIDs := make([]string, 0, len(calleeIDSet))
for id := range calleeIDSet {
calleeIDs = append(calleeIDs, id)
}
callerNodes := s.GetNodesByIDs(callerIDs)
calleeNodes := s.GetNodesByIDs(calleeIDs)
for _, id := range callerIDs {
n := callerNodes[id]
if n == nil || n.FilePath == filePath {
continue
}
res.CalledBy = append(res.CalledBy, n)
}
for _, id := range calleeIDs {
n := calleeNodes[id]
if n == nil || n.FilePath == filePath {
continue
}
res.Calls = append(res.Calls, n)
}
return res
}
// GetFileSubGraph returns every node anchored to filePath plus every
// edge adjacent to one of those nodes, deduplicated by (from, to, kind).
// A missing / empty file returns (nil, nil).
func (s *Store) GetFileSubGraph(filePath string) ([]*graph.Node, []*graph.Edge) {
if filePath == "" {
return nil, nil
}
nodes := s.GetFileNodes(filePath)
if len(nodes) == 0 {
return nil, nil
}
ids := make([]string, 0, len(nodes))
for _, n := range nodes {
if n != nil && n.ID != "" {
ids = append(ids, n.ID)
}
}
outByID := s.GetOutEdgesByNodeIDs(ids)
inByID := s.GetInEdgesByNodeIDs(ids)
type travEdgeKey struct {
from string
to string
kind graph.EdgeKind
}
seen := make(map[travEdgeKey]struct{}, 2*len(ids))
edges := make([]*graph.Edge, 0, 2*len(ids))
add := func(e *graph.Edge) {
if e == nil {
return
}
k := travEdgeKey{from: e.From, to: e.To, kind: e.Kind}
if _, ok := seen[k]; ok {
return
}
seen[k] = struct{}{}
edges = append(edges, e)
}
for _, id := range ids {
for _, e := range outByID[id] {
add(e)
}
for _, e := range inByID[id] {
add(e)
}
}
return nodes, edges
}
// GetFileSubGraphCounts is the count-only sibling of GetFileSubGraph:
// it returns the file's nodes plus the number of distinct adjacent
// edges, without materialising the edge slice for the caller.
func (s *Store) GetFileSubGraphCounts(filePath string) ([]*graph.Node, int) {
nodes, edges := s.GetFileSubGraph(filePath)
return nodes, len(edges)
}