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