-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrefresh.go
More file actions
115 lines (108 loc) · 4.39 KB
/
refresh.go
File metadata and controls
115 lines (108 loc) · 4.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package graph
import (
"fmt"
"github.com/randomcodespace/codeiq/internal/model"
)
// RemoveFile deletes every CodeNode whose file_path matches path along with
// every incident relationship across all rel tables. Idempotent — calling
// with a path that has no matching nodes is a no-op that returns nil.
//
// Implementation note: we iterate per rel table because Kuzu (0.11.3) does
// not yet support a heterogeneous "match any rel" DELETE across rel tables.
// The DETACH DELETE on CodeNode then handles whatever remains.
func (s *Store) RemoveFile(path string) error {
// First, drop every incident rel by walking each declared rel table.
// DETACH DELETE on CodeNode would handle this, but being explicit per
// table keeps the delete plan simple and predictable on Kuzu 0.11.3.
for _, kind := range model.AllEdgeKinds() {
q := fmt.Sprintf(
`MATCH (n:CodeNode)-[r:%s]->(m:CodeNode)
WHERE n.file_path = $p OR m.file_path = $p
DELETE r`,
relTableName(kind))
if _, err := s.Cypher(q, map[string]any{"p": path}); err != nil {
return fmt.Errorf("graph: remove edges for %s: %w", path, err)
}
}
// Now drop the nodes themselves.
if _, err := s.Cypher(
`MATCH (n:CodeNode) WHERE n.file_path = $p DELETE n`,
map[string]any{"p": path},
); err != nil {
return fmt.Errorf("graph: remove nodes for %s: %w", path, err)
}
return nil
}
// InsertFile bulk-loads nodes + edges for a single file. Equivalent to
// BulkLoadNodes + BulkLoadEdges; the path parameter is for API symmetry
// (file_path is on each node).
func (s *Store) InsertFile(path string, nodes []*model.CodeNode, edges []*model.CodeEdge) error {
if len(nodes) == 0 && len(edges) == 0 {
return nil
}
if err := s.BulkLoadNodes(nodes); err != nil {
return fmt.Errorf("graph: insert file %s nodes: %w", path, err)
}
if err := s.BulkLoadEdges(edges); err != nil {
return fmt.Errorf("graph: insert file %s edges: %w", path, err)
}
return nil
}
// Reset clears every CodeNode (and its incident edges via DETACH DELETE)
// plus every GraphMeta row, while preserving the schema. Used before a
// full re-enrich on a graph that already holds prior state, so the
// subsequent BulkLoad doesn't collide with stale primary keys.
//
// Calling Reset on a fresh (already empty) graph is a no-op.
func (s *Store) Reset() error {
if _, err := s.Cypher(`MATCH (n:CodeNode) DETACH DELETE n`); err != nil {
return fmt.Errorf("graph: reset CodeNode: %w", err)
}
if _, err := s.Cypher(`MATCH (m:GraphMeta) DELETE m`); err != nil {
return fmt.Errorf("graph: reset GraphMeta: %w", err)
}
return nil
}
// WipeLinkerEdges deletes every relationship whose source property is in
// the given sources set, across every declared rel table. Used by
// incremental enrich to clear previous linker emissions before re-running
// linker passes.
//
// Iterates per rel-type because Kuzu (0.11.3) doesn't support a
// heterogeneous "match any rel" DELETE across rel tables.
//
// Linker-emitted nodes (e.g., MODULE nodes from ModuleContainmentLinker)
// are also wiped when their source tag is in the set — caller can pass
// only edge-relevant tags if they want to preserve nodes.
func (s *Store) WipeLinkerEdges(sources []string) error {
if len(sources) == 0 {
return nil
}
for _, kind := range model.AllEdgeKinds() {
q := fmt.Sprintf(
`MATCH ()-[r:%s]->() WHERE r.source IN $sources DELETE r`,
relTableName(kind))
if _, err := s.Cypher(q, map[string]any{"sources": sources}); err != nil {
return fmt.Errorf("graph: wipe %s edges: %w", relTableName(kind), err)
}
}
// Linker-emitted nodes (module nodes) — drop any CodeNode whose source
// tag is in the set. These re-emit on the next linker pass.
if _, err := s.Cypher(
`MATCH (n:CodeNode) WHERE n.source IN $sources DELETE n`,
map[string]any{"sources": sources}); err != nil {
return fmt.Errorf("graph: wipe linker nodes: %w", err)
}
return nil
}
// ReplaceFile is the MODIFIED-file path: RemoveFile followed by InsertFile.
// There is a brief window between the two calls where the file's nodes are
// absent from the graph; concurrent readers see either pre-state or
// post-state but may briefly observe the file as missing. The window is
// fine for incremental enrich since enrich is the single writer.
func (s *Store) ReplaceFile(path string, nodes []*model.CodeNode, edges []*model.CodeEdge) error {
if err := s.RemoveFile(path); err != nil {
return err
}
return s.InsertFile(path, nodes, edges)
}