1use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
5
6use redb::ReadableDatabase;
7use sinter_core::{Confidence, Edge, Evidence, Node, NodeId, Relation};
8
9use crate::error::StoreError;
10use crate::store::{IN_EDGES, NODES, OUT_EDGES, Store};
11
12#[derive(Debug, Default, Clone)]
15pub struct EdgeFilter {
16 pub evidence: Option<BTreeSet<Evidence>>,
18 pub min_confidence: Option<Confidence>,
20 pub relations: Option<BTreeSet<Relation>>,
22}
23
24impl EdgeFilter {
25 pub fn admits(&self, edge: &Edge) -> bool {
26 if edge.relation == Relation::Contains {
27 return false;
28 }
29 if let Some(allowed) = &self.relations
30 && !allowed.contains(&edge.relation)
31 {
32 return false;
33 }
34 if let Some(allowed) = &self.evidence
35 && !allowed.contains(&edge.evidence)
36 {
37 return false;
38 }
39 if self.min_confidence == Some(Confidence::Certain)
40 && edge.confidence != Confidence::Certain
41 {
42 return false;
43 }
44 true
45 }
46}
47
48pub struct Reached {
51 pub node: Node,
52 pub depth: usize,
53 pub via: Edge,
54}
55
56pub fn direct_summary(reached: &[Reached]) -> (usize, usize) {
60 let direct: Vec<&Reached> = reached.iter().filter(|r| r.depth == 1).collect();
61 let files: std::collections::HashSet<&str> =
62 direct.iter().map(|r| r.node.file.as_str()).collect();
63 (direct.len(), files.len())
64}
65
66impl Store {
67 pub fn dependents(
70 &self,
71 id: &NodeId,
72 filter: &EdgeFilter,
73 max_depth: usize,
74 ) -> Result<Vec<Reached>, StoreError> {
75 let txn = self.db.begin_read()?;
76 let nodes = txn.open_table(NODES)?;
77 let incoming = txn.open_multimap_table(IN_EDGES)?;
78 let mut seen: HashSet<NodeId> = HashSet::from([id.clone()]);
79 let mut queue: VecDeque<(NodeId, usize)> = VecDeque::from([(id.clone(), 0)]);
80 let mut out = Vec::new();
81 while let Some((current, depth)) = queue.pop_front() {
82 if depth >= max_depth {
83 continue;
84 }
85 for guard in incoming.get(current.as_str())? {
86 let edge: Edge = postcard::from_bytes(guard?.value())?;
87 if !filter.admits(&edge) || !seen.insert(edge.src.clone()) {
88 continue;
89 }
90 if let Some(guard) = nodes.get(edge.src.as_str())? {
91 let node = postcard::from_bytes(guard.value())?;
92 queue.push_back((edge.src.clone(), depth + 1));
93 out.push(Reached {
94 node,
95 depth: depth + 1,
96 via: edge,
97 });
98 }
99 }
100 }
101 Ok(out)
102 }
103
104 pub fn dependencies(
109 &self,
110 id: &NodeId,
111 filter: &EdgeFilter,
112 max_depth: usize,
113 ) -> Result<Vec<Reached>, StoreError> {
114 let txn = self.db.begin_read()?;
115 let nodes = txn.open_table(NODES)?;
116 let outgoing = txn.open_multimap_table(OUT_EDGES)?;
117 let mut seen: HashSet<NodeId> = HashSet::from([id.clone()]);
118 let mut queue: VecDeque<(NodeId, usize)> = VecDeque::from([(id.clone(), 0)]);
119 if nodes
120 .get(id.as_str())?
121 .map(|guard| postcard::from_bytes::<Node>(guard.value()))
122 .transpose()?
123 .is_some_and(|n| n.kind == sinter_core::SymbolKind::File)
124 {
125 for guard in outgoing.get(id.as_str())? {
126 let edge: Edge = postcard::from_bytes(guard?.value())?;
127 if edge.relation == Relation::Contains && seen.insert(edge.dst.clone()) {
128 queue.push_back((edge.dst.clone(), 0));
129 }
130 }
131 }
132 let mut out = Vec::new();
133 while let Some((current, depth)) = queue.pop_front() {
134 if depth >= max_depth {
135 continue;
136 }
137 for guard in outgoing.get(current.as_str())? {
138 let edge: Edge = postcard::from_bytes(guard?.value())?;
139 if !filter.admits(&edge) || !seen.insert(edge.dst.clone()) {
140 continue;
141 }
142 if let Some(guard) = nodes.get(edge.dst.as_str())? {
143 let node = postcard::from_bytes(guard.value())?;
144 queue.push_back((edge.dst.clone(), depth + 1));
145 out.push(Reached {
146 node,
147 depth: depth + 1,
148 via: edge,
149 });
150 }
151 }
152 }
153 Ok(out)
154 }
155
156 pub fn shortest_path(
158 &self,
159 from: &NodeId,
160 to: &NodeId,
161 filter: &EdgeFilter,
162 ) -> Result<Option<Vec<Edge>>, StoreError> {
163 let txn = self.db.begin_read()?;
164 let nodes = txn.open_table(NODES)?;
165 let outgoing = txn.open_multimap_table(OUT_EDGES)?;
166 let mut prev: HashMap<NodeId, Edge> = HashMap::new();
167 let mut seen: HashSet<NodeId> = HashSet::from([from.clone()]);
168 let mut queue: VecDeque<NodeId> = VecDeque::from([from.clone()]);
169 if nodes
173 .get(from.as_str())?
174 .map(|guard| postcard::from_bytes::<Node>(guard.value()))
175 .transpose()?
176 .is_some_and(|n| n.kind == sinter_core::SymbolKind::File)
177 {
178 for guard in outgoing.get(from.as_str())? {
179 let edge: Edge = postcard::from_bytes(guard?.value())?;
180 if edge.relation == Relation::Contains && seen.insert(edge.dst.clone()) {
181 prev.insert(edge.dst.clone(), edge.clone());
182 queue.push_back(edge.dst.clone());
183 }
184 }
185 }
186 while let Some(current) = queue.pop_front() {
187 if ¤t == to {
188 let mut path = Vec::new();
189 let mut at = to.clone();
190 while &at != from {
191 let edge = prev[&at].clone();
192 at = edge.src.clone();
193 path.push(edge);
194 }
195 path.reverse();
196 return Ok(Some(path));
197 }
198 for guard in outgoing.get(current.as_str())? {
199 let edge: Edge = postcard::from_bytes(guard?.value())?;
200 if !filter.admits(&edge) || !seen.insert(edge.dst.clone()) {
201 continue;
202 }
203 prev.insert(edge.dst.clone(), edge.clone());
204 queue.push_back(edge.dst.clone());
205 }
206 }
207 Ok(None)
208 }
209}