Skip to main content

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}