Some checks failed
Go / build-and-release (push) Has been cancelled
- Implement TraverseFollows using Cypher path queries on FOLLOWS relationships - Implement TraverseFollowers using reverse path traversal - Implement FindMentions using MENTIONS relationships from p-tags - Implement TraverseThread using REFERENCES relationships from e-tags with bidirectional traversal (inbound replies, outbound parents) - Add GraphAdapter to bridge Neo4j to graph.GraphDatabase interface - Add GraphResult type implementing graph.GraphResultI for Neo4j - Initialize graph executor for Neo4j backend in app/main.go The implementation uses existing Neo4j schema and relationships created by SaveEvent() - no schema changes required. The _graph extension now works transparently with either Badger or Neo4j backends. Bump version to v0.35.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
198 lines
5.9 KiB
Go
198 lines
5.9 KiB
Go
package neo4j
|
|
|
|
import (
|
|
"sort"
|
|
)
|
|
|
|
// GraphResult contains depth-organized traversal results for graph queries.
|
|
// It tracks pubkeys and events discovered at each depth level, ensuring
|
|
// each entity appears only at the depth where it was first discovered.
|
|
//
|
|
// This is the Neo4j implementation that mirrors the Badger implementation
|
|
// in pkg/database/graph-result.go, implementing the graph.GraphResultI interface.
|
|
type GraphResult struct {
|
|
// PubkeysByDepth maps depth -> pubkeys first discovered at that depth.
|
|
// Each pubkey appears ONLY in the array for the depth where it was first seen.
|
|
// Depth 1 = direct connections, Depth 2 = connections of connections, etc.
|
|
PubkeysByDepth map[int][]string
|
|
|
|
// EventsByDepth maps depth -> event IDs discovered at that depth.
|
|
// Used for thread traversal queries.
|
|
EventsByDepth map[int][]string
|
|
|
|
// FirstSeenPubkey tracks which depth each pubkey was first discovered.
|
|
// Key is pubkey hex, value is the depth (1-indexed).
|
|
FirstSeenPubkey map[string]int
|
|
|
|
// FirstSeenEvent tracks which depth each event was first discovered.
|
|
// Key is event ID hex, value is the depth (1-indexed).
|
|
FirstSeenEvent map[string]int
|
|
|
|
// TotalPubkeys is the count of unique pubkeys discovered across all depths.
|
|
TotalPubkeys int
|
|
|
|
// TotalEvents is the count of unique events discovered across all depths.
|
|
TotalEvents int
|
|
}
|
|
|
|
// NewGraphResult creates a new initialized GraphResult.
|
|
func NewGraphResult() *GraphResult {
|
|
return &GraphResult{
|
|
PubkeysByDepth: make(map[int][]string),
|
|
EventsByDepth: make(map[int][]string),
|
|
FirstSeenPubkey: make(map[string]int),
|
|
FirstSeenEvent: make(map[string]int),
|
|
}
|
|
}
|
|
|
|
// AddPubkeyAtDepth adds a pubkey to the result at the specified depth if not already seen.
|
|
// Returns true if the pubkey was added (first time seen), false if already exists.
|
|
func (r *GraphResult) AddPubkeyAtDepth(pubkeyHex string, depth int) bool {
|
|
if _, exists := r.FirstSeenPubkey[pubkeyHex]; exists {
|
|
return false
|
|
}
|
|
|
|
r.FirstSeenPubkey[pubkeyHex] = depth
|
|
r.PubkeysByDepth[depth] = append(r.PubkeysByDepth[depth], pubkeyHex)
|
|
r.TotalPubkeys++
|
|
return true
|
|
}
|
|
|
|
// AddEventAtDepth adds an event ID to the result at the specified depth if not already seen.
|
|
// Returns true if the event was added (first time seen), false if already exists.
|
|
func (r *GraphResult) AddEventAtDepth(eventIDHex string, depth int) bool {
|
|
if _, exists := r.FirstSeenEvent[eventIDHex]; exists {
|
|
return false
|
|
}
|
|
|
|
r.FirstSeenEvent[eventIDHex] = depth
|
|
r.EventsByDepth[depth] = append(r.EventsByDepth[depth], eventIDHex)
|
|
r.TotalEvents++
|
|
return true
|
|
}
|
|
|
|
// HasPubkey returns true if the pubkey has been discovered at any depth.
|
|
func (r *GraphResult) HasPubkey(pubkeyHex string) bool {
|
|
_, exists := r.FirstSeenPubkey[pubkeyHex]
|
|
return exists
|
|
}
|
|
|
|
// HasEvent returns true if the event has been discovered at any depth.
|
|
func (r *GraphResult) HasEvent(eventIDHex string) bool {
|
|
_, exists := r.FirstSeenEvent[eventIDHex]
|
|
return exists
|
|
}
|
|
|
|
// ToDepthArrays converts the result to the response format: array of arrays.
|
|
// Index 0 = depth 1 pubkeys, Index 1 = depth 2 pubkeys, etc.
|
|
// Empty arrays are included for depths with no pubkeys to maintain index alignment.
|
|
func (r *GraphResult) ToDepthArrays() [][]string {
|
|
if len(r.PubkeysByDepth) == 0 {
|
|
return [][]string{}
|
|
}
|
|
|
|
// Find the maximum depth
|
|
maxDepth := 0
|
|
for d := range r.PubkeysByDepth {
|
|
if d > maxDepth {
|
|
maxDepth = d
|
|
}
|
|
}
|
|
|
|
// Create result array with entries for each depth
|
|
result := make([][]string, maxDepth)
|
|
for i := 0; i < maxDepth; i++ {
|
|
depth := i + 1 // depths are 1-indexed
|
|
if pubkeys, exists := r.PubkeysByDepth[depth]; exists {
|
|
result[i] = pubkeys
|
|
} else {
|
|
result[i] = []string{} // Empty array for depths with no pubkeys
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ToEventDepthArrays converts event results to the response format: array of arrays.
|
|
// Index 0 = depth 1 events, Index 1 = depth 2 events, etc.
|
|
func (r *GraphResult) ToEventDepthArrays() [][]string {
|
|
if len(r.EventsByDepth) == 0 {
|
|
return [][]string{}
|
|
}
|
|
|
|
maxDepth := 0
|
|
for d := range r.EventsByDepth {
|
|
if d > maxDepth {
|
|
maxDepth = d
|
|
}
|
|
}
|
|
|
|
result := make([][]string, maxDepth)
|
|
for i := 0; i < maxDepth; i++ {
|
|
depth := i + 1
|
|
if events, exists := r.EventsByDepth[depth]; exists {
|
|
result[i] = events
|
|
} else {
|
|
result[i] = []string{}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetAllPubkeys returns all pubkeys discovered across all depths.
|
|
func (r *GraphResult) GetAllPubkeys() []string {
|
|
all := make([]string, 0, r.TotalPubkeys)
|
|
for _, pubkeys := range r.PubkeysByDepth {
|
|
all = append(all, pubkeys...)
|
|
}
|
|
return all
|
|
}
|
|
|
|
// GetAllEvents returns all event IDs discovered across all depths.
|
|
func (r *GraphResult) GetAllEvents() []string {
|
|
all := make([]string, 0, r.TotalEvents)
|
|
for _, events := range r.EventsByDepth {
|
|
all = append(all, events...)
|
|
}
|
|
return all
|
|
}
|
|
|
|
// GetPubkeysByDepth returns the PubkeysByDepth map for external access.
|
|
func (r *GraphResult) GetPubkeysByDepth() map[int][]string {
|
|
return r.PubkeysByDepth
|
|
}
|
|
|
|
// GetEventsByDepth returns the EventsByDepth map for external access.
|
|
func (r *GraphResult) GetEventsByDepth() map[int][]string {
|
|
return r.EventsByDepth
|
|
}
|
|
|
|
// GetTotalPubkeys returns the total pubkey count for external access.
|
|
func (r *GraphResult) GetTotalPubkeys() int {
|
|
return r.TotalPubkeys
|
|
}
|
|
|
|
// GetTotalEvents returns the total event count for external access.
|
|
func (r *GraphResult) GetTotalEvents() int {
|
|
return r.TotalEvents
|
|
}
|
|
|
|
// GetDepthsSorted returns all depths that have pubkeys, sorted ascending.
|
|
func (r *GraphResult) GetDepthsSorted() []int {
|
|
depths := make([]int, 0, len(r.PubkeysByDepth))
|
|
for d := range r.PubkeysByDepth {
|
|
depths = append(depths, d)
|
|
}
|
|
sort.Ints(depths)
|
|
return depths
|
|
}
|
|
|
|
// GetEventDepthsSorted returns all depths that have events, sorted ascending.
|
|
func (r *GraphResult) GetEventDepthsSorted() []int {
|
|
depths := make([]int, 0, len(r.EventsByDepth))
|
|
for d := range r.EventsByDepth {
|
|
depths = append(depths, d)
|
|
}
|
|
sort.Ints(depths)
|
|
return depths
|
|
}
|