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 let nbr = if e.src == id { e.dst } else { e.src };
91 if !view.visible(nbr) {
97 continue;
98 }
99 edges.insert(e);
100 if visited.insert(nbr) {
101 let nd = d + 1;
102 nodes.push((nbr, nd));
103 queue.push_back((nbr, nd));
104 }
105 }
106 }
107
108 Neighborhood {
109 nodes,
110 edges: edges.into_iter().collect(),
111 }
112}
113
114fn push_unique(out: &mut Vec<EdgeRef>, seen: &mut BTreeSet<EdgeRef>, e: EdgeRef) {
115 if seen.insert(e) {
116 out.push(e);
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::{expand, neighborhood, Dir, EdgeRef};
123 use crate::view::GraphView;
124 use core_storage::v8::seam::{ColumnsView, EdgePropsView, TopologyView};
125 use core_storage::{ColumnStore, EdgeProps, IdMap, Interner, Topology};
126
127 struct Fx {
128 ids: IdMap,
129 syms: Interner,
130 labels: Vec<u32>,
131 props: ColumnStore,
132 topo: Topology,
133 eprops: EdgeProps,
134 }
135
136 impl Fx {
137 fn new() -> Self {
138 Fx {
139 ids: IdMap::new(),
140 syms: Interner::new(),
141 labels: vec![],
142 props: ColumnStore::new(),
143 topo: Topology::new(),
144 eprops: EdgeProps::new(),
145 }
146 }
147
148 fn add(&mut self, label: &str, key: &str) -> u32 {
149 let id = self.ids.get_or_insert(key);
150 let sym = self.syms.intern(label);
151 self.labels.resize(id as usize + 1, u32::MAX);
152 self.labels[id as usize] = sym;
153 id
154 }
155
156 fn view(&self) -> GraphView<'_> {
157 GraphView {
158 ids: &self.ids,
159 syms: &self.syms,
160 labels: &self.labels,
161 props: ColumnsView::owned(&self.props),
162 topo: TopologyView::owned(&self.topo),
163 edge_props: EdgePropsView::owned(&self.eprops),
164 mask: None,
165 }
166 }
167 }
168
169 struct Diamond {
174 fx: Fx,
175 a: u32,
176 b: u32,
177 c: u32,
178 d: u32,
179 knows: u32,
180 likes: u32,
181 }
182
183 fn diamond() -> Diamond {
184 let mut fx = Fx::new();
185 let a = fx.add("Person", "a");
186 let b = fx.add("Person", "b");
187 let c = fx.add("Person", "c");
188 let d = fx.add("Person", "d");
189 let knows = fx.syms.intern("KNOWS");
190 let likes = fx.syms.intern("LIKES");
191 fx.topo.add_edge(knows, a, b);
192 fx.topo.add_edge(knows, a, c);
193 fx.topo.add_edge(knows, b, d);
194 fx.topo.add_edge(knows, c, d);
195 fx.topo.add_edge(likes, a, d);
196 Diamond {
197 fx,
198 a,
199 b,
200 c,
201 d,
202 knows,
203 likes,
204 }
205 }
206
207 fn e(etype: u32, src: u32, dst: u32) -> EdgeRef {
208 EdgeRef { etype, src, dst }
209 }
210
211 #[test]
212 fn expand_etype_then_neighbor_order_and_both_is_out_then_in() {
213 let g = diamond();
214 let v = g.fx.view();
215 assert_eq!(
216 expand(&v, g.a, None, 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!(
225 expand(&v, g.a, Some(&[g.likes, g.knows]), Dir::Out),
226 vec![
227 e(g.knows, g.a, g.b),
228 e(g.knows, g.a, g.c),
229 e(g.likes, g.a, g.d)
230 ]
231 );
232 assert_eq!(
233 expand(&v, g.d, Some(&[g.knows]), Dir::In),
234 vec![e(g.knows, g.b, g.d), e(g.knows, g.c, g.d)]
235 );
236 assert_eq!(
237 expand(&v, g.b, Some(&[g.knows]), Dir::Both),
238 vec![e(g.knows, g.b, g.d), e(g.knows, g.a, g.b)]
239 );
240 assert!(expand(&v, g.a, Some(&[g.knows]), Dir::Out).contains(&e(g.knows, g.a, g.b)));
242 assert_eq!(
243 expand(&v, g.b, Some(&[g.knows]), Dir::In),
244 vec![e(g.knows, g.a, g.b)]
245 );
246 }
247
248 #[test]
249 fn expand_none_uses_topo_etypes_not_interned_symbols() {
250 let mut fx = Fx::new();
251 let a = fx.add("Person", "a");
253 let b = fx.add("Person", "b");
254 let knows = fx.syms.intern("KNOWS");
255 let _age = fx.syms.intern("age");
256 let _company = fx.syms.intern("Company");
257 let likes = fx.syms.intern("LIKES");
258 fx.topo.add_edge(knows, a, b);
259 fx.topo.add_edge(likes, a, b);
260 assert!(fx.syms.get("Person").unwrap() < knows);
261 assert!(knows < fx.syms.get("age").unwrap());
262 assert!(fx.syms.get("age").unwrap() < likes);
263 let v = fx.view();
264 assert_eq!(
265 expand(&v, a, None, Dir::Out),
266 vec![e(knows, a, b), e(likes, a, b)]
267 );
268 }
269
270 #[test]
271 fn expand_dedupes_only_identical_triples() {
272 let mut g = diamond();
273 g.fx.topo.add_edge(g.knows, g.a, g.a); let v = g.fx.view();
275 let both = expand(&v, g.a, Some(&[g.knows, g.knows]), Dir::Both);
276 let self_loop_hits = both.iter().filter(|x| *x == &e(g.knows, g.a, g.a)).count();
277 assert_eq!(self_loop_hits, 1);
278 assert!(both.contains(&e(g.knows, g.a, g.b)));
279 assert!(both.contains(&e(g.knows, g.a, g.c)));
280 }
281
282 #[test]
283 fn neighborhood_depth0_empty() {
284 let g = diamond();
285 let v = g.fx.view();
286 let n = neighborhood(&v, g.a, 0, None, Dir::Out);
287 assert!(n.nodes.is_empty());
288 assert!(n.edges.is_empty());
289 }
290
291 #[test]
292 fn neighborhood_depth1_vs_depth2_and_first_seen() {
293 let g = diamond();
294 let v = g.fx.view();
295
296 let d1 = neighborhood(&v, g.a, 1, Some(&[g.knows]), Dir::Out);
297 assert_eq!(d1.nodes, vec![(g.b, 1), (g.c, 1)]);
298 assert_eq!(d1.edges, vec![e(g.knows, g.a, g.b), e(g.knows, g.a, g.c)]);
299
300 let d2 = neighborhood(&v, g.a, 2, Some(&[g.knows]), Dir::Out);
301 assert_eq!(d2.nodes, vec![(g.b, 1), (g.c, 1), (g.d, 2)]);
302 assert_eq!(
304 d2.edges,
305 vec![
306 e(g.knows, g.a, g.b),
307 e(g.knows, g.a, g.c),
308 e(g.knows, g.b, g.d),
309 e(g.knows, g.c, g.d),
310 ]
311 );
312
313 let all = neighborhood(&v, g.a, 2, None, Dir::Out);
315 assert_eq!(all.nodes, vec![(g.b, 1), (g.c, 1), (g.d, 1)]);
316 assert_eq!(
317 all.edges,
318 vec![
319 e(g.knows, g.a, g.b),
320 e(g.knows, g.a, g.c),
321 e(g.knows, g.b, g.d),
322 e(g.knows, g.c, g.d),
323 e(g.likes, g.a, g.d),
324 ]
325 );
326 }
327
328 #[test]
329 fn neighborhood_dir_in_out_both_and_etype_filter() {
330 let g = diamond();
331 let v = g.fx.view();
332
333 let inn = neighborhood(&v, g.d, 1, None, Dir::In);
334 assert_eq!(inn.nodes, vec![(g.b, 1), (g.c, 1), (g.a, 1)]);
335 assert_eq!(
336 inn.edges,
337 vec![
338 e(g.knows, g.b, g.d),
339 e(g.knows, g.c, g.d),
340 e(g.likes, g.a, g.d),
341 ]
342 );
343
344 let likes_only = neighborhood(&v, g.a, 2, Some(&[g.likes]), Dir::Out);
345 assert_eq!(likes_only.nodes, vec![(g.d, 1)]);
346 assert_eq!(likes_only.edges, vec![e(g.likes, g.a, g.d)]);
347
348 let both = neighborhood(&v, g.b, 1, Some(&[g.knows]), Dir::Both);
349 assert_eq!(both.nodes, vec![(g.d, 1), (g.a, 1)]);
350 assert_eq!(both.edges, vec![e(g.knows, g.a, g.b), e(g.knows, g.b, g.d)]);
351 }
352
353 #[test]
354 fn neighborhood_is_deterministic() {
355 let g = diamond();
356 let v = g.fx.view();
357 let x = neighborhood(&v, g.a, 2, None, Dir::Both);
358 let y = neighborhood(&v, g.a, 2, None, Dir::Both);
359 assert_eq!(x, y);
360 assert_eq!(
361 expand(&v, g.a, None, Dir::Both),
362 expand(&v, g.a, None, Dir::Both)
363 );
364 }
365}