1use core_storage::{ColumnStore, EdgeProps, IdMap, Interner, Topology, Value};
2use std::collections::HashSet;
3
4pub struct GraphView<'a> {
6 pub ids: &'a IdMap,
7 pub syms: &'a Interner,
8 pub labels: &'a [u32],
9 pub props: &'a ColumnStore,
10 pub topo: &'a Topology,
11 pub edge_props: &'a EdgeProps,
12 pub mask: Option<&'a HashSet<u32>>,
15}
16
17impl<'a> GraphView<'a> {
18 #[inline]
21 pub fn visible(&self, id: u32) -> bool {
22 self.mask.is_none_or(|m| m.contains(&id))
23 }
24
25 pub fn node_id(&self, key: &str) -> Option<u32> {
26 self.ids.get(key)
27 }
28
29 pub fn key_of(&self, id: u32) -> &str {
30 self.ids.key_of(id).expect("dense ids")
31 }
32
33 pub fn label_of(&self, id: u32) -> Option<&str> {
34 let sym = *self.labels.get(id as usize)?;
35 if sym == u32::MAX {
36 return None;
37 }
38 self.syms.resolve(sym)
39 }
40
41 pub fn nodes_with_label(&self, label: &str) -> Vec<u32> {
42 let Some(sym) = self.syms.get(label) else {
43 return Vec::new();
44 };
45 self.labels
46 .iter()
47 .enumerate()
48 .filter_map(|(i, &s)| if s == sym { Some(i as u32) } else { None })
49 .collect()
50 }
51
52 pub fn prop(&self, id: u32, field: &str) -> Option<&Value> {
53 self.props.get(id, field)
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::GraphView;
60 use core_storage::{ColumnStore, EdgeProps, IdMap, Interner, Topology, Value};
61
62 struct Fx {
63 ids: IdMap,
64 syms: Interner,
65 labels: Vec<u32>,
66 props: ColumnStore,
67 topo: Topology,
68 eprops: EdgeProps,
69 }
70
71 impl Fx {
72 fn new() -> Self {
73 Fx {
74 ids: IdMap::new(),
75 syms: Interner::new(),
76 labels: vec![],
77 props: ColumnStore::new(),
78 topo: Topology::new(),
79 eprops: EdgeProps::new(),
80 }
81 }
82
83 fn add(&mut self, label: &str, key: &str, props: Vec<(&str, Value)>) -> u32 {
84 let id = self.ids.get_or_insert(key);
85 let sym = self.syms.intern(label);
86 self.labels.resize(id as usize + 1, u32::MAX);
87 self.labels[id as usize] = sym;
88 for (f, v) in props {
89 self.props.set(id, f, v);
90 }
91 id
92 }
93
94 fn view(&self) -> GraphView<'_> {
95 GraphView {
96 ids: &self.ids,
97 syms: &self.syms,
98 labels: &self.labels,
99 props: &self.props,
100 topo: &self.topo,
101 edge_props: &self.eprops,
102 mask: None,
103 }
104 }
105 }
106
107 #[test]
108 fn nodes_with_label_dense_id_order_and_unknown_empty() {
109 let mut fx = Fx::new();
110 let bob = fx.add("Person", "bob", vec![]);
111 let ada = fx.add("Person", "ada", vec![]);
112 let _acme = fx.add("Company", "acme", vec![]);
113 let v = fx.view();
114 assert_eq!(v.nodes_with_label("Person"), vec![bob, ada]);
115 assert_eq!(v.nodes_with_label("Person"), vec![0, 1]);
116 assert!(v.nodes_with_label("Nope").is_empty());
117 }
118
119 #[test]
120 fn graph_view_lookups() {
121 let mut fx = Fx::new();
122 let id = fx.add("Person", "ada", vec![("age", Value::Int(36))]);
123 let v = fx.view();
124 assert_eq!(v.node_id("ada"), Some(id));
125 assert_eq!(v.node_id("zzz"), None);
126 assert_eq!(v.key_of(id), "ada");
127 assert_eq!(v.label_of(id), Some("Person"));
128 assert_eq!(v.label_of(99), None);
129 assert_eq!(v.prop(id, "age"), Some(&Value::Int(36)));
130 assert_eq!(v.prop(id, "missing"), None);
131 }
132
133 #[test]
134 fn gap_sentinel_is_not_a_label() {
135 let mut fx = Fx::new();
136 let kept = fx.add("Person", "ada", vec![]);
137 fx.ids.get_or_insert("ghost");
139 fx.labels.resize(2, u32::MAX);
140 let later = fx.add("Person", "bob", vec![]);
141 let v = fx.view();
142 assert_eq!(v.label_of(1), None);
143 assert_eq!(v.nodes_with_label("Person"), vec![kept, later]);
144 }
145
146 #[test]
147 fn nodes_with_label_skips_tombstoned_id() {
148 let mut fx = Fx::new();
149 let ada = fx.add("Person", "ada", vec![]);
150 let bob = fx.add("Person", "bob", vec![]);
151 fx.ids.delete("ada");
152 fx.labels[ada as usize] = u32::MAX;
153 let v = fx.view();
154 assert_eq!(v.node_id("ada"), None);
155 assert_eq!(v.label_of(ada), None);
156 assert_eq!(v.nodes_with_label("Person"), vec![bob]);
157 }
158}