Skip to main content

oxgraph_csc/
lib.rs

1//! Borrowed compressed-sparse-column (inbound) graph views.
2//!
3//! Incoming adjacency is stored as CSR-on-transposed-edges in OXGTOPO sections.
4//! This crate is separate from [`oxgraph_csr`] so forward and inbound views
5//! cannot be mixed at the type level. The view is storage-agnostic: it does not
6//! own any section-kind constants and reads whatever offsets/targets kinds the
7//! caller supplies through [`CscSnapshotGraph::from_snapshot_with_kinds`]. The
8//! storage layer that persists the inbound index (for example the Postgres
9//! engine) owns the section-kind constants and the section version.
10//!
11//! # Performance
12//!
13//! Opening a snapshot-backed view is `O(s + n + m)` once per engine load.
14#![no_std]
15
16use core::fmt;
17
18use oxgraph_csr::{CsrEdgeId, CsrGraph, CsrNodeId, CsrSnapshotError, CsrSnapshotIndex};
19use oxgraph_graph::{ElementPredecessors, OutgoingNeighborsGraph, TopologyCounts};
20use oxgraph_layout_util::{NodeAxis, SnapshotWidth};
21use oxgraph_snapshot::Snapshot;
22use oxgraph_topology::TopologyBase;
23
24/// Dense node id in an inbound CSC view.
25///
26/// Alias of the shared [`LocalId`](oxgraph_layout_util::LocalId) branded by the
27/// node axis, parameterized over the node index width `N`.
28///
29/// # Performance
30///
31/// Copying, comparing, ordering, hashing, and debug-formatting are `O(1)`.
32pub type CscNodeId<N = u32> = oxgraph_layout_util::LocalId<NodeAxis, N>;
33
34/// Errors while opening inbound CSC sections from a snapshot.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum CscSnapshotError<N = u32, E = u32> {
37    /// Underlying CSR layout validation failed.
38    Layout(CsrSnapshotError<N, E>),
39}
40
41impl<N: fmt::Display, E: fmt::Display> fmt::Display for CscSnapshotError<N, E> {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match self {
44            Self::Layout(error) => write!(f, "inbound layout error: {error}"),
45        }
46    }
47}
48
49impl<N, E> core::error::Error for CscSnapshotError<N, E>
50where
51    N: fmt::Debug + fmt::Display,
52    E: fmt::Debug + fmt::Display,
53{
54}
55
56impl<N, E> From<CsrSnapshotError<N, E>> for CscSnapshotError<N, E> {
57    fn from(error: CsrSnapshotError<N, E>) -> Self {
58        Self::Layout(error)
59    }
60}
61
62/// Snapshot-backed inbound CSC view for node width `N` and edge width `E`.
63///
64/// # Performance
65///
66/// `perf: unspecified`; this alias carries no runtime cost.
67pub type CscInnerGraph<'view, N = u32, E = u32> = CsrGraph<
68    'view,
69    N,
70    E,
71    <E as SnapshotWidth>::LittleEndianWord,
72    <N as SnapshotWidth>::LittleEndianWord,
73>;
74
75/// Borrowed inbound adjacency (transpose-CSR / CSC layout).
76///
77/// `N` is the node index width and `E` is the edge index width; both default to
78/// `u32` for the common engine configuration.
79///
80/// # Performance
81///
82/// Per-node predecessor enumeration is `O(k)` for `k` incoming edges.
83#[derive(Clone, Copy)]
84pub struct CscSnapshotGraph<'view, N = u32, E = u32>(CscInnerGraph<'view, N, E>)
85where
86    N: CsrSnapshotIndex,
87    E: CsrSnapshotIndex;
88
89impl<N, E> fmt::Debug for CscSnapshotGraph<'_, N, E>
90where
91    N: CsrSnapshotIndex,
92    E: CsrSnapshotIndex,
93{
94    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
95        formatter
96            .debug_struct("CscSnapshotGraph")
97            .field("node_count", &self.node_count())
98            .field("relation_count", &self.relation_count())
99            .finish()
100    }
101}
102
103impl<'view, N, E> CscSnapshotGraph<'view, N, E>
104where
105    N: CsrSnapshotIndex,
106    E: CsrSnapshotIndex,
107{
108    /// Opens inbound sections using explicit snapshot section kinds.
109    ///
110    /// Reuses [`CsrGraph::from_snapshot_with_kinds`] over the transposed-edge
111    /// layout, so callers select whichever band their storage layer reserves
112    /// for the inbound index. CSC owns no section-kind constants.
113    ///
114    /// # Errors
115    ///
116    /// Returns [`CscSnapshotError`] when sections are missing, have the wrong
117    /// version, or fail validation.
118    ///
119    /// # Performance
120    ///
121    /// This function is `O(s + n + m)`.
122    pub fn from_snapshot_with_kinds(
123        snapshot: &Snapshot<'view>,
124        offsets_kind: u32,
125        targets_kind: u32,
126        version: u32,
127    ) -> Result<Self, CscSnapshotError<N, E>> {
128        let inner = CscInnerGraph::<'view, N, E>::from_snapshot_with_kinds(
129            snapshot,
130            offsets_kind,
131            targets_kind,
132            version,
133        )?;
134        Ok(Self(inner))
135    }
136
137    /// Returns the inner CSR graph backing this inbound view.
138    #[must_use]
139    pub const fn inner(&self) -> &CscInnerGraph<'view, N, E> {
140        &self.0
141    }
142
143    /// Returns the number of nodes in this inbound view.
144    ///
145    /// # Performance
146    ///
147    /// This method is `O(1)`.
148    #[must_use]
149    pub fn node_count(&self) -> usize {
150        self.0.element_count()
151    }
152
153    /// Returns the number of edges in this inbound view.
154    ///
155    /// # Performance
156    ///
157    /// This method is `O(1)`.
158    #[must_use]
159    pub fn relation_count(&self) -> usize {
160        self.0.relation_count()
161    }
162
163    /// Returns an iterator over predecessor node ids for `target`.
164    ///
165    /// # Performance
166    ///
167    /// This method is `O(1)` to create and `O(k)` to yield `k` predecessors.
168    pub fn predecessors(
169        &self,
170        target: CscNodeId<N>,
171    ) -> impl ExactSizeIterator<Item = CscNodeId<N>> + '_ {
172        self.0
173            .outgoing_neighbors(CsrNodeId::new(target.get()))
174            .map(|id| CscNodeId::new(id.get()))
175    }
176
177    /// Walks predecessor node ids for `target` via the CSC source slice.
178    ///
179    /// Stops early when `visit` returns `true`.
180    ///
181    /// # Performance
182    ///
183    /// This method is `O(k)` for `k` predecessors with no iterator adapters.
184    pub fn for_each_predecessor(
185        &self,
186        target: CscNodeId<N>,
187        mut visit: impl FnMut(CscNodeId<N>) -> bool,
188    ) -> bool {
189        self.0
190            .for_each_out_target(CsrNodeId::new(target.get()), |id| {
191                visit(CscNodeId::new(id.get()))
192            })
193    }
194}
195
196/// Iterator over predecessor node ids in one inbound row.
197pub struct CscPredecessors<'view, N = u32, E = u32>
198where
199    N: CsrSnapshotIndex + 'view,
200    E: CsrSnapshotIndex + 'view,
201{
202    /// Underlying CSR out-neighbor iterator for the transposed inbound row.
203    inner: <CscInnerGraph<'view, N, E> as OutgoingNeighborsGraph>::OutNeighbors<'view>,
204}
205
206impl<N, E> Iterator for CscPredecessors<'_, N, E>
207where
208    N: CsrSnapshotIndex,
209    E: CsrSnapshotIndex,
210{
211    type Item = CscNodeId<N>;
212
213    fn next(&mut self) -> Option<Self::Item> {
214        self.inner.next().map(|id| CscNodeId::new(id.get()))
215    }
216
217    fn size_hint(&self) -> (usize, Option<usize>) {
218        self.inner.size_hint()
219    }
220}
221
222impl<N, E> ExactSizeIterator for CscPredecessors<'_, N, E>
223where
224    N: CsrSnapshotIndex,
225    E: CsrSnapshotIndex,
226{
227}
228
229impl<N, E> TopologyBase for CscSnapshotGraph<'_, N, E>
230where
231    N: CsrSnapshotIndex,
232    E: CsrSnapshotIndex,
233{
234    type ElementId = CscNodeId<N>;
235    type RelationId = CsrEdgeId<E>;
236}
237
238impl<N, E> TopologyCounts for CscSnapshotGraph<'_, N, E>
239where
240    N: CsrSnapshotIndex,
241    E: CsrSnapshotIndex,
242{
243    fn element_count(&self) -> usize {
244        self.0.element_count()
245    }
246
247    fn relation_count(&self) -> usize {
248        self.0.relation_count()
249    }
250}
251
252impl<N, E> ElementPredecessors for CscSnapshotGraph<'_, N, E>
253where
254    N: CsrSnapshotIndex,
255    E: CsrSnapshotIndex,
256{
257    type Predecessors<'view>
258        = CscPredecessors<'view, N, E>
259    where
260        Self: 'view;
261
262    fn element_predecessors(&self, element: CscNodeId<N>) -> Self::Predecessors<'_> {
263        CscPredecessors {
264            inner: self.0.outgoing_neighbors(CsrNodeId::new(element.get())),
265        }
266    }
267}
268
269#[cfg(kani)]
270mod proofs;