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 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 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 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 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 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(¤t, 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(¤t) {
434 if cost > best {
435 continue;
436 }
437 }
438
439 if let Ok(neighbors) = self.neighbors(¤t, 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(¤t) {
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 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 #[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 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 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 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 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}