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