Skip to main content

core_query/
view.rs

1use core_storage::{ColumnStore, EdgeProps, IdMap, Interner, Topology, Value};
2use std::collections::HashSet;
3
4/// Read-only twin of `GraphMut`. Holds only borrowed graph state.
5pub 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    /// Optional query-scoped node visibility set. `None` = all nodes visible.
13    /// When `Some(set)`, only dense ids present in `set` are accessible.
14    pub mask: Option<&'a HashSet<u32>>,
15}
16
17impl<'a> GraphView<'a> {
18    /// Returns `true` if `id` is visible under the current mask.
19    /// Always `true` when no mask is set.
20    #[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        // Simulate a dense-id hole: next slot exists but holds the gap sentinel.
138        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}