graph-result.go raw
1 package neo4j
2
3 import (
4 "sort"
5 )
6
7 // GraphResult contains depth-organized traversal results for graph queries.
8 // It tracks pubkeys and events discovered at each depth level, ensuring
9 // each entity appears only at the depth where it was first discovered.
10 //
11 // This is the Neo4j implementation that mirrors the Badger implementation
12 // in pkg/database/graph-result.go, implementing the graph.GraphResultI interface.
13 type GraphResult struct {
14 // PubkeysByDepth maps depth -> pubkeys first discovered at that depth.
15 // Each pubkey appears ONLY in the array for the depth where it was first seen.
16 // Depth 1 = direct connections, Depth 2 = connections of connections, etc.
17 PubkeysByDepth map[int][]string
18
19 // EventsByDepth maps depth -> event IDs discovered at that depth.
20 // Used for thread traversal queries.
21 EventsByDepth map[int][]string
22
23 // FirstSeenPubkey tracks which depth each pubkey was first discovered.
24 // Key is pubkey hex, value is the depth (1-indexed).
25 FirstSeenPubkey map[string]int
26
27 // FirstSeenEvent tracks which depth each event was first discovered.
28 // Key is event ID hex, value is the depth (1-indexed).
29 FirstSeenEvent map[string]int
30
31 // TotalPubkeys is the count of unique pubkeys discovered across all depths.
32 TotalPubkeys int
33
34 // TotalEvents is the count of unique events discovered across all depths.
35 TotalEvents int
36
37 // InboundRefs tracks inbound references (events that reference discovered items).
38 // Structure: kind -> target_id -> []referencing_event_ids
39 InboundRefs map[uint16]map[string][]string
40
41 // OutboundRefs tracks outbound references (events referenced by discovered items).
42 // Structure: kind -> source_id -> []referenced_event_ids
43 OutboundRefs map[uint16]map[string][]string
44 }
45
46 // NewGraphResult creates a new initialized GraphResult.
47 func NewGraphResult() *GraphResult {
48 return &GraphResult{
49 PubkeysByDepth: make(map[int][]string),
50 EventsByDepth: make(map[int][]string),
51 FirstSeenPubkey: make(map[string]int),
52 FirstSeenEvent: make(map[string]int),
53 InboundRefs: make(map[uint16]map[string][]string),
54 OutboundRefs: make(map[uint16]map[string][]string),
55 }
56 }
57
58 // AddPubkeyAtDepth adds a pubkey to the result at the specified depth if not already seen.
59 // Returns true if the pubkey was added (first time seen), false if already exists.
60 func (r *GraphResult) AddPubkeyAtDepth(pubkeyHex string, depth int) bool {
61 if _, exists := r.FirstSeenPubkey[pubkeyHex]; exists {
62 return false
63 }
64
65 r.FirstSeenPubkey[pubkeyHex] = depth
66 r.PubkeysByDepth[depth] = append(r.PubkeysByDepth[depth], pubkeyHex)
67 r.TotalPubkeys++
68 return true
69 }
70
71 // AddEventAtDepth adds an event ID to the result at the specified depth if not already seen.
72 // Returns true if the event was added (first time seen), false if already exists.
73 func (r *GraphResult) AddEventAtDepth(eventIDHex string, depth int) bool {
74 if _, exists := r.FirstSeenEvent[eventIDHex]; exists {
75 return false
76 }
77
78 r.FirstSeenEvent[eventIDHex] = depth
79 r.EventsByDepth[depth] = append(r.EventsByDepth[depth], eventIDHex)
80 r.TotalEvents++
81 return true
82 }
83
84 // HasPubkey returns true if the pubkey has been discovered at any depth.
85 func (r *GraphResult) HasPubkey(pubkeyHex string) bool {
86 _, exists := r.FirstSeenPubkey[pubkeyHex]
87 return exists
88 }
89
90 // HasEvent returns true if the event has been discovered at any depth.
91 func (r *GraphResult) HasEvent(eventIDHex string) bool {
92 _, exists := r.FirstSeenEvent[eventIDHex]
93 return exists
94 }
95
96 // ToDepthArrays converts the result to the response format: array of arrays.
97 // Index 0 = depth 1 pubkeys, Index 1 = depth 2 pubkeys, etc.
98 // Empty arrays are included for depths with no pubkeys to maintain index alignment.
99 func (r *GraphResult) ToDepthArrays() [][]string {
100 if len(r.PubkeysByDepth) == 0 {
101 return [][]string{}
102 }
103
104 // Find the maximum depth
105 maxDepth := 0
106 for d := range r.PubkeysByDepth {
107 if d > maxDepth {
108 maxDepth = d
109 }
110 }
111
112 // Create result array with entries for each depth
113 result := make([][]string, maxDepth)
114 for i := 0; i < maxDepth; i++ {
115 depth := i + 1 // depths are 1-indexed
116 if pubkeys, exists := r.PubkeysByDepth[depth]; exists {
117 result[i] = pubkeys
118 } else {
119 result[i] = []string{} // Empty array for depths with no pubkeys
120 }
121 }
122 return result
123 }
124
125 // ToEventDepthArrays converts event results to the response format: array of arrays.
126 // Index 0 = depth 1 events, Index 1 = depth 2 events, etc.
127 func (r *GraphResult) ToEventDepthArrays() [][]string {
128 if len(r.EventsByDepth) == 0 {
129 return [][]string{}
130 }
131
132 maxDepth := 0
133 for d := range r.EventsByDepth {
134 if d > maxDepth {
135 maxDepth = d
136 }
137 }
138
139 result := make([][]string, maxDepth)
140 for i := 0; i < maxDepth; i++ {
141 depth := i + 1
142 if events, exists := r.EventsByDepth[depth]; exists {
143 result[i] = events
144 } else {
145 result[i] = []string{}
146 }
147 }
148 return result
149 }
150
151 // GetAllPubkeys returns all pubkeys discovered across all depths.
152 func (r *GraphResult) GetAllPubkeys() []string {
153 all := make([]string, 0, r.TotalPubkeys)
154 for _, pubkeys := range r.PubkeysByDepth {
155 all = append(all, pubkeys...)
156 }
157 return all
158 }
159
160 // GetAllEvents returns all event IDs discovered across all depths.
161 func (r *GraphResult) GetAllEvents() []string {
162 all := make([]string, 0, r.TotalEvents)
163 for _, events := range r.EventsByDepth {
164 all = append(all, events...)
165 }
166 return all
167 }
168
169 // GetPubkeysByDepth returns the PubkeysByDepth map for external access.
170 func (r *GraphResult) GetPubkeysByDepth() map[int][]string {
171 return r.PubkeysByDepth
172 }
173
174 // GetEventsByDepth returns the EventsByDepth map for external access.
175 func (r *GraphResult) GetEventsByDepth() map[int][]string {
176 return r.EventsByDepth
177 }
178
179 // GetTotalPubkeys returns the total pubkey count for external access.
180 func (r *GraphResult) GetTotalPubkeys() int {
181 return r.TotalPubkeys
182 }
183
184 // GetTotalEvents returns the total event count for external access.
185 func (r *GraphResult) GetTotalEvents() int {
186 return r.TotalEvents
187 }
188
189 // GetDepthsSorted returns all depths that have pubkeys, sorted ascending.
190 func (r *GraphResult) GetDepthsSorted() []int {
191 depths := make([]int, 0, len(r.PubkeysByDepth))
192 for d := range r.PubkeysByDepth {
193 depths = append(depths, d)
194 }
195 sort.Ints(depths)
196 return depths
197 }
198
199 // GetEventDepthsSorted returns all depths that have events, sorted ascending.
200 func (r *GraphResult) GetEventDepthsSorted() []int {
201 depths := make([]int, 0, len(r.EventsByDepth))
202 for d := range r.EventsByDepth {
203 depths = append(depths, d)
204 }
205 sort.Ints(depths)
206 return depths
207 }
208
209 // GetInboundRefs returns the InboundRefs map for external access.
210 func (r *GraphResult) GetInboundRefs() map[uint16]map[string][]string {
211 return r.InboundRefs
212 }
213
214 // GetOutboundRefs returns the OutboundRefs map for external access.
215 func (r *GraphResult) GetOutboundRefs() map[uint16]map[string][]string {
216 return r.OutboundRefs
217 }
218
219 // AddInboundRef records an inbound reference from a referencing event to a target.
220 func (r *GraphResult) AddInboundRef(kind uint16, targetIDHex string, referencingEventIDHex string) {
221 if r.InboundRefs[kind] == nil {
222 r.InboundRefs[kind] = make(map[string][]string)
223 }
224 r.InboundRefs[kind][targetIDHex] = append(r.InboundRefs[kind][targetIDHex], referencingEventIDHex)
225 }
226
227 // AddOutboundRef records an outbound reference from a source event to a referenced event.
228 func (r *GraphResult) AddOutboundRef(kind uint16, sourceIDHex string, referencedEventIDHex string) {
229 if r.OutboundRefs[kind] == nil {
230 r.OutboundRefs[kind] = make(map[string][]string)
231 }
232 r.OutboundRefs[kind][sourceIDHex] = append(r.OutboundRefs[kind][sourceIDHex], referencedEventIDHex)
233 }
234
235 // GetPubkeysAtDepth returns pubkeys at a specific depth, or empty slice if none.
236 func (r *GraphResult) GetPubkeysAtDepth(depth int) []string {
237 if pubkeys, exists := r.PubkeysByDepth[depth]; exists {
238 return pubkeys
239 }
240 return []string{}
241 }
242
243 // GetEventsAtDepth returns events at a specific depth, or empty slice if none.
244 func (r *GraphResult) GetEventsAtDepth(depth int) []string {
245 if events, exists := r.EventsByDepth[depth]; exists {
246 return events
247 }
248 return []string{}
249 }
250