1use core_storage::v8::seam::{ColumnsView, EdgePropsView, TopologyView, ValueRef};
2use core_storage::{IdMap, Interner};
3use std::collections::HashSet;
4
5pub struct GraphView<'a> {
7 pub ids: &'a IdMap,
8 pub syms: &'a Interner,
9 pub labels: &'a [u32],
10 pub props: ColumnsView<'a>,
14 pub topo: TopologyView<'a>,
19 pub edge_props: EdgePropsView<'a>,
23 pub mask: Option<&'a HashSet<u32>>,
26}
27
28impl<'a> GraphView<'a> {
29 #[inline]
32 pub fn visible(&self, id: u32) -> bool {
33 self.mask.is_none_or(|m| m.contains(&id))
34 }
35
36 pub fn node_id(&self, key: &str) -> Option<u32> {
37 self.ids.get(key)
38 }
39
40 pub fn key_of(&self, id: u32) -> &str {
41 self.ids.key_of(id).expect("dense ids")
42 }
43
44 pub fn label_of(&self, id: u32) -> Option<&str> {
45 let sym = *self.labels.get(id as usize)?;
46 if sym == u32::MAX {
47 return None;
48 }
49 self.syms.resolve(sym)
50 }
51
52 pub fn nodes_with_label(&self, label: &str) -> Vec<u32> {
53 let Some(sym) = self.syms.get(label) else {
54 return Vec::new();
55 };
56 self.labels
57 .iter()
58 .enumerate()
59 .filter_map(|(i, &s)| if s == sym { Some(i as u32) } else { None })
60 .collect()
61 }
62
63 pub fn prop(&self, id: u32, field: &str) -> Option<ValueRef<'_>> {
70 self.props.get(id, field)
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::GraphView;
77 use core_storage::v8::seam::{ColumnsView, EdgePropsView, TopologyView};
78 use core_storage::{ColumnStore, EdgeProps, IdMap, Interner, Topology, Value};
79
80 struct Fx {
81 ids: IdMap,
82 syms: Interner,
83 labels: Vec<u32>,
84 props: ColumnStore,
85 topo: Topology,
86 eprops: EdgeProps,
87 }
88
89 impl Fx {
90 fn new() -> Self {
91 Fx {
92 ids: IdMap::new(),
93 syms: Interner::new(),
94 labels: vec![],
95 props: ColumnStore::new(),
96 topo: Topology::new(),
97 eprops: EdgeProps::new(),
98 }
99 }
100
101 fn add(&mut self, label: &str, key: &str, props: Vec<(&str, Value)>) -> u32 {
102 let id = self.ids.get_or_insert(key);
103 let sym = self.syms.intern(label);
104 self.labels.resize(id as usize + 1, u32::MAX);
105 self.labels[id as usize] = sym;
106 for (f, v) in props {
107 self.props.set(id, f, v);
108 }
109 id
110 }
111
112 fn view(&self) -> GraphView<'_> {
113 GraphView {
114 ids: &self.ids,
115 syms: &self.syms,
116 labels: &self.labels,
117 props: ColumnsView::owned(&self.props),
118 topo: TopologyView::owned(&self.topo),
119 edge_props: EdgePropsView::owned(&self.eprops),
120 mask: None,
121 }
122 }
123 }
124
125 #[test]
126 fn prop_returns_none_for_missing() {
127 let mut fx = Fx::new();
128 let id = fx.add("N", "alice", vec![("age", Value::Int(36))]);
129 let v = fx.view();
130 assert_eq!(
131 v.prop(id, "age").map(|vr| vr.into_value()),
132 Some(Value::Int(36))
133 );
134 assert!(v.prop(id, "missing").is_none());
135 }
136
137 #[test]
138 fn graph_view_lookups() {
139 let mut fx = Fx::new();
140 let id = fx.add("Person", "ada", vec![("age", Value::Int(36))]);
141 let v = fx.view();
142 assert_eq!(v.node_id("ada"), Some(id));
143 assert_eq!(v.node_id("zzz"), None);
144 assert_eq!(v.key_of(id), "ada");
145 assert_eq!(v.label_of(id), Some("Person"));
146 assert_eq!(v.label_of(99), None);
147 assert_eq!(
148 v.prop(id, "age").map(|vr| vr.into_value()),
149 Some(Value::Int(36))
150 );
151 assert_eq!(v.prop(id, "missing"), None);
152 }
153
154 #[test]
155 fn gap_sentinel_is_not_a_label() {
156 let mut fx = Fx::new();
157 let kept = fx.add("Person", "ada", vec![]);
158 fx.ids.get_or_insert("ghost");
159 fx.labels.resize(2, u32::MAX);
160 let later = fx.add("Person", "bob", vec![]);
161 let v = fx.view();
162 assert_eq!(v.label_of(1), None);
163 assert_eq!(v.nodes_with_label("Person"), vec![kept, later]);
164 }
165
166 #[test]
167 fn nodes_with_label_skips_tombstoned_id() {
168 let mut fx = Fx::new();
169 let ada = fx.add("Person", "ada", vec![]);
170 let bob = fx.add("Person", "bob", vec![]);
171 fx.ids.delete("ada");
172 fx.labels[ada as usize] = u32::MAX;
173 let v = fx.view();
174 assert_eq!(v.node_id("ada"), None);
175 assert_eq!(v.label_of(ada), None);
176 assert_eq!(v.nodes_with_label("Person"), vec![bob]);
177 }
178
179 #[test]
180 fn nodes_with_label_dense_id_order_and_unknown_empty() {
181 let mut fx = Fx::new();
182 let bob = fx.add("Person", "bob", vec![]);
183 let ada = fx.add("Person", "ada", vec![]);
184 let _acme = fx.add("Company", "acme", vec![]);
185 let v = fx.view();
186 assert_eq!(v.nodes_with_label("Person"), vec![bob, ada]);
187 assert_eq!(v.nodes_with_label("Person"), vec![0, 1]);
188 assert!(v.nodes_with_label("Nope").is_empty());
189 }
190}