yo/graph.rs
1//! The typed graph surface, where a traversal that does not make sense does not
2//! compile (`11` section 6).
3//!
4//! A node type is a struct with an id and a label, an edge type is a struct that
5//! says which node type it goes from and which it goes to, and a walk is a chain
6//! of method calls. There is no query language, and the argument for not having
7//! one is this file: what a Cypher engine finds out at run time, this finds out
8//! at compile time.
9//!
10//! ```
11//! use yo::{Edge, Node, Yo};
12//!
13//! #[derive(Yo, Debug, PartialEq)]
14//! struct Person {
15//! #[yo(id)]
16//! id: u64,
17//! #[yo(index)]
18//! city: String,
19//! }
20//!
21//! #[derive(Yo, Debug, PartialEq)]
22//! struct Follows {
23//! since: i64,
24//! }
25//!
26//! impl Node for Person {
27//! const LABEL: &'static str = "Person";
28//! }
29//!
30//! impl Edge for Follows {
31//! type From = Person;
32//! type To = Person;
33//! const LABEL: &'static str = "FOLLOWS";
34//! }
35//!
36//! let db = yo::open(yo::MEMORY)?;
37//! let g = db.graph("social")?;
38//!
39//! let ada = g.add(&Person { id: 1, city: "london".to_owned() })?;
40//! let grace = g.add(&Person { id: 2, city: "london".to_owned() })?;
41//! let edsger = g.add(&Person { id: 3, city: "austin".to_owned() })?;
42//!
43//! g.link(ada, grace, &Follows { since: 2024 })?;
44//! g.link(grace, edsger, &Follows { since: 2026 })?;
45//!
46//! // Who does the person I follow follow.
47//! let two = g.walk(ada).out::<Follows>()?.out::<Follows>()?.nodes()?;
48//! assert_eq!(two, vec![Person { id: 3, city: "austin".to_owned() }]);
49//!
50//! // And a walk can start at an index rather than at an id.
51//! assert_eq!(g.find(Person::CITY, "london")?.len(), 2);
52//! # Ok::<(), yo::Error>(())
53//! ```
54//!
55//! # What does not compile
56//!
57//! An edge carries where it goes from and where it goes to in its type, so
58//! `out::<Follows>()` on a walk that is standing on a `Company` is a type error
59//! and not an empty result.
60//!
61//! ```compile_fail
62//! # use yo::{Edge, Node, Yo};
63//! # #[derive(Yo)] struct Person { #[yo(id)] id: u64 }
64//! # #[derive(Yo)] struct Company { #[yo(id)] id: u64 }
65//! # #[derive(Yo)] struct Follows { since: i64 }
66//! # impl Node for Person { const LABEL: &'static str = "Person"; }
67//! # impl Node for Company { const LABEL: &'static str = "Company"; }
68//! # impl Edge for Follows { type From = Person; type To = Person; const LABEL: &'static str = "FOLLOWS"; }
69//! # let db = yo::open(yo::MEMORY).unwrap();
70//! # let g = db.graph("social").unwrap();
71//! let acme = g.add(&Company { id: 100 }).unwrap();
72//! // Follows starts at a Person, so there is no such hop from a Company.
73//! g.walk(acme).out::<Follows>().unwrap();
74//! ```
75//!
76//! Linking is the same. An edge's ends are its own types, so putting a company
77//! on the wrong end of a `Follows` is a type error at the call site.
78//!
79//! ```compile_fail
80//! # use yo::{Edge, Node, Yo};
81//! # #[derive(Yo)] struct Person { #[yo(id)] id: u64 }
82//! # #[derive(Yo)] struct Company { #[yo(id)] id: u64 }
83//! # #[derive(Yo)] struct Follows { since: i64 }
84//! # impl Node for Person { const LABEL: &'static str = "Person"; }
85//! # impl Node for Company { const LABEL: &'static str = "Company"; }
86//! # impl Edge for Follows { type From = Person; type To = Person; const LABEL: &'static str = "FOLLOWS"; }
87//! # let db = yo::open(yo::MEMORY).unwrap();
88//! # let g = db.graph("social").unwrap();
89//! # let ada = g.add(&Person { id: 1 }).unwrap();
90//! let acme = g.add(&Company { id: 100 }).unwrap();
91//! g.link(ada, acme, &Follows { since: 2026 }).unwrap();
92//! ```
93//!
94//! # An [`Id`] is not the id you wrote
95//!
96//! [`Graph::add`] hands back an `Id<Person>`, which is a handle into this graph
97//! and not the `1` in the struct. The adjacency plane is keyed by a dense `u64`
98//! and that is what makes a hop a probe and a sequential read, so the id in the
99//! struct is looked up once on the way in and never again. Everything a walk
100//! touches is already dense.
101//!
102//! That is the trade and it is worth stating plainly. An entry point costs a
103//! hash lookup, and a hop costs nothing extra at all. A graph engine that keyed
104//! its adjacency by whatever the user's id happened to be would pay that lookup
105//! on every hop of every walk instead.
106//!
107//! Because the id is a handle, [`Graph::id_of`] is how you get back to one from
108//! the id you wrote, and it is the only call that costs the lookup.
109//!
110//! # One store, several node types
111//!
112//! Every node type shares one document collection and one adjacency plane, and
113//! the label keeps them apart. That costs four bytes a node, which is the label
114//! beside each dense id, and it buys the thing that matters: a two hop walk that
115//! crosses from `Person` to `Company` is one plane and one contiguous run, not a
116//! join between two stores.
117//!
118//! An index is declared per path rather than per type, so two node types that
119//! both index `$.name` share one index and [`Graph::find`] filters the answer by
120//! label. That over-reads when two types share a path and share values, and it
121//! is written down here rather than found later. Two types that index the same
122//! path for different kinds is a conflict and is refused.
123
124use core::marker::PhantomData;
125use std::collections::HashMap;
126
127use yo_common::{Code, Error, Result};
128use yo_doc::Builder;
129use yo_graph::Dir;
130use yo_shape::{Desc, Shape, Tag};
131
132use crate::db::Handle;
133use crate::doc::{Asked, Document, Field, IndexKind, Indexed, Path, key_of};
134
135/// A type that is a node in a graph.
136///
137/// A [`Document`] with a label. The label is what keeps two node types apart in
138/// one store, and it is a string rather than a number so that a graph read back
139/// off disk knows what it is holding.
140#[diagnostic::on_unimplemented(
141 message = "`{Self}` is not a node type",
142 label = "this type has no label",
143 note = "give it a label with an `impl Node` block naming a `const LABEL`, and make sure it derives Yo with one field marked `#[yo(id)]`"
144)]
145pub trait Node: Document {
146 /// What this type is called in the graph.
147 const LABEL: &'static str;
148}
149
150/// A type that is an edge in a graph.
151///
152/// The two ends are in the type, which is the whole point: a hop that a node
153/// type cannot start is a type error rather than an empty answer, and an edge
154/// put between the wrong pair does not compile.
155///
156/// An edge has no id, because an edge is identified by where it is rather than
157/// by a field, so this is a [`Field`] and an [`Indexed`] rather than a
158/// [`Document`].
159#[diagnostic::on_unimplemented(
160 message = "`{Self}` is not an edge type",
161 label = "this type does not say where it goes",
162 note = "say where it goes with an `impl Edge` block naming a `From`, a `To` and a `const LABEL`"
163)]
164pub trait Edge: Field + Indexed {
165 /// The node type an edge of this kind starts at.
166 type From: Node;
167 /// The node type an edge of this kind ends at.
168 type To: Node;
169 /// What this type is called in the graph.
170 const LABEL: &'static str;
171}
172
173/// A node in a graph.
174///
175/// Handed out by [`Graph::add`] and by [`Graph::id_of`], and taken by every call
176/// that starts somewhere. The type parameter is what makes a wrong hop a compile
177/// error, so it is not a `u64` you can build by hand.
178pub struct Id<N> {
179 raw: u64,
180 /// `fn() -> N` so that an `Id` is `Copy` and `Send` whatever `N` is.
181 marker: PhantomData<fn() -> N>,
182}
183
184impl<N> Clone for Id<N> {
185 fn clone(&self) -> Id<N> {
186 *self
187 }
188}
189
190impl<N> Copy for Id<N> {}
191
192impl<N> PartialEq for Id<N> {
193 fn eq(&self, other: &Id<N>) -> bool {
194 self.raw == other.raw
195 }
196}
197
198impl<N> Eq for Id<N> {}
199
200impl<N> core::hash::Hash for Id<N> {
201 fn hash<H: core::hash::Hasher>(&self, h: &mut H) {
202 self.raw.hash(h);
203 }
204}
205
206impl<N: Node> core::fmt::Debug for Id<N> {
207 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208 write!(f, "{}#{}", N::LABEL, self.raw)
209 }
210}
211
212impl<N> Id<N> {
213 fn new(raw: u64) -> Id<N> {
214 Id {
215 raw,
216 marker: PhantomData,
217 }
218 }
219}
220
221/// One edge in a graph, which is what [`Graph::link`] answers with.
222///
223/// Two edges of the same kind between the same pair are two edges, so this
224/// names one of them and reading an edge's fields needs it.
225pub struct EdgeId<E> {
226 slot: u32,
227 marker: PhantomData<fn() -> E>,
228}
229
230impl<E> Clone for EdgeId<E> {
231 fn clone(&self) -> EdgeId<E> {
232 *self
233 }
234}
235
236impl<E> Copy for EdgeId<E> {}
237
238impl<E> PartialEq for EdgeId<E> {
239 fn eq(&self, other: &EdgeId<E>) -> bool {
240 self.slot == other.slot
241 }
242}
243
244impl<E> Eq for EdgeId<E> {}
245
246impl<E: Edge> core::fmt::Debug for EdgeId<E> {
247 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
248 write!(f, "{}#{}", E::LABEL, self.slot)
249 }
250}
251
252/// One step across an edge: where it went, and which edge it was.
253///
254/// A pair with names on it rather than a tuple, because the whole point of the
255/// second half is that reading the edge's fields needs a handle and a caller
256/// should not have to remember which end of a tuple that is.
257pub struct Hop<E: Edge> {
258 /// The node at the far end.
259 pub to: Id<E::To>,
260 /// The edge that got there, which [`Graph::edge`] reads.
261 pub edge: EdgeId<E>,
262}
263
264impl<E: Edge> Clone for Hop<E> {
265 fn clone(&self) -> Hop<E> {
266 *self
267 }
268}
269
270impl<E: Edge> Copy for Hop<E> {}
271
272impl<E: Edge> core::fmt::Debug for Hop<E> {
273 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
274 write!(f, "{:?} by {:?}", self.to, self.edge)
275 }
276}
277
278/// What a graph collection holds.
279///
280/// The plane and the documents are [`yo_graph::Graph`]. Everything else here is
281/// the typing: which labels are in use, which shape each was first opened with,
282/// and the one lookup that turns the id in a struct into the dense id the plane
283/// is keyed by.
284pub(crate) struct Store {
285 g: yo_graph::Graph,
286 /// Node labels, in the order they were first used. The position is the
287 /// label id, which is what `of` holds.
288 nodes: Vec<Kind>,
289 /// Edge labels, likewise, and the position is the label the plane is given.
290 edges: Vec<Kind>,
291 /// The id in a struct, tagged with its node label, to the dense id.
292 ids: HashMap<Box<[u8]>, u64>,
293 /// The label of each dense id, or [`GONE`] for one that was removed.
294 of: Vec<u32>,
295 /// The next dense id never handed out.
296 next: u64,
297 /// Which paths are indexed on each side, and how, so that two types asking
298 /// for the same path share one index and two types asking for it in two
299 /// different ways is refused.
300 node_paths: HashMap<&'static str, IndexKind>,
301 edge_paths: HashMap<&'static str, IndexKind>,
302 scratch: Builder,
303}
304
305/// A label that was removed, in `of`.
306const GONE: u32 = u32::MAX;
307
308/// A label in use, and the shape it was first used with.
309struct Kind {
310 label: &'static str,
311 shape: Tag,
312 live: usize,
313}
314
315impl Store {
316 pub(crate) fn new() -> Store {
317 Store {
318 g: yo_graph::Graph::new(),
319 nodes: Vec::new(),
320 edges: Vec::new(),
321 ids: HashMap::new(),
322 of: Vec::new(),
323 next: 0,
324 node_paths: HashMap::new(),
325 edge_paths: HashMap::new(),
326 scratch: Builder::new(),
327 }
328 }
329
330 pub(crate) fn memory_bytes(&self) -> usize {
331 self.g.memory_bytes()
332 + self.of.capacity() * size_of::<u32>()
333 + self.ids.capacity() * (size_of::<Box<[u8]>>() + size_of::<u64>() + 16)
334 }
335
336 /// The label id of a node type, registering it the first time.
337 fn node_kind<N: Node>(&mut self) -> Result<u32> {
338 let want = tag_of::<N>();
339 let at = register(&mut self.nodes, N::LABEL, want)?;
340 declare(&mut self.node_paths, N::INDEXES, N::LABEL, |path, kind| {
341 self.g.index_nodes(path, kind)
342 })?;
343 Ok(at)
344 }
345
346 /// The label id of an edge type, registering it the first time.
347 fn edge_kind<E: Edge>(&mut self) -> Result<u32> {
348 let want = tag_of::<E>();
349 let at = register(&mut self.edges, E::LABEL, want)?;
350 declare(&mut self.edge_paths, E::INDEXES, E::LABEL, |path, kind| {
351 self.g.index_edges(path, kind)
352 })?;
353 Ok(at)
354 }
355
356 /// The label id of an edge type without registering it, for a read that
357 /// should answer nothing rather than create anything.
358 fn edge_seen<E: Edge>(&self) -> Option<u32> {
359 seen(&self.edges, E::LABEL)
360 }
361
362 fn node_seen<N: Node>(&self) -> Option<u32> {
363 seen(&self.nodes, N::LABEL)
364 }
365
366 /// Whether a dense id is a node of `kind`.
367 fn is(&self, raw: u64, kind: u32) -> bool {
368 usize::try_from(raw).is_ok_and(|i| self.of.get(i).copied() == Some(kind))
369 }
370}
371
372fn seen(kinds: &[Kind], label: &'static str) -> Option<u32> {
373 kinds
374 .iter()
375 .position(|k| k.label == label)
376 .map(|at| at as u32)
377}
378
379/// Find a label or add it, and check the shape has not changed under it.
380fn register(kinds: &mut Vec<Kind>, label: &'static str, want: Tag) -> Result<u32> {
381 if let Some(at) = kinds.iter().position(|k| k.label == label) {
382 if kinds[at].shape != want {
383 return Err(Error::fmt(
384 Code::ShapeMismatch,
385 format_args!(
386 "this graph already holds {label} under another shape, so the two types cannot share the label"
387 ),
388 ));
389 }
390 return Ok(at as u32);
391 }
392 if kinds.len() >= u32::MAX as usize {
393 return Err(Error::new(Code::Full, "this graph has no labels left"));
394 }
395 kinds.push(Kind {
396 label,
397 shape: want,
398 live: 0,
399 });
400 Ok((kinds.len() - 1) as u32)
401}
402
403/// Declare a type's indexes, sharing one per path across the types that ask for
404/// it and refusing two types that want the same path indexed differently.
405fn declare(
406 have: &mut HashMap<&'static str, IndexKind>,
407 want: &'static [(&'static str, IndexKind)],
408 label: &'static str,
409 mut create: impl FnMut(&str, IndexKind) -> Result<()>,
410) -> Result<()> {
411 for (path, kind) in want {
412 match have.get(path) {
413 Some(already) if already == kind => {}
414 Some(already) => {
415 return Err(Error::fmt(
416 Code::Invalid,
417 format_args!(
418 "{label} asks for {path} to be indexed for {kind:?}, and another type in this graph already indexes it for {already:?}. One path is one index, so the two types have to agree"
419 ),
420 ));
421 }
422 None => {
423 create(path, *kind)?;
424 have.insert(path, *kind);
425 }
426 }
427 }
428 Ok(())
429}
430
431fn tag_of<T: Shape>() -> Tag {
432 let mut d = Desc::new();
433 T::describe(&mut d);
434 d.tag()
435}
436
437/// A graph.
438///
439/// Cheap to clone and cheap to keep around, the same way [`crate::Docs`] is: the
440/// handle is a pointer and an index, and every clone is the same graph.
441#[derive(Clone)]
442pub struct Graph {
443 db: Handle,
444 at: usize,
445}
446
447impl core::fmt::Debug for Graph {
448 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
449 let name = self
450 .db
451 .read(|inner| Ok(inner.collections[self.at].name.clone()))
452 .unwrap_or_else(|_| "?".to_owned());
453 f.debug_struct("Graph").field("name", &name).finish()
454 }
455}
456
457impl Graph {
458 pub(crate) fn new(db: Handle, at: usize) -> Graph {
459 Graph { db, at }
460 }
461
462 /// The name this graph was opened under.
463 ///
464 /// # Errors
465 ///
466 /// [`Code::Invalid`] if called from inside a callback that is already
467 /// holding this database.
468 pub fn name(&self) -> Result<String> {
469 self.db
470 .read(|inner| Ok(inner.collections[self.at].name.clone()))
471 }
472
473 fn write<R>(&self, f: impl FnOnce(&mut Store) -> Result<R>) -> Result<R> {
474 self.db
475 .write(|inner| f(inner.collections[self.at].data.graph_mut()))
476 }
477
478 fn read<R>(&self, f: impl FnOnce(&Store) -> Result<R>) -> Result<R> {
479 self.db
480 .read(|inner| f(inner.collections[self.at].data.graph()))
481 }
482
483 /// Put a node in, replacing whatever was under its id.
484 ///
485 /// The [`Id`] that comes back is this graph's handle on the node and is the
486 /// same one every time for the same struct id, so adding a node twice
487 /// updates it rather than making a second one.
488 ///
489 /// # Errors
490 ///
491 /// [`Code::ShapeMismatch`] when another type already uses this label, and
492 /// [`Code::Invalid`] for an id that cannot be a key.
493 pub fn add<N: Node>(&self, node: &N) -> Result<Id<N>> {
494 let key = key_of(node.id(), IndexKind::Equality, "the id")?;
495 self.write(|s| {
496 let kind = s.node_kind::<N>()?;
497 let tagged = tagged(kind, key.as_bytes());
498 let raw = match s.ids.get(&tagged[..]) {
499 Some(raw) => *raw,
500 None => {
501 let raw = s.next;
502 s.next += 1;
503 s.ids.insert(tagged.into_boxed_slice(), raw);
504 s.of.push(kind);
505 s.nodes[kind as usize].live += 1;
506 raw
507 }
508 };
509 s.scratch.clear();
510 Field::write(node, &mut s.scratch)?;
511 let bytes = s.scratch.finish()?;
512 s.g.put_node(raw, bytes)?;
513 Ok(Id::new(raw))
514 })
515 }
516
517 /// This graph's handle on the node with the id you wrote, if it has one.
518 ///
519 /// The one call that pays the lookup from a struct id to a dense id, which
520 /// is what the module docs are about.
521 ///
522 /// # Errors
523 ///
524 /// [`Code::Invalid`] for an id that cannot be a key.
525 pub fn id_of<N: Node>(&self, id: &<N::Id as Asked>::Ask) -> Result<Option<Id<N>>> {
526 let key = key_of(id, IndexKind::Equality, "the id")?;
527 self.read(|s| {
528 let Some(kind) = s.node_seen::<N>() else {
529 return Ok(None);
530 };
531 Ok(s.ids
532 .get(&tagged(kind, key.as_bytes())[..])
533 .map(|raw| Id::new(*raw)))
534 })
535 }
536
537 /// Read a node back.
538 ///
539 /// # Errors
540 ///
541 /// [`Code::Corrupt`] if the stored node is not an `N`, which is a graph that
542 /// disagrees with its own labels.
543 pub fn get<N: Node>(&self, id: Id<N>) -> Result<Option<N>> {
544 self.read(|s| match s.g.node(id.raw) {
545 Some(doc) => N::read(doc).map(Some),
546 None => Ok(None),
547 })
548 }
549
550 /// Whether this graph still has that node.
551 ///
552 /// # Errors
553 ///
554 /// [`Code::Invalid`] if called from inside a callback holding this database.
555 pub fn has<N: Node>(&self, id: Id<N>) -> Result<bool> {
556 self.read(|s| Ok(s.g.has_node(id.raw)))
557 }
558
559 /// Take a node out, along with every edge at either end of it.
560 ///
561 /// Answers whether the node was there.
562 ///
563 /// # Errors
564 ///
565 /// [`Code::Corrupt`] if the stored node is not an `N`.
566 pub fn remove<N: Node>(&self, id: Id<N>) -> Result<bool> {
567 self.write(|s| {
568 let Some(doc) = s.g.node(id.raw) else {
569 return Ok(false);
570 };
571 // The struct is read back so that its id can be turned into the key
572 // the lookup table is holding. Storing the key a second time would
573 // be a copy of every id in the graph to save this one decode on a
574 // path that is already removing edges.
575 let node = N::read(doc)?;
576 let key = key_of(node.id(), IndexKind::Equality, "the id")?;
577 let Some(kind) = s.node_seen::<N>() else {
578 return Ok(false);
579 };
580 s.ids.remove(&tagged(kind, key.as_bytes())[..]);
581 if let Ok(i) = usize::try_from(id.raw)
582 && let Some(slot) = s.of.get_mut(i)
583 {
584 *slot = GONE;
585 }
586 s.nodes[kind as usize].live -= 1;
587 s.g.remove_node(id.raw)
588 })
589 }
590
591 /// How many nodes of this type the graph holds.
592 ///
593 /// # Errors
594 ///
595 /// [`Code::Invalid`] if called from inside a callback holding this database.
596 pub fn count<N: Node>(&self) -> Result<usize> {
597 self.read(|s| {
598 Ok(s.node_seen::<N>()
599 .map_or(0, |kind| s.nodes[kind as usize].live))
600 })
601 }
602
603 /// How many nodes of every type the graph holds.
604 ///
605 /// # Errors
606 ///
607 /// [`Code::Invalid`] if called from inside a callback holding this database.
608 pub fn nodes(&self) -> Result<usize> {
609 self.read(|s| Ok(s.g.nodes()))
610 }
611
612 /// How many edges of every type the graph holds, counting two edges between
613 /// the same pair as two.
614 ///
615 /// # Errors
616 ///
617 /// [`Code::Invalid`] if called from inside a callback holding this database.
618 pub fn edges(&self) -> Result<usize> {
619 self.read(|s| Ok(s.g.edges()))
620 }
621
622 /// Put an edge between two nodes.
623 ///
624 /// Linking the same pair twice leaves two edges, each with its own fields,
625 /// because that is what a property graph means by a multigraph and because
626 /// two ratings of the same film on two dates is the case rather than the
627 /// corner case.
628 ///
629 /// # Errors
630 ///
631 /// [`Code::NotFound`] if either end is not in the graph, because an edge
632 /// hanging off an id that was never added is a dangling reference that every
633 /// later read would have to guard against.
634 pub fn link<E: Edge>(&self, from: Id<E::From>, to: Id<E::To>, edge: &E) -> Result<EdgeId<E>> {
635 self.write(|s| {
636 let label = s.edge_kind::<E>()?;
637 let from_kind = s.node_kind::<E::From>()?;
638 let to_kind = s.node_kind::<E::To>()?;
639 if !s.is(from.raw, from_kind) {
640 return Err(gone::<E::From>(from.raw));
641 }
642 if !s.is(to.raw, to_kind) {
643 return Err(gone::<E::To>(to.raw));
644 }
645 s.scratch.clear();
646 Field::write(edge, &mut s.scratch)?;
647 let bytes = s.scratch.finish()?;
648 let slot = s.g.link(from.raw, to.raw, label, bytes)?;
649 Ok(EdgeId {
650 slot,
651 marker: PhantomData,
652 })
653 })
654 }
655
656 /// Take one edge of this kind out from between two nodes.
657 ///
658 /// Answers whether there was one. With two edges between the same pair it
659 /// takes one of them, and which one is whatever the run's order left.
660 ///
661 /// # Errors
662 ///
663 /// [`Code::Invalid`] if called from inside a callback holding this database.
664 pub fn unlink<E: Edge>(&self, from: Id<E::From>, to: Id<E::To>) -> Result<bool> {
665 self.write(|s| {
666 let Some(label) = s.edge_seen::<E>() else {
667 return Ok(false);
668 };
669 Ok(s.g.unlink(from.raw, to.raw, label).is_some())
670 })
671 }
672
673 /// Read an edge's fields.
674 ///
675 /// # Errors
676 ///
677 /// [`Code::Corrupt`] if the stored edge is not an `E`.
678 pub fn edge<E: Edge>(&self, id: EdgeId<E>) -> Result<Option<E>> {
679 self.read(|s| match s.g.edge(id.slot) {
680 Some(doc) => E::read(doc).map(Some),
681 None => Ok(None),
682 })
683 }
684
685 /// Where an edge of this kind goes from this node.
686 ///
687 /// # Errors
688 ///
689 /// [`Code::Invalid`] if called from inside a callback holding this database.
690 pub fn out<E: Edge>(&self, from: Id<E::From>) -> Result<Vec<Id<E::To>>> {
691 self.step::<E>(from.raw, Dir::Out).map(ids)
692 }
693
694 /// Where an edge of this kind comes into this node from.
695 ///
696 /// # Errors
697 ///
698 /// [`Code::Invalid`] if called from inside a callback holding this database.
699 pub fn incoming<E: Edge>(&self, to: Id<E::To>) -> Result<Vec<Id<E::From>>> {
700 self.step::<E>(to.raw, Dir::In).map(ids)
701 }
702
703 /// The same as [`Graph::out`], with the edge that got to each one.
704 ///
705 /// # Errors
706 ///
707 /// [`Code::Invalid`] if called from inside a callback holding this database.
708 pub fn out_edges<E: Edge>(&self, from: Id<E::From>) -> Result<Vec<Hop<E>>> {
709 self.read(|s| {
710 let Some(label) = s.edge_seen::<E>() else {
711 return Ok(Vec::new());
712 };
713 Ok(s.g
714 .hop(from.raw, label, Dir::Out)
715 .map(|(node, slot)| Hop {
716 to: Id::new(node),
717 edge: EdgeId {
718 slot,
719 marker: PhantomData,
720 },
721 })
722 .collect())
723 })
724 }
725
726 /// How many edges of this kind leave this node.
727 ///
728 /// Read off the run header, so it does not touch the run.
729 ///
730 /// # Errors
731 ///
732 /// [`Code::Invalid`] if called from inside a callback holding this database.
733 pub fn degree<E: Edge>(&self, from: Id<E::From>) -> Result<usize> {
734 self.read(|s| {
735 Ok(s.edge_seen::<E>()
736 .map_or(0, |label| s.g.degree(from.raw, label, Dir::Out)))
737 })
738 }
739
740 /// Start a walk at a node.
741 #[must_use]
742 pub fn walk<N: Node>(&self, from: Id<N>) -> Walk<'_, N> {
743 Walk {
744 g: self,
745 at: vec![from.raw],
746 marker: PhantomData,
747 }
748 }
749
750 /// Start a walk at everything a path index answers.
751 ///
752 /// # Errors
753 ///
754 /// The same as [`Graph::find`].
755 pub fn walk_from<N: Node, V: Asked>(
756 &self,
757 path: impl Into<Path<N, V>>,
758 value: &V::Ask,
759 ) -> Result<Walk<'_, N>> {
760 let at = self.matching::<N, V>(path.into(), value)?;
761 Ok(Walk {
762 g: self,
763 at,
764 marker: PhantomData,
765 })
766 }
767
768 /// Every node of this type with `value` at `path`.
769 ///
770 /// # Errors
771 ///
772 /// [`Code::Invalid`] if the path is not indexed, and [`Code::Corrupt`] if a
773 /// stored node is not an `N`.
774 pub fn find<N: Node, V: Asked>(
775 &self,
776 path: impl Into<Path<N, V>>,
777 value: &V::Ask,
778 ) -> Result<Vec<N>> {
779 let path = path.into();
780 let key = key_of(value, path.kind(), "this value")?;
781 self.read(|s| {
782 let Some(kind) = s.node_seen::<N>() else {
783 return Ok(Vec::new());
784 };
785 let mut out = Vec::new();
786 let mut bad = None;
787 s.g.find_nodes(path.path(), &key, |raw, doc| {
788 // The index covers every type that asked for this path, so the
789 // label is what says whether this row is an `N`.
790 if !s.is(raw, kind) {
791 return;
792 }
793 match N::read(doc) {
794 Ok(node) => out.push(node),
795 Err(e) => bad = bad.take().or(Some(e)),
796 }
797 })?;
798 match bad {
799 Some(e) => Err(e),
800 None => Ok(out),
801 }
802 })
803 }
804
805 /// How many nodes of this type have `value` at `path`.
806 ///
807 /// # Errors
808 ///
809 /// The same as [`Graph::find`], without the decode.
810 pub fn count_at<N: Node, V: Asked>(
811 &self,
812 path: impl Into<Path<N, V>>,
813 value: &V::Ask,
814 ) -> Result<usize> {
815 Ok(self.matching::<N, V>(path.into(), value)?.len())
816 }
817
818 /// What this graph weighs.
819 ///
820 /// # Errors
821 ///
822 /// [`Code::Invalid`] if called from inside a callback holding this database.
823 pub fn memory_bytes(&self) -> Result<usize> {
824 self.read(|s| Ok(s.memory_bytes()))
825 }
826
827 /// The dense ids of every node of this type at `path`.
828 fn matching<N: Node, V: Asked>(&self, path: Path<N, V>, value: &V::Ask) -> Result<Vec<u64>> {
829 let key = key_of(value, path.kind(), "this value")?;
830 self.read(|s| {
831 let Some(kind) = s.node_seen::<N>() else {
832 return Ok(Vec::new());
833 };
834 let mut out = Vec::new();
835 s.g.find_nodes(path.path(), &key, |raw, _| {
836 if s.is(raw, kind) {
837 out.push(raw);
838 }
839 })?;
840 Ok(out)
841 })
842 }
843
844 /// One hop, in dense ids.
845 fn step<E: Edge>(&self, from: u64, dir: Dir) -> Result<Vec<u64>> {
846 self.read(|s| {
847 Ok(s.edge_seen::<E>()
848 .map_or_else(Vec::new, |label| s.g.neighbours(from, label, dir).to_vec()))
849 })
850 }
851
852 /// A whole frontier hopped at once, deduplicated.
853 fn frontier<E: Edge>(&self, at: &[u64], dir: Dir) -> Result<Vec<u64>> {
854 self.read(|s| {
855 let Some(label) = s.edge_seen::<E>() else {
856 return Ok(Vec::new());
857 };
858 // The headers of the whole frontier are asked for before any of
859 // them is read, because the frontier is known as soon as the last
860 // hop finished and there is no reason for these probes to be
861 // serial. It is worth about a fifth of a two hop walk.
862 for node in at {
863 s.g.prefetch(*node, label, dir);
864 }
865 let mut out = Vec::new();
866 for node in at {
867 out.extend_from_slice(s.g.neighbours(*node, label, dir));
868 }
869 // A frontier that keeps every path to a node grows as the product
870 // of the degrees, and a walk is asking which nodes it can reach and
871 // not by how many routes, so it is the set that goes on.
872 out.sort_unstable();
873 out.dedup();
874 Ok(out)
875 })
876 }
877}
878
879/// A walk standing on a set of nodes of one type.
880///
881/// Each hop is a whole frontier at a time, and the frontier is a set, so a
882/// diamond in the graph does not turn into two copies of the node at the far
883/// end. See [`Graph::walk`].
884pub struct Walk<'a, N> {
885 g: &'a Graph,
886 at: Vec<u64>,
887 marker: PhantomData<fn() -> N>,
888}
889
890impl<N: Node> core::fmt::Debug for Walk<'_, N> {
891 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
892 f.debug_struct("Walk")
893 .field("on", &N::LABEL)
894 .field("len", &self.at.len())
895 .finish()
896 }
897}
898
899impl<'a, N: Node> Walk<'a, N> {
900 /// Follow every edge of this kind forwards.
901 ///
902 /// `E::From` is `N`, which is what makes a hop the graph does not have a
903 /// compile error rather than an empty answer.
904 ///
905 /// # Errors
906 ///
907 /// [`Code::Invalid`] if called from inside a callback holding this database.
908 pub fn out<E: Edge<From = N>>(self) -> Result<Walk<'a, E::To>> {
909 let at = self.g.frontier::<E>(&self.at, Dir::Out)?;
910 Ok(Walk {
911 g: self.g,
912 at,
913 marker: PhantomData,
914 })
915 }
916
917 /// Follow every edge of this kind backwards.
918 ///
919 /// # Errors
920 ///
921 /// [`Code::Invalid`] if called from inside a callback holding this database.
922 pub fn incoming<E: Edge<To = N>>(self) -> Result<Walk<'a, E::From>> {
923 let at = self.g.frontier::<E>(&self.at, Dir::In)?;
924 Ok(Walk {
925 g: self.g,
926 at,
927 marker: PhantomData,
928 })
929 }
930
931 /// Keep only the nodes that pass.
932 ///
933 /// The node is read to be looked at, so this is the expensive filter and it
934 /// belongs at the end of a walk rather than in the middle of one.
935 ///
936 /// # Errors
937 ///
938 /// [`Code::Corrupt`] if a node on the walk is not an `N`.
939 pub fn filter(self, mut keep: impl FnMut(&N) -> bool) -> Result<Walk<'a, N>> {
940 let mut at = Vec::with_capacity(self.at.len());
941 self.g.read(|s| {
942 for raw in &self.at {
943 if let Some(doc) = s.g.node(*raw)
944 && keep(&N::read(doc)?)
945 {
946 at.push(*raw);
947 }
948 }
949 Ok(())
950 })?;
951 Ok(Walk {
952 g: self.g,
953 at,
954 marker: PhantomData,
955 })
956 }
957
958 /// How many nodes the walk is standing on.
959 #[must_use]
960 pub fn len(&self) -> usize {
961 self.at.len()
962 }
963
964 /// Whether the walk reached nothing.
965 #[must_use]
966 pub fn is_empty(&self) -> bool {
967 self.at.is_empty()
968 }
969
970 /// The nodes the walk reached, as handles.
971 #[must_use]
972 pub fn ids(self) -> Vec<Id<N>> {
973 ids(self.at)
974 }
975
976 /// The nodes the walk reached, read out.
977 ///
978 /// A probe per node, which is what `11` section 4 prices this at and the
979 /// reason it is a separate call rather than what a hop hands back.
980 ///
981 /// # Errors
982 ///
983 /// [`Code::Corrupt`] if a node on the walk is not an `N`.
984 pub fn nodes(self) -> Result<Vec<N>> {
985 self.g.read(|s| {
986 let mut out = Vec::with_capacity(self.at.len());
987 for raw in &self.at {
988 if let Some(doc) = s.g.node(*raw) {
989 out.push(N::read(doc)?);
990 }
991 }
992 Ok(out)
993 })
994 }
995}
996
997fn ids<N>(raw: Vec<u64>) -> Vec<Id<N>> {
998 raw.into_iter().map(Id::new).collect()
999}
1000
1001/// A node's key in the lookup table: its label and then the id in the struct.
1002///
1003/// The label is in front because two node types can hold the same id and they
1004/// are two nodes.
1005fn tagged(kind: u32, key: &[u8]) -> Vec<u8> {
1006 let mut out = Vec::with_capacity(4 + key.len());
1007 out.extend_from_slice(&kind.to_le_bytes());
1008 out.extend_from_slice(key);
1009 out
1010}
1011
1012fn gone<N: Node>(raw: u64) -> Error {
1013 Error::fmt(
1014 Code::NotFound,
1015 format_args!(
1016 "{}#{raw} is not in this graph, so there is nothing to put an edge on. Add the node first, or use the id that add() answered with",
1017 N::LABEL
1018 ),
1019 )
1020}