1use crate::view::GraphView;
2use core_storage::Direction;
3use std::collections::{BTreeSet, VecDeque};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Dir {
7 Out,
8 In,
9 Both,
10}
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub struct EdgeRef {
14 pub etype: u32,
15 pub src: u32,
16 pub dst: u32,
17}
18
19pub fn expand(view: &GraphView, id: u32, etypes: Option<&[u32]>, dir: Dir) -> Vec<EdgeRef> {
23 let types: Vec<u32> = match etypes {
24 Some(ts) => {
25 let mut v = ts.to_vec();
26 v.sort_unstable();
27 v.dedup();
28 v
29 }
30 None => view.topo.etypes().collect(),
31 };
32 let mut out = Vec::new();
33 let mut seen = BTreeSet::new();
34 for etype in types {
35 if matches!(dir, Dir::Out | Dir::Both) {
36 for &dst in view.topo.neighbors(etype, Direction::Out, id).as_ref() {
37 push_unique(
38 &mut out,
39 &mut seen,
40 EdgeRef {
41 etype,
42 src: id,
43 dst,
44 },
45 );
46 }
47 }
48 if matches!(dir, Dir::In | Dir::Both) {
49 for &src in view.topo.neighbors(etype, Direction::In, id).as_ref() {
50 push_unique(
51 &mut out,
52 &mut seen,
53 EdgeRef {
54 etype,
55 src,
56 dst: id,
57 },
58 );
59 }
60 }
61 }
62 out
63}
64
65#[derive(Debug, Clone, PartialEq)]
66pub struct Neighborhood {
67 pub nodes: Vec<(u32, u32)>, pub edges: Vec<EdgeRef>, }
70
71pub fn neighborhood(
72 view: &GraphView,
73 start: u32,
74 depth: u32,
75 etypes: Option<&[u32]>,
76 dir: Dir,
77) -> Neighborhood {
78 let mut visited = BTreeSet::new();
79 visited.insert(start);
80 let mut queue = VecDeque::new();
81 queue.push_back((start, 0u32));
82 let mut nodes = Vec::new();
83 let mut edges = BTreeSet::new();
84
85 while let Some((id, d)) = queue.pop_front() {
86 if d >= depth {
87 continue;
88 }
89 for e in expand(view, id, etypes, dir) {
90 edges.insert(e);
91 let nbr = if e.src == id { e.dst } else { e.src };
92 if visited.insert(nbr) {
93 let nd = d + 1;
94 nodes.push((nbr, nd));
95 queue.push_back((nbr, nd));
96 }
97 }
98 }
99
100 Neighborhood {
101 nodes,
102 edges: edges.into_iter().collect(),
103 }
104}
105
106fn push_unique(out: &mut Vec<EdgeRef>, seen: &mut BTreeSet<EdgeRef>, e: EdgeRef) {
107 if seen.insert(e) {
108 out.push(e);
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::{expand, neighborhood, Dir, EdgeRef};
115 use crate::view::GraphView;
116 use core_storage::{ColumnStore, EdgeProps, IdMap, Interner, Topology};
117
118 struct Fx {
119 ids: IdMap,
120 syms: Interner,
121 labels: Vec<u32>,
122 props: ColumnStore,
123 topo: Topology,
124 eprops: EdgeProps,
125 }
126
127 impl Fx {
128 fn new() -> Self {
129 Fx {
130 ids: IdMap::new(),
131 syms: Interner::new(),
132 labels: vec![],
133 props: ColumnStore::new(),
134 topo: Topology::new(),
135 eprops: EdgeProps::new(),
136 }
137 }
138
139 fn add(&mut self, label: &str, key: &str) -> u32 {
140 let id = self.ids.get_or_insert(key);
141 let sym = self.syms.intern(label);
142 self.labels.resize(id as usize + 1, u32::MAX);
143 self.labels[id as usize] = sym;
144 id
145 }
146
147 fn view(&self) -> GraphView<'_> {
148 GraphView {
149 ids: &self.ids,
150 syms: &self.syms,
151 labels: &self.labels,
152 props: &self.props,
153 topo: &self.topo,
154 edge_props: &self.eprops,
155 mask: None,
156 }
157 }
158 }
159
160 struct Diamond {
165 fx: Fx,
166 a: u32,
167 b: u32,
168 c: u32,
169 d: u32,
170 knows: u32,
171 likes: u32,
172 }
173
174 fn diamond() -> Diamond {
175 let mut fx = Fx::new();
176 let a = fx.add("Person", "a");
177 let b = fx.add("Person", "b");
178 let c = fx.add("Person", "c");
179 let d = fx.add("Person", "d");
180 let knows = fx.syms.intern("KNOWS");
181 let likes = fx.syms.intern("LIKES");
182 fx.topo.add_edge(knows, a, b);
183 fx.topo.add_edge(knows, a, c);
184 fx.topo.add_edge(knows, b, d);
185 fx.topo.add_edge(knows, c, d);
186 fx.topo.add_edge(likes, a, d);
187 Diamond {
188 fx,
189 a,
190 b,
191 c,
192 d,
193 knows,
194 likes,
195 }
196 }
197
198 fn e(etype: u32, src: u32, dst: u32) -> EdgeRef {
199 EdgeRef { etype, src, dst }
200 }
201
202 #[test]
203 fn expand_etype_then_neighbor_order_and_both_is_out_then_in() {
204 let g = diamond();
205 let v = g.fx.view();
206 assert_eq!(
207 expand(&v, g.a, None, Dir::Out),
208 vec![
209 e(g.knows, g.a, g.b),
210 e(g.knows, g.a, g.c),
211 e(g.likes, g.a, g.d)
212 ]
213 );
214 assert_eq!(
216 expand(&v, g.a, Some(&[g.likes, g.knows]), Dir::Out),
217 vec![
218 e(g.knows, g.a, g.b),
219 e(g.knows, g.a, g.c),
220 e(g.likes, g.a, g.d)
221 ]
222 );
223 assert_eq!(
224 expand(&v, g.d, Some(&[g.knows]), Dir::In),
225 vec![e(g.knows, g.b, g.d), e(g.knows, g.c, g.d)]
226 );
227 assert_eq!(
228 expand(&v, g.b, Some(&[g.knows]), Dir::Both),
229 vec![e(g.knows, g.b, g.d), e(g.knows, g.a, g.b)]
230 );
231 assert!(expand(&v, g.a, Some(&[g.knows]), Dir::Out).contains(&e(g.knows, g.a, g.b)));
233 assert_eq!(
234 expand(&v, g.b, Some(&[g.knows]), Dir::In),
235 vec![e(g.knows, g.a, g.b)]
236 );
237 }
238
239 #[test]
240 fn expand_none_uses_topo_etypes_not_interned_symbols() {
241 let mut fx = Fx::new();
242 let a = fx.add("Person", "a");
244 let b = fx.add("Person", "b");
245 let knows = fx.syms.intern("KNOWS");
246 let _age = fx.syms.intern("age");
247 let _company = fx.syms.intern("Company");
248 let likes = fx.syms.intern("LIKES");
249 fx.topo.add_edge(knows, a, b);
250 fx.topo.add_edge(likes, a, b);
251 assert!(fx.syms.get("Person").unwrap() < knows);
252 assert!(knows < fx.syms.get("age").unwrap());
253 assert!(fx.syms.get("age").unwrap() < likes);
254 let v = fx.view();
255 assert_eq!(
256 expand(&v, a, None, Dir::Out),
257 vec![e(knows, a, b), e(likes, a, b)]
258 );
259 }
260
261 #[test]
262 fn expand_dedupes_only_identical_triples() {
263 let mut g = diamond();
264 g.fx.topo.add_edge(g.knows, g.a, g.a); let v = g.fx.view();
266 let both = expand(&v, g.a, Some(&[g.knows, g.knows]), Dir::Both);
267 let self_loop_hits = both.iter().filter(|x| *x == &e(g.knows, g.a, g.a)).count();
268 assert_eq!(self_loop_hits, 1);
269 assert!(both.contains(&e(g.knows, g.a, g.b)));
270 assert!(both.contains(&e(g.knows, g.a, g.c)));
271 }
272
273 #[test]
274 fn neighborhood_depth0_empty() {
275 let g = diamond();
276 let v = g.fx.view();
277 let n = neighborhood(&v, g.a, 0, None, Dir::Out);
278 assert!(n.nodes.is_empty());
279 assert!(n.edges.is_empty());
280 }
281
282 #[test]
283 fn neighborhood_depth1_vs_depth2_and_first_seen() {
284 let g = diamond();
285 let v = g.fx.view();
286
287 let d1 = neighborhood(&v, g.a, 1, Some(&[g.knows]), Dir::Out);
288 assert_eq!(d1.nodes, vec![(g.b, 1), (g.c, 1)]);
289 assert_eq!(d1.edges, vec![e(g.knows, g.a, g.b), e(g.knows, g.a, g.c)]);
290
291 let d2 = neighborhood(&v, g.a, 2, Some(&[g.knows]), Dir::Out);
292 assert_eq!(d2.nodes, vec![(g.b, 1), (g.c, 1), (g.d, 2)]);
293 assert_eq!(
295 d2.edges,
296 vec![
297 e(g.knows, g.a, g.b),
298 e(g.knows, g.a, g.c),
299 e(g.knows, g.b, g.d),
300 e(g.knows, g.c, g.d),
301 ]
302 );
303
304 let all = neighborhood(&v, g.a, 2, None, Dir::Out);
306 assert_eq!(all.nodes, vec![(g.b, 1), (g.c, 1), (g.d, 1)]);
307 assert_eq!(
308 all.edges,
309 vec![
310 e(g.knows, g.a, g.b),
311 e(g.knows, g.a, g.c),
312 e(g.knows, g.b, g.d),
313 e(g.knows, g.c, g.d),
314 e(g.likes, g.a, g.d),
315 ]
316 );
317 }
318
319 #[test]
320 fn neighborhood_dir_in_out_both_and_etype_filter() {
321 let g = diamond();
322 let v = g.fx.view();
323
324 let inn = neighborhood(&v, g.d, 1, None, Dir::In);
325 assert_eq!(inn.nodes, vec![(g.b, 1), (g.c, 1), (g.a, 1)]);
326 assert_eq!(
327 inn.edges,
328 vec![
329 e(g.knows, g.b, g.d),
330 e(g.knows, g.c, g.d),
331 e(g.likes, g.a, g.d),
332 ]
333 );
334
335 let likes_only = neighborhood(&v, g.a, 2, Some(&[g.likes]), Dir::Out);
336 assert_eq!(likes_only.nodes, vec![(g.d, 1)]);
337 assert_eq!(likes_only.edges, vec![e(g.likes, g.a, g.d)]);
338
339 let both = neighborhood(&v, g.b, 1, Some(&[g.knows]), Dir::Both);
340 assert_eq!(both.nodes, vec![(g.d, 1), (g.a, 1)]);
341 assert_eq!(both.edges, vec![e(g.knows, g.a, g.b), e(g.knows, g.b, g.d)]);
342 }
343
344 #[test]
345 fn neighborhood_is_deterministic() {
346 let g = diamond();
347 let v = g.fx.view();
348 let x = neighborhood(&v, g.a, 2, None, Dir::Both);
349 let y = neighborhood(&v, g.a, 2, None, Dir::Both);
350 assert_eq!(x, y);
351 assert_eq!(
352 expand(&v, g.a, None, Dir::Both),
353 expand(&v, g.a, None, Dir::Both)
354 );
355 }
356}