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 prop_index: None,
166 layer: None,
167 }
168 }
169 }
170
171 struct Diamond {
176 fx: Fx,
177 a: u32,
178 b: u32,
179 c: u32,
180 d: u32,
181 knows: u32,
182 likes: u32,
183 }
184
185 fn diamond() -> Diamond {
186 let mut fx = Fx::new();
187 let a = fx.add("Person", "a");
188 let b = fx.add("Person", "b");
189 let c = fx.add("Person", "c");
190 let d = fx.add("Person", "d");
191 let knows = fx.syms.intern("KNOWS");
192 let likes = fx.syms.intern("LIKES");
193 fx.topo.add_edge(knows, a, b);
194 fx.topo.add_edge(knows, a, c);
195 fx.topo.add_edge(knows, b, d);
196 fx.topo.add_edge(knows, c, d);
197 fx.topo.add_edge(likes, a, d);
198 Diamond {
199 fx,
200 a,
201 b,
202 c,
203 d,
204 knows,
205 likes,
206 }
207 }
208
209 fn e(etype: u32, src: u32, dst: u32) -> EdgeRef {
210 EdgeRef { etype, src, dst }
211 }
212
213 #[test]
214 fn expand_etype_then_neighbor_order_and_both_is_out_then_in() {
215 let g = diamond();
216 let v = g.fx.view();
217 assert_eq!(
218 expand(&v, g.a, None, Dir::Out),
219 vec![
220 e(g.knows, g.a, g.b),
221 e(g.knows, g.a, g.c),
222 e(g.likes, g.a, g.d)
223 ]
224 );
225 assert_eq!(
227 expand(&v, g.a, Some(&[g.likes, g.knows]), Dir::Out),
228 vec![
229 e(g.knows, g.a, g.b),
230 e(g.knows, g.a, g.c),
231 e(g.likes, g.a, g.d)
232 ]
233 );
234 assert_eq!(
235 expand(&v, g.d, Some(&[g.knows]), Dir::In),
236 vec![e(g.knows, g.b, g.d), e(g.knows, g.c, g.d)]
237 );
238 assert_eq!(
239 expand(&v, g.b, Some(&[g.knows]), Dir::Both),
240 vec![e(g.knows, g.b, g.d), e(g.knows, g.a, g.b)]
241 );
242 assert!(expand(&v, g.a, Some(&[g.knows]), Dir::Out).contains(&e(g.knows, g.a, g.b)));
244 assert_eq!(
245 expand(&v, g.b, Some(&[g.knows]), Dir::In),
246 vec![e(g.knows, g.a, g.b)]
247 );
248 }
249
250 #[test]
251 fn expand_none_uses_topo_etypes_not_interned_symbols() {
252 let mut fx = Fx::new();
253 let a = fx.add("Person", "a");
255 let b = fx.add("Person", "b");
256 let knows = fx.syms.intern("KNOWS");
257 let _age = fx.syms.intern("age");
258 let _company = fx.syms.intern("Company");
259 let likes = fx.syms.intern("LIKES");
260 fx.topo.add_edge(knows, a, b);
261 fx.topo.add_edge(likes, a, b);
262 assert!(fx.syms.get("Person").unwrap() < knows);
263 assert!(knows < fx.syms.get("age").unwrap());
264 assert!(fx.syms.get("age").unwrap() < likes);
265 let v = fx.view();
266 assert_eq!(
267 expand(&v, a, None, Dir::Out),
268 vec![e(knows, a, b), e(likes, a, b)]
269 );
270 }
271
272 #[test]
273 fn expand_dedupes_only_identical_triples() {
274 let mut g = diamond();
275 g.fx.topo.add_edge(g.knows, g.a, g.a); let v = g.fx.view();
277 let both = expand(&v, g.a, Some(&[g.knows, g.knows]), Dir::Both);
278 let self_loop_hits = both.iter().filter(|x| *x == &e(g.knows, g.a, g.a)).count();
279 assert_eq!(self_loop_hits, 1);
280 assert!(both.contains(&e(g.knows, g.a, g.b)));
281 assert!(both.contains(&e(g.knows, g.a, g.c)));
282 }
283
284 #[test]
285 fn neighborhood_depth0_empty() {
286 let g = diamond();
287 let v = g.fx.view();
288 let n = neighborhood(&v, g.a, 0, None, Dir::Out);
289 assert!(n.nodes.is_empty());
290 assert!(n.edges.is_empty());
291 }
292
293 #[test]
294 fn neighborhood_depth1_vs_depth2_and_first_seen() {
295 let g = diamond();
296 let v = g.fx.view();
297
298 let d1 = neighborhood(&v, g.a, 1, Some(&[g.knows]), Dir::Out);
299 assert_eq!(d1.nodes, vec![(g.b, 1), (g.c, 1)]);
300 assert_eq!(d1.edges, vec![e(g.knows, g.a, g.b), e(g.knows, g.a, g.c)]);
301
302 let d2 = neighborhood(&v, g.a, 2, Some(&[g.knows]), Dir::Out);
303 assert_eq!(d2.nodes, vec![(g.b, 1), (g.c, 1), (g.d, 2)]);
304 assert_eq!(
306 d2.edges,
307 vec![
308 e(g.knows, g.a, g.b),
309 e(g.knows, g.a, g.c),
310 e(g.knows, g.b, g.d),
311 e(g.knows, g.c, g.d),
312 ]
313 );
314
315 let all = neighborhood(&v, g.a, 2, None, Dir::Out);
317 assert_eq!(all.nodes, vec![(g.b, 1), (g.c, 1), (g.d, 1)]);
318 assert_eq!(
319 all.edges,
320 vec![
321 e(g.knows, g.a, g.b),
322 e(g.knows, g.a, g.c),
323 e(g.knows, g.b, g.d),
324 e(g.knows, g.c, g.d),
325 e(g.likes, g.a, g.d),
326 ]
327 );
328 }
329
330 #[test]
331 fn neighborhood_dir_in_out_both_and_etype_filter() {
332 let g = diamond();
333 let v = g.fx.view();
334
335 let inn = neighborhood(&v, g.d, 1, None, Dir::In);
336 assert_eq!(inn.nodes, vec![(g.b, 1), (g.c, 1), (g.a, 1)]);
337 assert_eq!(
338 inn.edges,
339 vec![
340 e(g.knows, g.b, g.d),
341 e(g.knows, g.c, g.d),
342 e(g.likes, g.a, g.d),
343 ]
344 );
345
346 let likes_only = neighborhood(&v, g.a, 2, Some(&[g.likes]), Dir::Out);
347 assert_eq!(likes_only.nodes, vec![(g.d, 1)]);
348 assert_eq!(likes_only.edges, vec![e(g.likes, g.a, g.d)]);
349
350 let both = neighborhood(&v, g.b, 1, Some(&[g.knows]), Dir::Both);
351 assert_eq!(both.nodes, vec![(g.d, 1), (g.a, 1)]);
352 assert_eq!(both.edges, vec![e(g.knows, g.a, g.b), e(g.knows, g.b, g.d)]);
353 }
354
355 #[test]
356 fn neighborhood_is_deterministic() {
357 let g = diamond();
358 let v = g.fx.view();
359 let x = neighborhood(&v, g.a, 2, None, Dir::Both);
360 let y = neighborhood(&v, g.a, 2, None, Dir::Both);
361 assert_eq!(x, y);
362 assert_eq!(
363 expand(&v, g.a, None, Dir::Both),
364 expand(&v, g.a, None, Dir::Both)
365 );
366 }
367}