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