1use core_storage::property_index::PropertyIndex;
2use core_storage::v8::seam::{ColumnsView, EdgePropsView, TopologyView, ValueRef};
3use core_storage::{IdMap, Interner, Value};
4use std::collections::HashSet;
5
6pub struct GraphView<'a> {
8 pub ids: &'a IdMap,
9 pub syms: &'a Interner,
10 pub labels: &'a [u32],
11 pub props: ColumnsView<'a>,
15 pub topo: TopologyView<'a>,
20 pub edge_props: EdgePropsView<'a>,
24 pub mask: Option<&'a HashSet<u32>>,
27 pub prop_index: Option<&'a PropertyIndex>,
31}
32
33impl<'a> GraphView<'a> {
34 #[inline]
37 pub fn visible(&self, id: u32) -> bool {
38 self.mask.is_none_or(|m| m.contains(&id))
39 }
40
41 pub fn node_id(&self, key: &str) -> Option<u32> {
42 self.ids.get(key)
43 }
44
45 pub fn key_of(&self, id: u32) -> &str {
46 self.ids.key_of(id).expect("dense ids")
47 }
48
49 pub fn label_of(&self, id: u32) -> Option<&str> {
50 let sym = *self.labels.get(id as usize)?;
51 if sym == u32::MAX {
52 return None;
53 }
54 self.syms.resolve(sym)
55 }
56
57 pub fn nodes_with_prop(&self, label: &str, field: &str, value: &Value) -> Option<Vec<u32>> {
62 let index = self.prop_index?;
63 if !index.is_enabled(label, field) {
64 return None;
65 }
66 let ids = index
67 .lookup(label, field, value)
68 .into_iter()
69 .filter(|&id| self.visible(id))
70 .collect();
71 Some(ids)
72 }
73
74 pub fn nodes_with_label(&self, label: &str) -> Vec<u32> {
75 let Some(sym) = self.syms.get(label) else {
76 return Vec::new();
77 };
78 self.labels
79 .iter()
80 .enumerate()
81 .filter_map(|(i, &s)| if s == sym { Some(i as u32) } else { None })
82 .collect()
83 }
84
85 pub fn nodes_all(&self) -> Vec<u32> {
90 self.labels
91 .iter()
92 .enumerate()
93 .filter_map(|(i, &s)| {
94 if s != u32::MAX && self.visible(i as u32) {
95 Some(i as u32)
96 } else {
97 None
98 }
99 })
100 .collect()
101 }
102
103 pub fn prop(&self, id: u32, field: &str) -> Option<ValueRef<'_>> {
110 self.props.get(id, field)
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::GraphView;
117 use core_storage::v8::seam::{ColumnsView, EdgePropsView, TopologyView};
118 use core_storage::{ColumnStore, EdgeProps, IdMap, Interner, Topology, Value};
119
120 struct Fx {
121 ids: IdMap,
122 syms: Interner,
123 labels: Vec<u32>,
124 props: ColumnStore,
125 topo: Topology,
126 eprops: EdgeProps,
127 }
128
129 impl Fx {
130 fn new() -> Self {
131 Fx {
132 ids: IdMap::new(),
133 syms: Interner::new(),
134 labels: vec![],
135 props: ColumnStore::new(),
136 topo: Topology::new(),
137 eprops: EdgeProps::new(),
138 }
139 }
140
141 fn add(&mut self, label: &str, key: &str, props: Vec<(&str, Value)>) -> u32 {
142 let id = self.ids.get_or_insert(key);
143 let sym = self.syms.intern(label);
144 self.labels.resize(id as usize + 1, u32::MAX);
145 self.labels[id as usize] = sym;
146 for (f, v) in props {
147 self.props.set(id, f, v);
148 }
149 id
150 }
151
152 fn view(&self) -> GraphView<'_> {
153 GraphView {
154 ids: &self.ids,
155 syms: &self.syms,
156 labels: &self.labels,
157 props: ColumnsView::owned(&self.props),
158 topo: TopologyView::owned(&self.topo),
159 edge_props: EdgePropsView::owned(&self.eprops),
160 mask: None,
161 prop_index: None,
162 }
163 }
164 }
165
166 #[test]
167 fn prop_returns_none_for_missing() {
168 let mut fx = Fx::new();
169 let id = fx.add("N", "alice", vec![("age", Value::Int(36))]);
170 let v = fx.view();
171 assert_eq!(
172 v.prop(id, "age").map(|vr| vr.into_value()),
173 Some(Value::Int(36))
174 );
175 assert!(v.prop(id, "missing").is_none());
176 }
177
178 #[test]
179 fn graph_view_lookups() {
180 let mut fx = Fx::new();
181 let id = fx.add("Person", "ada", vec![("age", Value::Int(36))]);
182 let v = fx.view();
183 assert_eq!(v.node_id("ada"), Some(id));
184 assert_eq!(v.node_id("zzz"), None);
185 assert_eq!(v.key_of(id), "ada");
186 assert_eq!(v.label_of(id), Some("Person"));
187 assert_eq!(v.label_of(99), None);
188 assert_eq!(
189 v.prop(id, "age").map(|vr| vr.into_value()),
190 Some(Value::Int(36))
191 );
192 assert_eq!(v.prop(id, "missing"), None);
193 }
194
195 #[test]
196 fn gap_sentinel_is_not_a_label() {
197 let mut fx = Fx::new();
198 let kept = fx.add("Person", "ada", vec![]);
199 fx.ids.get_or_insert("ghost");
200 fx.labels.resize(2, u32::MAX);
201 let later = fx.add("Person", "bob", vec![]);
202 let v = fx.view();
203 assert_eq!(v.label_of(1), None);
204 assert_eq!(v.nodes_with_label("Person"), vec![kept, later]);
205 }
206
207 #[test]
208 fn nodes_with_label_skips_tombstoned_id() {
209 let mut fx = Fx::new();
210 let ada = fx.add("Person", "ada", vec![]);
211 let bob = fx.add("Person", "bob", vec![]);
212 fx.ids.delete("ada");
213 fx.labels[ada as usize] = u32::MAX;
214 let v = fx.view();
215 assert_eq!(v.node_id("ada"), None);
216 assert_eq!(v.label_of(ada), None);
217 assert_eq!(v.nodes_with_label("Person"), vec![bob]);
218 }
219
220 #[test]
221 fn nodes_with_label_dense_id_order_and_unknown_empty() {
222 let mut fx = Fx::new();
223 let bob = fx.add("Person", "bob", vec![]);
224 let ada = fx.add("Person", "ada", vec![]);
225 let _acme = fx.add("Company", "acme", vec![]);
226 let v = fx.view();
227 assert_eq!(v.nodes_with_label("Person"), vec![bob, ada]);
228 assert_eq!(v.nodes_with_label("Person"), vec![0, 1]);
229 assert!(v.nodes_with_label("Nope").is_empty());
230 }
231
232 #[test]
233 fn nodes_all_respects_mask() {
234 use std::collections::HashSet;
235 let mut fx = Fx::new();
236 let alice = fx.add("Person", "alice", vec![]);
237 let bob = fx.add("Person", "bob", vec![]);
238 let carol = fx.add("Person", "carol", vec![]);
239
240 let v_full = fx.view();
242 let all = v_full.nodes_all();
243 assert!(all.contains(&alice));
244 assert!(all.contains(&bob));
245 assert!(all.contains(&carol));
246
247 let visible: HashSet<u32> = [alice, carol].into_iter().collect();
249 let v_masked = GraphView {
250 ids: &fx.ids,
251 syms: &fx.syms,
252 labels: &fx.labels,
253 props: ColumnsView::owned(&fx.props),
254 topo: TopologyView::owned(&fx.topo),
255 edge_props: EdgePropsView::owned(&fx.eprops),
256 mask: Some(&visible),
257 prop_index: None,
258 };
259 let masked = v_masked.nodes_all();
260 assert!(masked.contains(&alice));
261 assert!(!masked.contains(&bob), "masked node must be excluded");
262 assert!(masked.contains(&carol));
263 }
264}