1use std::collections::{HashMap, HashSet};
2
3use core_storage::property_index::{PropertyIndex, UnifiedIndex};
4use core_storage::v8::seam::{ColumnsView, EdgePropsView, TopologyView, ValueRef};
5use core_storage::{IdMap, Interner, Value};
6
7use crate::visible::VisibleSet;
8
9pub struct GraphView<'a> {
19 pub ids: &'a IdMap,
20 pub syms: &'a Interner,
21 pub labels: &'a [u32],
22 pub props: ColumnsView<'a>,
26 pub topo: TopologyView<'a>,
31 pub edge_props: EdgePropsView<'a>,
35 pub mask: Option<&'a VisibleSet>,
41 pub prop_index: Option<&'a PropertyIndex>,
45 pub layer: Option<&'a NodeLayer>,
50}
51
52#[derive(Debug, Clone)]
61pub struct NodeLayer {
62 id_floor: u32,
64 keys: Vec<String>,
66 labels: Vec<u32>,
68 by_key: HashMap<String, u32>,
71 deleted: HashSet<u32>,
73 relabeled: HashMap<u32, u32>,
75 renamed: HashMap<u32, String>,
77 sym_floor: u32,
79 syms: Vec<String>,
81 by_sym: HashMap<String, u32>,
82}
83
84impl NodeLayer {
85 pub fn new(ids: &IdMap, syms: &Interner) -> Self {
88 Self {
89 id_floor: ids.len() as u32,
90 keys: Vec::new(),
91 labels: Vec::new(),
92 by_key: HashMap::new(),
93 deleted: HashSet::new(),
94 relabeled: HashMap::new(),
95 renamed: HashMap::new(),
96 sym_floor: syms.len() as u32,
97 syms: Vec::new(),
98 by_sym: HashMap::new(),
99 }
100 }
101
102 pub fn intern(&mut self, below: &Interner, name: &str) -> u32 {
105 if let Some(sym) = below.get(name) {
106 return sym;
107 }
108 if let Some(&sym) = self.by_sym.get(name) {
109 return sym;
110 }
111 let sym = self.sym_floor + self.syms.len() as u32;
112 self.syms.push(name.to_string());
113 self.by_sym.insert(name.to_string(), sym);
114 sym
115 }
116
117 pub fn insert(&mut self, key: &str, label_sym: u32) -> u32 {
121 let id = self.id_floor + self.keys.len() as u32;
122 self.keys.push(key.to_string());
123 self.labels.push(label_sym);
124 self.by_key.insert(key.to_string(), id);
125 id
126 }
127
128 pub fn relabel(&mut self, id: u32, label_sym: u32) {
130 match id.checked_sub(self.id_floor) {
131 Some(i) => self.labels[i as usize] = label_sym,
132 None => {
133 self.relabeled.insert(id, label_sym);
134 }
135 }
136 }
137
138 pub fn rename(&mut self, id: u32, old: &str, new: &str) {
141 if self.by_key.get(old) == Some(&id) {
142 self.by_key.remove(old);
143 }
144 match id.checked_sub(self.id_floor) {
145 Some(i) => self.keys[i as usize] = new.to_string(),
146 None => {
147 self.renamed.insert(id, new.to_string());
148 }
149 }
150 self.by_key.insert(new.to_string(), id);
151 }
152
153 pub fn delete(&mut self, key: &str, id: u32) {
155 if self.by_key.get(key) == Some(&id) {
156 self.by_key.remove(key);
157 }
158 self.deleted.insert(id);
159 }
160
161 #[inline]
162 fn is_deleted(&self, id: u32) -> bool {
163 !self.deleted.is_empty() && self.deleted.contains(&id)
164 }
165
166 #[inline]
168 fn below_label(&self, id: u32, below: u32) -> u32 {
169 if self.is_deleted(id) {
170 return u32::MAX;
171 }
172 if self.relabeled.is_empty() {
173 return below;
174 }
175 self.relabeled.get(&id).copied().unwrap_or(below)
176 }
177}
178
179pub struct LabelSyms<'a> {
182 below: std::slice::Iter<'a, u32>,
183 layer: Option<&'a NodeLayer>,
184 next: usize,
186}
187
188impl Iterator for LabelSyms<'_> {
189 type Item = u32;
190
191 #[inline]
192 fn next(&mut self) -> Option<u32> {
193 let i = self.next;
194 self.next += 1;
195 if let Some(&sym) = self.below.next() {
196 return Some(match self.layer {
197 None => sym,
198 Some(layer) => layer.below_label(i as u32, sym),
199 });
200 }
201 let layer = self.layer?;
202 let floor = layer.id_floor as usize;
203 if i < floor {
204 return Some(u32::MAX);
206 }
207 let sym = *layer.labels.get(i - floor)?;
208 Some(if layer.is_deleted(i as u32) {
209 u32::MAX
210 } else {
211 sym
212 })
213 }
214
215 fn size_hint(&self) -> (usize, Option<usize>) {
216 let below = self.below.len();
217 match self.layer {
218 None => (below, Some(below)),
219 Some(_) => (below, None),
220 }
221 }
222}
223
224impl<'a> GraphView<'a> {
225 #[inline]
228 pub fn visible(&self, id: u32) -> bool {
229 self.mask.is_none_or(|m| m.contains(id))
230 }
231
232 pub fn node_id(&self, key: &str) -> Option<u32> {
233 match self.layer {
234 None => self.ids.get(key),
235 Some(layer) => {
236 if let Some(&id) = layer.by_key.get(key) {
237 return Some(id);
238 }
239 self.ids
240 .get(key)
241 .filter(|id| !layer.is_deleted(*id) && !layer.renamed.contains_key(id))
242 }
243 }
244 }
245
246 pub fn key_of(&self, id: u32) -> &str {
247 self.try_key_of(id).expect("dense ids")
248 }
249
250 pub fn try_key_of(&self, id: u32) -> Option<&str> {
253 match self.layer {
254 None => self.ids.key_of(id),
255 Some(layer) => {
256 if layer.is_deleted(id) {
257 return None;
258 }
259 match id.checked_sub(layer.id_floor) {
260 Some(i) => layer.keys.get(i as usize).map(String::as_str),
261 None => match layer.renamed.get(&id) {
262 Some(key) => Some(key),
263 None => self.ids.key_of(id),
264 },
265 }
266 }
267 }
268 }
269
270 pub fn id_bound(&self) -> u32 {
272 match self.layer {
273 None => self.ids.len() as u32,
274 Some(layer) => layer.id_floor + layer.keys.len() as u32,
275 }
276 }
277
278 pub fn sym(&self, name: &str) -> Option<u32> {
280 self.syms
281 .get(name)
282 .or_else(|| self.layer.and_then(|l| l.by_sym.get(name).copied()))
283 }
284
285 pub fn sym_name(&self, sym: u32) -> Option<&str> {
287 match self
288 .layer
289 .and_then(|l| sym.checked_sub(l.sym_floor).map(|i| (l, i)))
290 {
291 Some((layer, i)) => layer.syms.get(i as usize).map(String::as_str),
292 None => self.syms.resolve(sym),
293 }
294 }
295
296 #[inline]
299 pub fn label_syms(&self) -> LabelSyms<'_> {
300 LabelSyms {
301 below: self.labels.iter(),
302 layer: self.layer,
303 next: 0,
304 }
305 }
306
307 pub fn label_sym(&self, id: u32) -> Option<u32> {
309 let sym = match self.layer {
310 None => *self.labels.get(id as usize)?,
311 Some(layer) => match id.checked_sub(layer.id_floor) {
312 Some(i) if !layer.is_deleted(id) => *layer.labels.get(i as usize)?,
313 Some(_) => return None,
314 None => layer.below_label(id, *self.labels.get(id as usize)?),
315 },
316 };
317 (sym != u32::MAX).then_some(sym)
318 }
319
320 pub fn label_of(&self, id: u32) -> Option<&str> {
321 self.sym_name(self.label_sym(id)?)
322 }
323
324 pub fn nodes_with_prop(&self, label: &str, field: &str, value: &Value) -> Option<Vec<u32>> {
329 let index = self.prop_index?;
330 if !index.is_enabled(label, field) || !UnifiedIndex::answers_eq(value) {
333 return None;
334 }
335 let ids = index
336 .lookup(label, field, value)
337 .into_iter()
338 .filter(|&id| self.visible(id))
339 .collect();
340 Some(ids)
341 }
342
343 pub fn range_index(&self, label: &str, field: &str) -> Option<&UnifiedIndex> {
350 let index = self.prop_index?;
351 let usable = self.layer.is_none()
352 && !index.is_layered()
353 && index.kind_of(label, field) == Some(core_storage::IndexKind::Range);
354 usable.then_some(index)
355 }
356
357 pub fn nodes_with_label(&self, label: &str) -> Vec<u32> {
358 let Some(sym) = self.sym(label) else {
359 return Vec::new();
360 };
361 let matching = |(i, s): (usize, u32)| if s == sym { Some(i as u32) } else { None };
362 match self.layer {
363 None => self
364 .labels
365 .iter()
366 .copied()
367 .enumerate()
368 .filter_map(matching)
369 .collect(),
370 Some(_) => self.label_syms().enumerate().filter_map(matching).collect(),
371 }
372 }
373
374 pub fn nodes_all(&self) -> Vec<u32> {
379 let live = |(i, s): (usize, u32)| {
380 if s != u32::MAX && self.visible(i as u32) {
381 Some(i as u32)
382 } else {
383 None
384 }
385 };
386 match self.layer {
387 None => self
388 .labels
389 .iter()
390 .copied()
391 .enumerate()
392 .filter_map(live)
393 .collect(),
394 Some(_) => self.label_syms().enumerate().filter_map(live).collect(),
395 }
396 }
397
398 pub fn prop(&self, id: u32, field: &str) -> Option<ValueRef<'_>> {
405 self.props.get(id, field)
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::{GraphView, VisibleSet};
412 use core_storage::v8::seam::{ColumnsView, EdgePropsView, TopologyView};
413 use core_storage::{ColumnStore, EdgeProps, IdMap, Interner, Topology, Value};
414
415 struct Fx {
416 ids: IdMap,
417 syms: Interner,
418 labels: Vec<u32>,
419 props: ColumnStore,
420 topo: Topology,
421 eprops: EdgeProps,
422 }
423
424 impl Fx {
425 fn new() -> Self {
426 Fx {
427 ids: IdMap::new(),
428 syms: Interner::new(),
429 labels: vec![],
430 props: ColumnStore::new(),
431 topo: Topology::new(),
432 eprops: EdgeProps::new(),
433 }
434 }
435
436 fn add(&mut self, label: &str, key: &str, props: Vec<(&str, Value)>) -> u32 {
437 let id = self.ids.get_or_insert(key);
438 let sym = self.syms.intern(label);
439 self.labels.resize(id as usize + 1, u32::MAX);
440 self.labels[id as usize] = sym;
441 for (f, v) in props {
442 self.props.set(id, f, v);
443 }
444 id
445 }
446
447 fn view(&self) -> GraphView<'_> {
448 GraphView {
449 ids: &self.ids,
450 syms: &self.syms,
451 labels: &self.labels,
452 props: ColumnsView::owned(&self.props),
453 topo: TopologyView::owned(&self.topo),
454 edge_props: EdgePropsView::owned(&self.eprops),
455 mask: None,
456 prop_index: None,
457 layer: None,
458 }
459 }
460 }
461
462 #[test]
463 fn prop_returns_none_for_missing() {
464 let mut fx = Fx::new();
465 let id = fx.add("N", "alice", vec![("age", Value::Int(36))]);
466 let v = fx.view();
467 assert_eq!(
468 v.prop(id, "age").map(|vr| vr.into_value()),
469 Some(Value::Int(36))
470 );
471 assert!(v.prop(id, "missing").is_none());
472 }
473
474 #[test]
475 fn graph_view_lookups() {
476 let mut fx = Fx::new();
477 let id = fx.add("Person", "ada", vec![("age", Value::Int(36))]);
478 let v = fx.view();
479 assert_eq!(v.node_id("ada"), Some(id));
480 assert_eq!(v.node_id("zzz"), None);
481 assert_eq!(v.key_of(id), "ada");
482 assert_eq!(v.label_of(id), Some("Person"));
483 assert_eq!(v.label_of(99), None);
484 assert_eq!(
485 v.prop(id, "age").map(|vr| vr.into_value()),
486 Some(Value::Int(36))
487 );
488 assert_eq!(v.prop(id, "missing"), None);
489 }
490
491 #[test]
492 fn gap_sentinel_is_not_a_label() {
493 let mut fx = Fx::new();
494 let kept = fx.add("Person", "ada", vec![]);
495 fx.ids.get_or_insert("ghost");
496 fx.labels.resize(2, u32::MAX);
497 let later = fx.add("Person", "bob", vec![]);
498 let v = fx.view();
499 assert_eq!(v.label_of(1), None);
500 assert_eq!(v.nodes_with_label("Person"), vec![kept, later]);
501 }
502
503 #[test]
504 fn nodes_with_label_skips_tombstoned_id() {
505 let mut fx = Fx::new();
506 let ada = fx.add("Person", "ada", vec![]);
507 let bob = fx.add("Person", "bob", vec![]);
508 fx.ids.delete("ada");
509 fx.labels[ada as usize] = u32::MAX;
510 let v = fx.view();
511 assert_eq!(v.node_id("ada"), None);
512 assert_eq!(v.label_of(ada), None);
513 assert_eq!(v.nodes_with_label("Person"), vec![bob]);
514 }
515
516 #[test]
517 fn nodes_with_label_dense_id_order_and_unknown_empty() {
518 let mut fx = Fx::new();
519 let bob = fx.add("Person", "bob", vec![]);
520 let ada = fx.add("Person", "ada", vec![]);
521 let _acme = fx.add("Company", "acme", vec![]);
522 let v = fx.view();
523 assert_eq!(v.nodes_with_label("Person"), vec![bob, ada]);
524 assert_eq!(v.nodes_with_label("Person"), vec![0, 1]);
525 assert!(v.nodes_with_label("Nope").is_empty());
526 }
527
528 #[test]
529 fn nodes_all_respects_mask() {
530 let mut fx = Fx::new();
531 let alice = fx.add("Person", "alice", vec![]);
532 let bob = fx.add("Person", "bob", vec![]);
533 let carol = fx.add("Person", "carol", vec![]);
534
535 let v_full = fx.view();
537 let all = v_full.nodes_all();
538 assert!(all.contains(&alice));
539 assert!(all.contains(&bob));
540 assert!(all.contains(&carol));
541
542 let visible: VisibleSet = [alice, carol].into_iter().collect();
544 let v_masked = GraphView {
545 ids: &fx.ids,
546 syms: &fx.syms,
547 labels: &fx.labels,
548 props: ColumnsView::owned(&fx.props),
549 topo: TopologyView::owned(&fx.topo),
550 edge_props: EdgePropsView::owned(&fx.eprops),
551 mask: Some(&visible),
552 prop_index: None,
553 layer: None,
554 };
555 let masked = v_masked.nodes_all();
556 assert!(masked.contains(&alice));
557 assert!(!masked.contains(&bob), "masked node must be excluded");
558 assert!(masked.contains(&carol));
559 }
560
561 #[test]
562 fn layer_inserts_deletes_and_resurrects_without_touching_below() {
563 let mut fx = Fx::new();
564 let ada = fx.add("Person", "ada", vec![]);
565 let bob = fx.add("Person", "bob", vec![]);
566 let mut tx = super::NodeLayer::new(&fx.ids, &fx.syms);
567 let company = tx.intern(&fx.syms, "Company");
568 let person = tx.intern(&fx.syms, "Person");
569 let acme = tx.insert("acme", company);
570 tx.delete("ada", ada);
571 let ada2 = tx.insert("ada", person);
572 let base_view = fx.view();
573 let v = GraphView {
574 layer: Some(&tx),
575 ..fx.view()
576 };
577 assert_eq!(acme, 2);
578 assert_eq!(
579 ada2, 3,
580 "a re-created key gets a fresh id, as the commit gives it"
581 );
582 assert_eq!(v.node_id("ada"), Some(ada2));
583 assert_eq!(v.try_key_of(ada), None);
584 assert_eq!(v.key_of(acme), "acme");
585 assert_eq!(v.label_of(acme), Some("Company"));
586 assert_eq!(v.id_bound(), 4);
587 assert_eq!(v.nodes_with_label("Person"), vec![bob, ada2]);
588 assert_eq!(v.nodes_with_label("Company"), vec![acme]);
589 assert_eq!(v.nodes_all(), vec![bob, acme, ada2]);
590 assert_eq!(base_view.node_id("ada"), Some(ada));
592 assert_eq!(base_view.sym("Company"), None);
593 assert_eq!(base_view.nodes_all(), vec![ada, bob]);
594 }
595}