Skip to main content

codesynapse_core/
graph.rs

1use crate::error::{CodeSynapseError, Result};
2use crate::types::{Edge, Node, NodeId};
3use std::collections::HashMap;
4use std::path::Path;
5use std::sync::RwLock;
6
7pub trait GraphStore: Send + Sync {
8    fn add_node(&self, node: Node) -> Result<()>;
9    fn add_edge(&self, edge: Edge) -> Result<()>;
10    fn get_node(&self, id: &str) -> Result<Option<Node>>;
11    fn get_all_nodes(&self) -> Result<Vec<Node>>;
12    fn get_all_edges(&self) -> Result<Vec<Edge>>;
13    fn neighbors(&self, id: &str, relation_filter: Option<&str>) -> Result<Vec<(Node, Edge)>>;
14    fn search(&self, query: &str, top_k: usize) -> Result<Vec<(f64, Node)>>;
15    fn shortest_path(&self, src: &str, tgt: &str) -> Result<Option<Vec<Node>>>;
16    fn dijkstra_shortest_path(&self, src: &str, tgt: &str) -> Result<Option<Vec<Node>>>;
17    fn subgraph(&self, node_ids: &[&str]) -> Result<(Vec<Node>, Vec<Edge>)>;
18    fn node_count(&self) -> Result<usize>;
19    fn edge_count(&self) -> Result<usize>;
20    fn remove_node(&self, id: &str) -> Result<()>;
21    fn remove_edge(&self, source: &str, target: &str, relation: &str) -> Result<()>;
22    fn clear(&self) -> Result<()>;
23}
24
25pub enum StoreBackend {
26    Sled(SledGraphStore),
27    Memory(MemoryGraphStore),
28}
29
30impl GraphStore for StoreBackend {
31    fn add_node(&self, node: Node) -> Result<()> {
32        match self {
33            StoreBackend::Sled(s) => s.add_node(node),
34            StoreBackend::Memory(m) => m.add_node(node),
35        }
36    }
37
38    fn add_edge(&self, edge: Edge) -> Result<()> {
39        match self {
40            StoreBackend::Sled(s) => s.add_edge(edge),
41            StoreBackend::Memory(m) => m.add_edge(edge),
42        }
43    }
44
45    fn get_node(&self, id: &str) -> Result<Option<Node>> {
46        match self {
47            StoreBackend::Sled(s) => s.get_node(id),
48            StoreBackend::Memory(m) => m.get_node(id),
49        }
50    }
51
52    fn get_all_nodes(&self) -> Result<Vec<Node>> {
53        match self {
54            StoreBackend::Sled(s) => s.get_all_nodes(),
55            StoreBackend::Memory(m) => m.get_all_nodes(),
56        }
57    }
58
59    fn get_all_edges(&self) -> Result<Vec<Edge>> {
60        match self {
61            StoreBackend::Sled(s) => s.get_all_edges(),
62            StoreBackend::Memory(m) => m.get_all_edges(),
63        }
64    }
65
66    fn neighbors(&self, id: &str, relation_filter: Option<&str>) -> Result<Vec<(Node, Edge)>> {
67        match self {
68            StoreBackend::Sled(s) => s.neighbors(id, relation_filter),
69            StoreBackend::Memory(m) => m.neighbors(id, relation_filter),
70        }
71    }
72
73    fn search(&self, query: &str, top_k: usize) -> Result<Vec<(f64, Node)>> {
74        match self {
75            StoreBackend::Sled(s) => s.search(query, top_k),
76            StoreBackend::Memory(m) => m.search(query, top_k),
77        }
78    }
79
80    fn shortest_path(&self, src: &str, tgt: &str) -> Result<Option<Vec<Node>>> {
81        match self {
82            StoreBackend::Sled(s) => s.shortest_path(src, tgt),
83            StoreBackend::Memory(m) => m.shortest_path(src, tgt),
84        }
85    }
86
87    fn dijkstra_shortest_path(&self, src: &str, tgt: &str) -> Result<Option<Vec<Node>>> {
88        match self {
89            StoreBackend::Sled(s) => s.dijkstra_shortest_path(src, tgt),
90            StoreBackend::Memory(m) => m.dijkstra_shortest_path(src, tgt),
91        }
92    }
93
94    fn subgraph(&self, node_ids: &[&str]) -> Result<(Vec<Node>, Vec<Edge>)> {
95        match self {
96            StoreBackend::Sled(s) => s.subgraph(node_ids),
97            StoreBackend::Memory(m) => m.subgraph(node_ids),
98        }
99    }
100
101    fn node_count(&self) -> Result<usize> {
102        match self {
103            StoreBackend::Sled(s) => s.node_count(),
104            StoreBackend::Memory(m) => m.node_count(),
105        }
106    }
107
108    fn edge_count(&self) -> Result<usize> {
109        match self {
110            StoreBackend::Sled(s) => s.edge_count(),
111            StoreBackend::Memory(m) => m.edge_count(),
112        }
113    }
114
115    fn remove_node(&self, id: &str) -> Result<()> {
116        match self {
117            StoreBackend::Sled(s) => s.remove_node(id),
118            StoreBackend::Memory(m) => m.remove_node(id),
119        }
120    }
121
122    fn remove_edge(&self, source: &str, target: &str, relation: &str) -> Result<()> {
123        match self {
124            StoreBackend::Sled(s) => s.remove_edge(source, target, relation),
125            StoreBackend::Memory(m) => m.remove_edge(source, target, relation),
126        }
127    }
128
129    fn clear(&self) -> Result<()> {
130        match self {
131            StoreBackend::Sled(s) => s.clear(),
132            StoreBackend::Memory(m) => m.clear(),
133        }
134    }
135}
136
137pub struct SledGraphStore {
138    db: sled::Db,
139    inverted_index: RwLock<HashMap<String, Vec<String>>>,
140}
141
142impl SledGraphStore {
143    pub fn open(path: &Path) -> Result<Self> {
144        let db = sled::open(path).map_err(|e| CodeSynapseError::Database(e.to_string()))?;
145        let store = SledGraphStore {
146            db,
147            inverted_index: RwLock::new(HashMap::new()),
148        };
149        store.rebuild_index()?;
150        Ok(store)
151    }
152
153    pub fn temporary() -> Result<Self> {
154        let dir = std::env::temp_dir().join(format!("codesynapse-{}", uuid::Uuid::new_v4()));
155        std::fs::create_dir_all(&dir).ok();
156        Self::open(&dir)
157    }
158
159    fn node_key(id: &str) -> Vec<u8> {
160        format!("node:{}", id).into_bytes()
161    }
162
163    fn edge_key(id: &str) -> Vec<u8> {
164        format!("edge:{}", id).into_bytes()
165    }
166
167    fn adjacency_key(src: &str, target: &str) -> Vec<u8> {
168        format!("adj:{}:{}", src, target).into_bytes()
169    }
170
171    fn rebuild_index(&self) -> Result<()> {
172        let mut index = self.inverted_index.write().unwrap();
173        index.clear();
174        for result in self.db.iter() {
175            let (key, value) = result.map_err(|e| CodeSynapseError::Database(e.to_string()))?;
176            let key_str = String::from_utf8_lossy(&key);
177            if key_str.starts_with("node:") {
178                let node: Node = bincode::deserialize(&value).map_err(CodeSynapseError::Bincode)?;
179                for token in tokenize(&node.label) {
180                    index.entry(token).or_default().push(node.id.clone());
181                }
182                for token in tokenize(&node.id) {
183                    index.entry(token).or_default().push(node.id.clone());
184                }
185            }
186        }
187        Ok(())
188    }
189}
190
191impl GraphStore for SledGraphStore {
192    fn add_node(&self, node: Node) -> Result<()> {
193        let key = Self::node_key(&node.id);
194        let value = bincode::serialize(&node).map_err(CodeSynapseError::Bincode)?;
195        self.db
196            .insert(key, value)
197            .map_err(|e| CodeSynapseError::Database(e.to_string()))?;
198
199        // Update inverted index
200        let mut index = self.inverted_index.write().unwrap();
201        for token in tokenize(&node.label) {
202            index.entry(token).or_default().push(node.id.clone());
203        }
204        for token in tokenize(&node.id) {
205            index.entry(token).or_default().push(node.id.clone());
206        }
207
208        self.db
209            .flush()
210            .map_err(|e| CodeSynapseError::Database(e.to_string()))?;
211        Ok(())
212    }
213
214    fn add_edge(&self, edge: Edge) -> Result<()> {
215        let edge_id = format!("{}->{}:{}", edge.source, edge.target, edge.relation);
216        let key = Self::edge_key(&edge_id);
217        let value = bincode::serialize(&edge).map_err(CodeSynapseError::Bincode)?;
218        self.db
219            .insert(key, value)
220            .map_err(|e| CodeSynapseError::Database(e.to_string()))?;
221
222        // Adjacency index
223        let adj_key = Self::adjacency_key(&edge.source, &edge.target);
224        let adj_bytes = bincode::serialize(&edge).map_err(CodeSynapseError::Bincode)?;
225        self.db
226            .insert(adj_key, adj_bytes)
227            .map_err(|e| CodeSynapseError::Database(e.to_string()))?;
228
229        Ok(())
230    }
231
232    fn get_node(&self, id: &str) -> Result<Option<Node>> {
233        let key = Self::node_key(id);
234        match self.db.get(key) {
235            Ok(Some(value)) => {
236                let node: Node = bincode::deserialize(&value).map_err(CodeSynapseError::Bincode)?;
237                Ok(Some(node))
238            }
239            Ok(None) => Ok(None),
240            Err(e) => Err(CodeSynapseError::Database(e.to_string())),
241        }
242    }
243
244    fn get_all_nodes(&self) -> Result<Vec<Node>> {
245        let mut nodes = Vec::new();
246        for result in self.db.iter() {
247            let (key, value) = result.map_err(|e| CodeSynapseError::Database(e.to_string()))?;
248            let key_str = String::from_utf8_lossy(&key);
249            if key_str.starts_with("node:") {
250                let node: Node = bincode::deserialize(&value).map_err(CodeSynapseError::Bincode)?;
251                nodes.push(node);
252            }
253        }
254        Ok(nodes)
255    }
256
257    fn get_all_edges(&self) -> Result<Vec<Edge>> {
258        let mut edges = Vec::new();
259        for result in self.db.iter() {
260            let (key, value) = result.map_err(|e| CodeSynapseError::Database(e.to_string()))?;
261            let key_str = String::from_utf8_lossy(&key);
262            if key_str.starts_with("edge:") {
263                let edge: Edge = bincode::deserialize(&value).map_err(CodeSynapseError::Bincode)?;
264                edges.push(edge);
265            }
266        }
267        Ok(edges)
268    }
269
270    fn neighbors(&self, id: &str, _relation_filter: Option<&str>) -> Result<Vec<(Node, Edge)>> {
271        let mut result = Vec::new();
272        // Scan adjacency keys starting with adj:{id}:
273        let prefix = format!("adj:{}:", id);
274        for entry in self.db.scan_prefix(prefix.as_bytes()) {
275            let (_key, value) = entry.map_err(|e| CodeSynapseError::Database(e.to_string()))?;
276            let edge: Edge = bincode::deserialize(&value).map_err(CodeSynapseError::Bincode)?;
277            let neighbor_id = if edge.source == id {
278                &edge.target
279            } else {
280                &edge.source
281            };
282            if let Some(node) = self.get_node(neighbor_id)? {
283                result.push((node, edge));
284            }
285        }
286        Ok(result)
287    }
288
289    fn search(&self, query: &str, top_k: usize) -> Result<Vec<(f64, Node)>> {
290        let tokens = tokenize(query);
291        if tokens.is_empty() {
292            return Ok(vec![]);
293        }
294
295        let index = self.inverted_index.read().unwrap();
296        let mut scores: HashMap<String, f64> = HashMap::new();
297
298        // Use inverted index if available, fall back to full scan
299        if !index.is_empty() {
300            for token in &tokens {
301                if let Some(ids) = index.get(token) {
302                    for id in ids {
303                        *scores.entry(id.clone()).or_insert(0.0) += 1.0;
304                    }
305                }
306            }
307        } else {
308            // Full scan fallback
309            for result in self.db.iter() {
310                let (_key, value) =
311                    result.map_err(|e| CodeSynapseError::Database(e.to_string()))?;
312                let key_str = String::from_utf8_lossy(&_key);
313                if key_str.starts_with("node:") {
314                    let node: Node =
315                        bincode::deserialize(&value).map_err(CodeSynapseError::Bincode)?;
316                    let mut score = 0.0;
317                    for token in &tokens {
318                        if node.label.to_lowercase().contains(token) {
319                            score += 1.0;
320                        }
321                        if node.id.to_lowercase().contains(token) {
322                            score += 0.5;
323                        }
324                    }
325                    if score > 0.0 {
326                        scores.insert(node.id.clone(), score);
327                    }
328                }
329            }
330        }
331
332        let mut ranked: Vec<(f64, Node)> = scores
333            .into_iter()
334            .filter_map(|(id, score)| self.get_node(&id).ok().flatten().map(|n| (score, n)))
335            .collect();
336
337        ranked.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
338        ranked.truncate(top_k);
339        Ok(ranked)
340    }
341
342    fn shortest_path(&self, src: &str, tgt: &str) -> Result<Option<Vec<Node>>> {
343        if src == tgt {
344            return self.get_node(src).map(|n| n.map(|n| vec![n]));
345        }
346
347        let mut visited = std::collections::HashSet::new();
348        let mut queue = std::collections::VecDeque::new();
349        let mut parent: HashMap<String, String> = HashMap::new();
350
351        visited.insert(src.to_string());
352        queue.push_back(src.to_string());
353
354        while let Some(current) = queue.pop_front() {
355            if current == tgt {
356                let mut path = Vec::new();
357                let mut node_id = tgt.to_string();
358                while node_id != src {
359                    if let Some(n) = self.get_node(&node_id)? {
360                        path.push(n);
361                    }
362                    node_id = parent.get(&node_id).cloned().unwrap_or_default();
363                }
364                if let Some(n) = self.get_node(src)? {
365                    path.push(n);
366                }
367                path.reverse();
368                return Ok(Some(path));
369            }
370
371            if let Ok(neighbors) = self.neighbors(&current, None) {
372                for (neighbor, _) in neighbors {
373                    if visited.insert(neighbor.id.clone()) {
374                        parent.insert(neighbor.id.clone(), current.clone());
375                        queue.push_back(neighbor.id.clone());
376                    }
377                }
378            }
379        }
380
381        Ok(None)
382    }
383
384    fn dijkstra_shortest_path(&self, src: &str, tgt: &str) -> Result<Option<Vec<Node>>> {
385        use std::collections::BinaryHeap;
386
387        if src == tgt {
388            return self.get_node(src).map(|n| n.map(|n| vec![n]));
389        }
390
391        let mut distances: HashMap<String, f64> = HashMap::new();
392        let mut parents: HashMap<String, String> = HashMap::new();
393        let mut heap = BinaryHeap::new();
394
395        distances.insert(src.to_string(), 0.0);
396
397        #[derive(PartialEq)]
398        struct State(String, f64);
399        impl Eq for State {}
400        impl PartialOrd for State {
401            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
402                Some(self.cmp(other))
403            }
404        }
405        impl Ord for State {
406            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
407                other
408                    .1
409                    .partial_cmp(&self.1)
410                    .unwrap_or(std::cmp::Ordering::Equal)
411            }
412        }
413
414        heap.push(State(src.to_string(), 0.0));
415
416        while let Some(State(current, cost)) = heap.pop() {
417            if current == tgt {
418                let mut path = Vec::new();
419                let mut node_id = tgt.to_string();
420                while node_id != src {
421                    if let Some(n) = self.get_node(&node_id)? {
422                        path.push(n);
423                    }
424                    node_id = parents.get(&node_id).cloned().unwrap_or_default();
425                }
426                if let Some(n) = self.get_node(src)? {
427                    path.push(n);
428                }
429                path.reverse();
430                return Ok(Some(path));
431            }
432
433            if let Some(&best) = distances.get(&current) {
434                if cost > best {
435                    continue;
436                }
437            }
438
439            if let Ok(neighbors) = self.neighbors(&current, None) {
440                for (neighbor, edge) in neighbors {
441                    let next_cost = cost + edge.weight;
442                    let is_better = distances.get(&neighbor.id).is_none_or(|&d| next_cost < d);
443                    if is_better {
444                        distances.insert(neighbor.id.clone(), next_cost);
445                        parents.insert(neighbor.id.clone(), current.clone());
446                        heap.push(State(neighbor.id.clone(), next_cost));
447                    }
448                }
449            }
450        }
451
452        Ok(None)
453    }
454
455    fn subgraph(&self, node_ids: &[&str]) -> Result<(Vec<Node>, Vec<Edge>)> {
456        let mut nodes = Vec::new();
457        let mut edges = Vec::new();
458        let id_set: std::collections::HashSet<&str> = node_ids.iter().copied().collect();
459
460        for id in node_ids {
461            if let Some(node) = self.get_node(id)? {
462                nodes.push(node);
463            }
464        }
465
466        let all_edges = self.get_all_edges()?;
467        for edge in all_edges {
468            if id_set.contains(edge.source.as_str()) && id_set.contains(edge.target.as_str()) {
469                edges.push(edge);
470            }
471        }
472
473        Ok((nodes, edges))
474    }
475
476    fn node_count(&self) -> Result<usize> {
477        let mut count = 0;
478        for result in self.db.iter() {
479            let (key, _) = result.map_err(|e| CodeSynapseError::Database(e.to_string()))?;
480            if String::from_utf8_lossy(&key).starts_with("node:") {
481                count += 1;
482            }
483        }
484        Ok(count)
485    }
486
487    fn edge_count(&self) -> Result<usize> {
488        let mut count = 0;
489        for result in self.db.iter() {
490            let (key, _) = result.map_err(|e| CodeSynapseError::Database(e.to_string()))?;
491            if String::from_utf8_lossy(&key).starts_with("edge:") {
492                count += 1;
493            }
494        }
495        Ok(count)
496    }
497
498    fn remove_node(&self, id: &str) -> Result<()> {
499        let key = Self::node_key(id);
500        self.db
501            .remove(key)
502            .map_err(|e| CodeSynapseError::Database(e.to_string()))?;
503        Ok(())
504    }
505
506    fn remove_edge(&self, source: &str, target: &str, relation: &str) -> Result<()> {
507        let edge_id = format!("{}->{}:{}", source, target, relation);
508        let key = Self::edge_key(&edge_id);
509        self.db
510            .remove(key)
511            .map_err(|e| CodeSynapseError::Database(e.to_string()))?;
512        let adj_key = Self::adjacency_key(source, target);
513        self.db
514            .remove(adj_key)
515            .map_err(|e| CodeSynapseError::Database(e.to_string()))?;
516        Ok(())
517    }
518
519    fn clear(&self) -> Result<()> {
520        self.db
521            .clear()
522            .map_err(|e| CodeSynapseError::Database(e.to_string()))?;
523        self.inverted_index.write().unwrap().clear();
524        Ok(())
525    }
526}
527
528pub struct MemoryGraphStore {
529    nodes: RwLock<HashMap<NodeId, Node>>,
530    edges: RwLock<Vec<Edge>>,
531    inverted_index: RwLock<HashMap<String, Vec<String>>>,
532}
533
534impl MemoryGraphStore {
535    pub fn new() -> Self {
536        MemoryGraphStore {
537            nodes: RwLock::new(HashMap::new()),
538            edges: RwLock::new(Vec::new()),
539            inverted_index: RwLock::new(HashMap::new()),
540        }
541    }
542}
543
544impl Default for MemoryGraphStore {
545    fn default() -> Self {
546        Self::new()
547    }
548}
549
550impl GraphStore for MemoryGraphStore {
551    fn add_node(&self, node: Node) -> Result<()> {
552        let mut nodes = self.nodes.write().unwrap();
553        nodes.insert(node.id.clone(), node.clone());
554
555        let mut index = self.inverted_index.write().unwrap();
556        for token in tokenize(&node.label) {
557            index.entry(token).or_default().push(node.id.clone());
558        }
559        for token in tokenize(&node.id) {
560            index.entry(token).or_default().push(node.id.clone());
561        }
562
563        Ok(())
564    }
565
566    fn add_edge(&self, edge: Edge) -> Result<()> {
567        self.edges.write().unwrap().push(edge);
568        Ok(())
569    }
570
571    fn get_node(&self, id: &str) -> Result<Option<Node>> {
572        Ok(self.nodes.read().unwrap().get(id).cloned())
573    }
574
575    fn get_all_nodes(&self) -> Result<Vec<Node>> {
576        Ok(self.nodes.read().unwrap().values().cloned().collect())
577    }
578
579    fn get_all_edges(&self) -> Result<Vec<Edge>> {
580        Ok(self.edges.read().unwrap().clone())
581    }
582
583    fn neighbors(&self, id: &str, relation_filter: Option<&str>) -> Result<Vec<(Node, Edge)>> {
584        let edges = self.edges.read().unwrap();
585        let nodes = self.nodes.read().unwrap();
586        let mut result = Vec::new();
587        for edge in edges.iter() {
588            if let Some(filter) = relation_filter {
589                if edge.relation != filter {
590                    continue;
591                }
592            }
593            if edge.source == id {
594                if let Some(node) = nodes.get(&edge.target) {
595                    result.push((node.clone(), edge.clone()));
596                }
597            } else if edge.target == id {
598                if let Some(node) = nodes.get(&edge.source) {
599                    result.push((node.clone(), edge.clone()));
600                }
601            }
602        }
603        Ok(result)
604    }
605
606    fn search(&self, query: &str, top_k: usize) -> Result<Vec<(f64, Node)>> {
607        let tokens = tokenize(query);
608        if tokens.is_empty() {
609            return Ok(vec![]);
610        }
611
612        let nodes = self.nodes.read().unwrap();
613        let index = self.inverted_index.read().unwrap();
614
615        let mut scores: HashMap<String, f64> = HashMap::new();
616
617        if !index.is_empty() {
618            for token in &tokens {
619                if let Some(ids) = index.get(token) {
620                    for id in ids {
621                        *scores.entry(id.clone()).or_insert(0.0) += 1.0;
622                    }
623                }
624            }
625        } else {
626            for node in nodes.values() {
627                let mut score = 0.0;
628                for token in &tokens {
629                    if node.label.to_lowercase().contains(token) {
630                        score += 1.0;
631                    }
632                    if node.id.to_lowercase().contains(token) {
633                        score += 0.5;
634                    }
635                }
636                if score > 0.0 {
637                    scores.insert(node.id.clone(), score);
638                }
639            }
640        }
641
642        let mut ranked: Vec<(f64, Node)> = scores
643            .into_iter()
644            .filter_map(|(id, score)| nodes.get(&id).map(|n| (score, n.clone())))
645            .collect();
646
647        ranked.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
648        ranked.truncate(top_k);
649        Ok(ranked)
650    }
651
652    fn shortest_path(&self, src: &str, tgt: &str) -> Result<Option<Vec<Node>>> {
653        if src == tgt {
654            return Ok(self
655                .nodes
656                .read()
657                .unwrap()
658                .get(src)
659                .cloned()
660                .map(|n| vec![n]));
661        }
662
663        let nodes = self.nodes.read().unwrap();
664        let edges = self.edges.read().unwrap();
665
666        let mut visited = std::collections::HashSet::new();
667        let mut queue = std::collections::VecDeque::new();
668        let mut parent: HashMap<String, String> = HashMap::new();
669
670        visited.insert(src.to_string());
671        queue.push_back(src.to_string());
672
673        while let Some(current) = queue.pop_front() {
674            if current == tgt {
675                let mut path = Vec::new();
676                let mut node_id = tgt.to_string();
677                while node_id != src {
678                    if let Some(n) = nodes.get(&node_id) {
679                        path.push(n.clone());
680                    }
681                    node_id = parent.get(&node_id).cloned().unwrap_or_default();
682                }
683                if let Some(n) = nodes.get(src) {
684                    path.push(n.clone());
685                }
686                path.reverse();
687                return Ok(Some(path));
688            }
689
690            for edge in edges.iter() {
691                let neighbor = if edge.source == current {
692                    Some(&edge.target)
693                } else if edge.target == current {
694                    Some(&edge.source)
695                } else {
696                    None
697                };
698
699                if let Some(neighbor) = neighbor {
700                    if visited.insert(neighbor.clone()) {
701                        parent.insert(neighbor.clone(), current.clone());
702                        queue.push_back(neighbor.clone());
703                    }
704                }
705            }
706        }
707
708        Ok(None)
709    }
710
711    fn dijkstra_shortest_path(&self, src: &str, tgt: &str) -> Result<Option<Vec<Node>>> {
712        if src == tgt {
713            return Ok(self
714                .nodes
715                .read()
716                .unwrap()
717                .get(src)
718                .cloned()
719                .map(|n| vec![n]));
720        }
721
722        let nodes = self.nodes.read().unwrap();
723        let edges = self.edges.read().unwrap();
724        let mut distances: HashMap<String, f64> = HashMap::new();
725        let mut parents: HashMap<String, String> = HashMap::new();
726        let mut heap = std::collections::BinaryHeap::new();
727
728        distances.insert(src.to_string(), 0.0);
729
730        #[derive(PartialEq)]
731        struct State(String, f64);
732        impl Eq for State {}
733        impl PartialOrd for State {
734            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
735                Some(self.cmp(other))
736            }
737        }
738        impl Ord for State {
739            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
740                other
741                    .1
742                    .partial_cmp(&self.1)
743                    .unwrap_or(std::cmp::Ordering::Equal)
744            }
745        }
746
747        heap.push(State(src.to_string(), 0.0));
748
749        while let Some(State(current, cost)) = heap.pop() {
750            if current == tgt {
751                let mut path = Vec::new();
752                let mut node_id = tgt.to_string();
753                while node_id != src {
754                    if let Some(n) = nodes.get(&node_id) {
755                        path.push(n.clone());
756                    }
757                    node_id = parents.get(&node_id).cloned().unwrap_or_default();
758                }
759                if let Some(n) = nodes.get(src) {
760                    path.push(n.clone());
761                }
762                path.reverse();
763                return Ok(Some(path));
764            }
765
766            if let Some(&best) = distances.get(&current) {
767                if cost > best {
768                    continue;
769                }
770            }
771
772            for edge in edges.iter() {
773                let neighbor = if edge.source == current {
774                    Some(&edge.target)
775                } else if edge.target == current {
776                    Some(&edge.source)
777                } else {
778                    None
779                };
780                if let Some(neighbor) = neighbor {
781                    let next_cost = cost + edge.weight;
782                    let is_better = distances.get(neighbor).is_none_or(|&d| next_cost < d);
783                    if is_better {
784                        distances.insert(neighbor.clone(), next_cost);
785                        parents.insert(neighbor.clone(), current.clone());
786                        heap.push(State(neighbor.clone(), next_cost));
787                    }
788                }
789            }
790        }
791
792        Ok(None)
793    }
794
795    fn subgraph(&self, node_ids: &[&str]) -> Result<(Vec<Node>, Vec<Edge>)> {
796        let nodes = self.nodes.read().unwrap();
797        let edges = self.edges.read().unwrap();
798        let id_set: std::collections::HashSet<&str> = node_ids.iter().copied().collect();
799
800        let sg_nodes: Vec<Node> = nodes
801            .values()
802            .filter(|n| id_set.contains(n.id.as_str()))
803            .cloned()
804            .collect();
805        let sg_edges: Vec<Edge> = edges
806            .iter()
807            .filter(|e| id_set.contains(e.source.as_str()) && id_set.contains(e.target.as_str()))
808            .cloned()
809            .collect();
810
811        Ok((sg_nodes, sg_edges))
812    }
813
814    fn node_count(&self) -> Result<usize> {
815        Ok(self.nodes.read().unwrap().len())
816    }
817
818    fn edge_count(&self) -> Result<usize> {
819        Ok(self.edges.read().unwrap().len())
820    }
821
822    fn remove_node(&self, id: &str) -> Result<()> {
823        self.nodes.write().unwrap().remove(id);
824        Ok(())
825    }
826
827    fn remove_edge(&self, source: &str, target: &str, relation: &str) -> Result<()> {
828        let mut edges = self.edges.write().unwrap();
829        edges.retain(|e| !(e.source == source && e.target == target && e.relation == relation));
830        Ok(())
831    }
832
833    fn clear(&self) -> Result<()> {
834        self.nodes.write().unwrap().clear();
835        self.edges.write().unwrap().clear();
836        self.inverted_index.write().unwrap().clear();
837        Ok(())
838    }
839}
840
841fn tokenize(s: &str) -> Vec<String> {
842    s.to_lowercase()
843        .split(|c: char| !c.is_alphanumeric())
844        .filter(|t| !t.is_empty() && t.len() >= 2)
845        .map(|t| t.to_string())
846        .collect()
847}
848
849#[cfg(test)]
850mod tests {
851    use super::*;
852    use std::collections::HashMap;
853    use tempfile;
854
855    fn make_node(id: &str, label: &str) -> Node {
856        Node {
857            id: id.to_string(),
858            label: label.to_string(),
859            file_type: "code".to_string(),
860            source_file: "test.py".to_string(),
861            source_location: None,
862            community: None,
863            rationale: None,
864            docstring: None,
865            metadata: HashMap::new(),
866        }
867    }
868
869    fn make_edge(src: &str, tgt: &str, relation: &str) -> Edge {
870        Edge {
871            source: src.to_string(),
872            target: tgt.to_string(),
873            relation: relation.to_string(),
874            confidence: "EXTRACTED".to_string(),
875            source_file: Some("test.py".to_string()),
876            weight: 1.0,
877            context: None,
878        }
879    }
880
881    #[test]
882    fn test_memory_store_add_get_node() {
883        let store = MemoryGraphStore::new();
884        store.add_node(make_node("a", "Alpha")).unwrap();
885        let node = store.get_node("a").unwrap().unwrap();
886        assert_eq!(node.label, "Alpha");
887    }
888
889    #[test]
890    fn test_memory_store_search() {
891        let store = MemoryGraphStore::new();
892        store.add_node(make_node("auth", "AuthService")).unwrap();
893        store.add_node(make_node("user", "UserService")).unwrap();
894
895        let results = store.search("auth", 10).unwrap();
896        assert_eq!(results.len(), 1);
897        assert_eq!(results[0].1.label, "AuthService");
898    }
899
900    #[test]
901    fn test_memory_store_neighbors() {
902        let store = MemoryGraphStore::new();
903        store.add_node(make_node("a", "A")).unwrap();
904        store.add_node(make_node("b", "B")).unwrap();
905        store.add_edge(make_edge("a", "b", "connects")).unwrap();
906
907        let neighbors = store.neighbors("a", None).unwrap();
908        assert_eq!(neighbors.len(), 1);
909        assert_eq!(neighbors[0].0.label, "B");
910    }
911
912    #[test]
913    fn test_memory_store_shortest_path() {
914        let store = MemoryGraphStore::new();
915        store.add_node(make_node("a", "A")).unwrap();
916        store.add_node(make_node("b", "B")).unwrap();
917        store.add_node(make_node("c", "C")).unwrap();
918        store.add_edge(make_edge("a", "b", "connects")).unwrap();
919        store.add_edge(make_edge("b", "c", "connects")).unwrap();
920
921        let path = store.shortest_path("a", "c").unwrap().unwrap();
922        assert_eq!(path.len(), 3);
923        assert_eq!(path[0].label, "A");
924        assert_eq!(path[1].label, "B");
925        assert_eq!(path[2].label, "C");
926    }
927
928    #[test]
929    fn test_memory_store_shortest_path_same_node() {
930        let store = MemoryGraphStore::new();
931        store.add_node(make_node("a", "A")).unwrap();
932        let path = store.shortest_path("a", "a").unwrap().unwrap();
933        assert_eq!(path.len(), 1);
934    }
935
936    #[test]
937    fn test_memory_store_shortest_path_no_path() {
938        let store = MemoryGraphStore::new();
939        store.add_node(make_node("a", "A")).unwrap();
940        store.add_node(make_node("b", "B")).unwrap();
941        let path = store.shortest_path("a", "b").unwrap();
942        assert!(path.is_none());
943    }
944
945    #[test]
946    fn test_memory_store_subgraph() {
947        let store = MemoryGraphStore::new();
948        store.add_node(make_node("a", "A")).unwrap();
949        store.add_node(make_node("b", "B")).unwrap();
950        store.add_node(make_node("c", "C")).unwrap();
951        store.add_edge(make_edge("a", "b", "connects")).unwrap();
952        store.add_edge(make_edge("b", "c", "connects")).unwrap();
953
954        let (sg_nodes, sg_edges) = store.subgraph(&["a", "b"]).unwrap();
955        assert_eq!(sg_nodes.len(), 2);
956        assert_eq!(sg_edges.len(), 1);
957    }
958
959    #[test]
960    fn test_tokenize() {
961        let tokens = tokenize("AuthService");
962        assert!(tokens.contains(&"authservice".to_string()));
963
964        let tokens = tokenize("Hello World");
965        assert!(tokens.contains(&"hello".to_string()));
966        assert!(tokens.contains(&"world".to_string()));
967    }
968
969    #[test]
970    fn test_sled_store_basic_ops() {
971        let store = SledGraphStore::temporary().unwrap();
972        store.add_node(make_node("a", "Alpha")).unwrap();
973        store.add_node(make_node("b", "Beta")).unwrap();
974        store.add_edge(make_edge("a", "b", "connects")).unwrap();
975
976        assert_eq!(store.node_count().unwrap(), 2);
977        assert_eq!(store.edge_count().unwrap(), 1);
978
979        let node = store.get_node("a").unwrap().unwrap();
980        assert_eq!(node.label, "Alpha");
981    }
982
983    #[test]
984    fn test_sled_store_shortest_path() {
985        let store = SledGraphStore::temporary().unwrap();
986        store.add_node(make_node("a", "A")).unwrap();
987        store.add_node(make_node("b", "B")).unwrap();
988        store.add_node(make_node("c", "C")).unwrap();
989        store.add_edge(make_edge("a", "b", "connects")).unwrap();
990        store.add_edge(make_edge("b", "c", "connects")).unwrap();
991
992        let path = store.shortest_path("a", "c").unwrap().unwrap();
993        assert_eq!(path.len(), 3);
994        assert_eq!(path[0].label, "A");
995        assert_eq!(path[1].label, "B");
996        assert_eq!(path[2].label, "C");
997    }
998
999    #[test]
1000    fn test_sled_store_shortest_path_no_path() {
1001        let store = SledGraphStore::temporary().unwrap();
1002        store.add_node(make_node("a", "A")).unwrap();
1003        store.add_node(make_node("b", "B")).unwrap();
1004        // no edge between a and b
1005        let path = store.shortest_path("a", "b").unwrap();
1006        assert!(path.is_none());
1007    }
1008
1009    #[test]
1010    fn test_sled_store_rebuild_index() {
1011        let store = SledGraphStore::temporary().unwrap();
1012        store.add_node(make_node("auth", "AuthService")).unwrap();
1013        store.rebuild_index().unwrap();
1014
1015        let results = store.search("auth", 10).unwrap();
1016        assert_eq!(results.len(), 1);
1017    }
1018
1019    #[test]
1020    fn test_store_backend_memory() {
1021        let backend = StoreBackend::Memory(MemoryGraphStore::new());
1022        backend.add_node(make_node("a", "A")).unwrap();
1023        assert_eq!(backend.node_count().unwrap(), 1);
1024    }
1025
1026    // --- Edge case tests ---
1027
1028    #[test]
1029    fn test_memory_get_node_nonexistent() {
1030        let store = MemoryGraphStore::new();
1031        let node = store.get_node("nonexistent").unwrap();
1032        assert!(node.is_none());
1033    }
1034
1035    #[test]
1036    fn test_memory_neighbors_nonexistent() {
1037        let store = MemoryGraphStore::new();
1038        let nbrs = store.neighbors("ghost", None).unwrap();
1039        assert!(nbrs.is_empty());
1040    }
1041
1042    #[test]
1043    fn test_memory_neighbors_relation_filter() {
1044        let store = MemoryGraphStore::new();
1045        store.add_node(make_node("a", "A")).unwrap();
1046        store.add_node(make_node("b", "B")).unwrap();
1047        store.add_edge(make_edge("a", "b", "calls")).unwrap();
1048        store.add_edge(make_edge("a", "b", "imports")).unwrap();
1049
1050        let calls = store.neighbors("a", Some("calls")).unwrap();
1051        assert_eq!(calls.len(), 1);
1052        assert_eq!(calls[0].1.relation, "calls");
1053
1054        let imports = store.neighbors("a", Some("imports")).unwrap();
1055        assert_eq!(imports.len(), 1);
1056
1057        let none = store.neighbors("a", Some("inherits")).unwrap();
1058        assert!(none.is_empty());
1059    }
1060
1061    #[test]
1062    fn test_memory_search_empty_query() {
1063        let store = MemoryGraphStore::new();
1064        store.add_node(make_node("a", "Alpha")).unwrap();
1065        let results = store.search("", 10).unwrap();
1066        assert!(results.is_empty());
1067    }
1068
1069    #[test]
1070    fn test_memory_search_single_char() {
1071        let store = MemoryGraphStore::new();
1072        store.add_node(make_node("a", "Alpha")).unwrap();
1073        let results = store.search("A", 10).unwrap();
1074        assert!(results.is_empty(), "single char should be tokenized away");
1075    }
1076
1077    #[test]
1078    fn test_memory_search_no_match() {
1079        let store = MemoryGraphStore::new();
1080        store.add_node(make_node("a", "Alpha")).unwrap();
1081        let results = store.search("Zeta", 10).unwrap();
1082        assert!(results.is_empty());
1083    }
1084
1085    #[test]
1086    fn test_memory_remove_node_nonexistent() {
1087        let store = MemoryGraphStore::new();
1088        store.remove_node("ghost").unwrap();
1089        // no panic — should be a no-op
1090        assert_eq!(store.node_count().unwrap(), 0);
1091    }
1092
1093    #[test]
1094    fn test_memory_remove_edge_nonexistent() {
1095        let store = MemoryGraphStore::new();
1096        store.remove_edge("a", "b", "calls").unwrap();
1097        assert_eq!(store.edge_count().unwrap(), 0);
1098    }
1099
1100    #[test]
1101    fn test_memory_add_duplicate_edge() {
1102        let store = MemoryGraphStore::new();
1103        store.add_node(make_node("a", "A")).unwrap();
1104        store.add_node(make_node("b", "B")).unwrap();
1105        let e1 = make_edge("a", "b", "calls");
1106        let e2 = make_edge("a", "b", "calls");
1107        store.add_edge(e1).unwrap();
1108        store.add_edge(e2).unwrap();
1109        assert_eq!(
1110            store.edge_count().unwrap(),
1111            2,
1112            "duplicate edges are allowed"
1113        );
1114    }
1115
1116    #[test]
1117    fn test_memory_subgraph_nonexistent_ids() {
1118        let store = MemoryGraphStore::new();
1119        let (nodes, edges) = store.subgraph(&["ghost1", "ghost2"]).unwrap();
1120        assert!(nodes.is_empty());
1121        assert!(edges.is_empty());
1122    }
1123
1124    #[test]
1125    fn test_memory_shortest_path_missing_src() {
1126        let store = MemoryGraphStore::new();
1127        store.add_node(make_node("b", "B")).unwrap();
1128        let path = store.shortest_path("a", "b").unwrap();
1129        assert!(path.is_none(), "no path when src node missing");
1130    }
1131
1132    #[test]
1133    fn test_memory_clear_then_ops() {
1134        let store = MemoryGraphStore::new();
1135        store.add_node(make_node("a", "A")).unwrap();
1136        store.add_edge(make_edge("a", "b", "calls")).unwrap();
1137        store.clear().unwrap();
1138        assert_eq!(store.node_count().unwrap(), 0);
1139        assert_eq!(store.edge_count().unwrap(), 0);
1140        // After clear, adding should still work
1141        store.add_node(make_node("b", "B")).unwrap();
1142        assert_eq!(store.node_count().unwrap(), 1);
1143    }
1144
1145    #[test]
1146    fn test_sled_store_clear() {
1147        let store = SledGraphStore::temporary().unwrap();
1148        store.add_node(make_node("a", "A")).unwrap();
1149        store.clear().unwrap();
1150        assert_eq!(store.node_count().unwrap(), 0);
1151    }
1152
1153    #[test]
1154    fn test_dijkstra_shortest_path_weighted() {
1155        let store = MemoryGraphStore::new();
1156        store.add_node(make_node("a", "A")).unwrap();
1157        store.add_node(make_node("b", "B")).unwrap();
1158        store.add_node(make_node("c", "C")).unwrap();
1159        store.add_node(make_node("d", "D")).unwrap();
1160        // a -> b (1.0), a -> c (10.0), b -> d (1.0), c -> d (1.0)
1161        store
1162            .add_edge(make_edge_weighted("a", "b", "connects", 1.0))
1163            .unwrap();
1164        store
1165            .add_edge(make_edge_weighted("a", "c", "connects", 10.0))
1166            .unwrap();
1167        store
1168            .add_edge(make_edge_weighted("b", "d", "connects", 1.0))
1169            .unwrap();
1170        store
1171            .add_edge(make_edge_weighted("c", "d", "connects", 1.0))
1172            .unwrap();
1173
1174        let path = store.dijkstra_shortest_path("a", "d").unwrap().unwrap();
1175        assert_eq!(path.len(), 3, "shortest path should be a->b->d");
1176        assert_eq!(path[0].label, "A");
1177        assert_eq!(path[1].label, "B");
1178        assert_eq!(path[2].label, "D");
1179    }
1180
1181    #[test]
1182    fn test_dijkstra_no_path() {
1183        let store = MemoryGraphStore::new();
1184        store.add_node(make_node("a", "A")).unwrap();
1185        store.add_node(make_node("b", "B")).unwrap();
1186        let path = store.dijkstra_shortest_path("a", "b").unwrap();
1187        assert!(path.is_none());
1188    }
1189
1190    #[test]
1191    fn test_dijkstra_same_node() {
1192        let store = MemoryGraphStore::new();
1193        store.add_node(make_node("a", "A")).unwrap();
1194        let path = store.dijkstra_shortest_path("a", "a").unwrap().unwrap();
1195        assert_eq!(path.len(), 1);
1196    }
1197
1198    fn make_edge_weighted(src: &str, tgt: &str, relation: &str, weight: f64) -> Edge {
1199        Edge {
1200            source: src.to_string(),
1201            target: tgt.to_string(),
1202            relation: relation.to_string(),
1203            confidence: "EXTRACTED".to_string(),
1204            source_file: Some("test.py".to_string()),
1205            weight,
1206            context: None,
1207        }
1208    }
1209
1210    #[test]
1211    fn test_sled_store_persistence() {
1212        let tmpdir = tempfile::tempdir().unwrap();
1213        let db_path = tmpdir.path().join("sled.db");
1214
1215        {
1216            let store = SledGraphStore::open(&db_path).unwrap();
1217            store.add_node(make_node("persist_a", "PersistA")).unwrap();
1218            store.add_node(make_node("persist_b", "PersistB")).unwrap();
1219            store
1220                .add_edge(make_edge("persist_a", "persist_b", "persists"))
1221                .unwrap();
1222            assert_eq!(store.node_count().unwrap(), 2);
1223            assert_eq!(store.edge_count().unwrap(), 1);
1224            drop(store);
1225        }
1226
1227        {
1228            let store = SledGraphStore::open(&db_path).unwrap();
1229            assert_eq!(store.node_count().unwrap(), 2);
1230            assert_eq!(store.edge_count().unwrap(), 1);
1231            let node = store.get_node("persist_a").unwrap().unwrap();
1232            assert_eq!(node.label, "PersistA");
1233            let all_nodes = store.get_all_nodes().unwrap();
1234            let ids: Vec<&str> = all_nodes.iter().map(|n| n.id.as_str()).collect();
1235            assert!(ids.contains(&"persist_a"));
1236            assert!(ids.contains(&"persist_b"));
1237        }
1238    }
1239
1240    #[test]
1241    fn test_sled_store_reopen_empty() {
1242        let tmpdir = tempfile::tempdir().unwrap();
1243        let db_path = tmpdir.path().join("sled.db");
1244
1245        {
1246            let store = SledGraphStore::open(&db_path).unwrap();
1247            assert_eq!(store.node_count().unwrap(), 0);
1248            assert_eq!(store.edge_count().unwrap(), 0);
1249            drop(store);
1250        }
1251
1252        {
1253            let store = SledGraphStore::open(&db_path).unwrap();
1254            assert_eq!(store.node_count().unwrap(), 0);
1255            assert_eq!(store.edge_count().unwrap(), 0);
1256        }
1257    }
1258
1259    #[test]
1260    fn test_sled_store_search_after_reopen_with_new_add() {
1261        let tmpdir = tempfile::tempdir().unwrap();
1262        let db_path = tmpdir.path().join("sled.db");
1263
1264        {
1265            let store = SledGraphStore::open(&db_path).unwrap();
1266            store.add_node(make_node("auth", "AuthService")).unwrap();
1267            drop(store);
1268        }
1269
1270        {
1271            let store = SledGraphStore::open(&db_path).unwrap();
1272            // Adding a new node makes the in-memory index non-empty.
1273            // Without rebuild on open, search uses the index and misses "auth".
1274            store.add_node(make_node("new", "NewService")).unwrap();
1275            let results = store.search("auth", 10).unwrap();
1276            assert_eq!(
1277                results.len(),
1278                1,
1279                "persisted node must be found after reopen+add"
1280            );
1281        }
1282    }
1283}