Skip to main content

oxgraph_csc/
lib.rs

1//! Borrowed compressed-sparse-column (inbound) graph views.
2//!
3//! Incoming adjacency is stored in Postgres-owned OXGTOPO sections (`0x0201` /
4//! `0x0202`). The physical layout matches CSR on transposed edges; this crate
5//! is separate from [`oxgraph_csr`] so forward and inbound views cannot be
6//! mixed at the type level.
7//!
8//! # Performance
9//!
10//! Opening a snapshot-backed view is `O(s + n + m)` once per engine load.
11#![no_std]
12
13use core::fmt;
14
15use oxgraph_csr::{CsrEdgeId, CsrGraph, CsrNodeId, CsrSnapshotError, CsrSnapshotIndex};
16use oxgraph_graph::{ElementPredecessors, OutgoingNeighborsGraph, TopologyCounts};
17use oxgraph_snapshot::Snapshot;
18use oxgraph_topology::TopologyBase;
19
20/// Postgres-owned inbound offsets section (`0x0201`).
21pub const SNAPSHOT_KIND_PG_INBOUND_OFFSETS_U32: u32 = 0x0201;
22
23/// Postgres-owned inbound targets section (`0x0202`).
24pub const SNAPSHOT_KIND_PG_INBOUND_TARGETS_U32: u32 = 0x0202;
25
26/// Dense node id in an inbound CSC view.
27#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct CscNodeId(pub u32);
29
30/// Errors while opening inbound CSC sections from a snapshot.
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum CscSnapshotError {
33    /// Underlying CSR layout validation failed.
34    Layout(CsrSnapshotError<u32, u32>),
35}
36
37impl fmt::Display for CscSnapshotError {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        match self {
40            Self::Layout(error) => write!(f, "inbound layout error: {error}"),
41        }
42    }
43}
44
45impl core::error::Error for CscSnapshotError {}
46
47impl From<CsrSnapshotError<u32, u32>> for CscSnapshotError {
48    fn from(error: CsrSnapshotError<u32, u32>) -> Self {
49        Self::Layout(error)
50    }
51}
52
53/// Snapshot-backed inbound CSC view for `u32` node and edge indices.
54///
55/// # Performance
56///
57/// `perf: unspecified`; this alias carries no runtime cost.
58pub type CscInnerGraph<'view> = CsrGraph<
59    'view,
60    u32,
61    u32,
62    <u32 as CsrSnapshotIndex>::LittleEndianWord,
63    <u32 as CsrSnapshotIndex>::LittleEndianWord,
64>;
65
66/// Borrowed inbound adjacency (transpose-CSR / CSC layout).
67///
68/// # Performance
69///
70/// Per-node predecessor enumeration is `O(k)` for `k` incoming edges.
71#[derive(Clone, Copy, Debug)]
72pub struct CscSnapshotGraph<'view>(CscInnerGraph<'view>);
73
74impl<'view> CscSnapshotGraph<'view> {
75    /// Opens inbound sections from a validated [`Snapshot`].
76    ///
77    /// # Errors
78    ///
79    /// Returns [`CscSnapshotError`] when sections are missing or fail validation.
80    ///
81    /// # Performance
82    ///
83    /// This function is `O(s + n + m)`.
84    pub fn from_snapshot(snapshot: &Snapshot<'view>) -> Result<Self, CscSnapshotError> {
85        Self::from_snapshot_with_kinds(
86            snapshot,
87            SNAPSHOT_KIND_PG_INBOUND_OFFSETS_U32,
88            SNAPSHOT_KIND_PG_INBOUND_TARGETS_U32,
89        )
90    }
91
92    /// Opens inbound sections using explicit snapshot section kinds.
93    ///
94    /// # Errors
95    ///
96    /// Returns [`CscSnapshotError`] when sections are missing or fail validation.
97    ///
98    /// # Performance
99    ///
100    /// This function is `O(s + n + m)`.
101    pub fn from_snapshot_with_kinds(
102        snapshot: &Snapshot<'view>,
103        offsets_kind: u32,
104        targets_kind: u32,
105    ) -> Result<Self, CscSnapshotError> {
106        let offsets_section = snapshot
107            .section(offsets_kind)
108            .ok_or(CsrSnapshotError::MissingOffsets)?;
109        let targets_section = snapshot
110            .section(targets_kind)
111            .ok_or(CsrSnapshotError::MissingTargets)?;
112
113        let offsets: &'view [<u32 as CsrSnapshotIndex>::LittleEndianWord] = offsets_section
114            .try_as_slice()
115            .map_err(CsrSnapshotError::OffsetsView)?;
116        let targets: &'view [<u32 as CsrSnapshotIndex>::LittleEndianWord] = targets_section
117            .try_as_slice()
118            .map_err(CsrSnapshotError::TargetsView)?;
119
120        if offsets.is_empty() {
121            return Err(CsrSnapshotError::OffsetsEmpty.into());
122        }
123
124        let node_count_usize = offsets.len() - 1;
125        let node_count =
126            u32::try_from(node_count_usize).map_err(|_| CsrSnapshotError::NodeCountOverflow {
127                offsets_len: offsets.len(),
128            })?;
129
130        let inner = CscInnerGraph::validate(node_count, offsets, targets)
131            .map_err(|error| CscSnapshotError::Layout(CsrSnapshotError::Csr(error)))?;
132        Ok(Self(inner))
133    }
134
135    /// Returns the inner CSR graph backing this inbound view.
136    #[must_use]
137    pub const fn inner(&self) -> &CscInnerGraph<'view> {
138        &self.0
139    }
140
141    /// Returns the number of nodes in this inbound view.
142    ///
143    /// # Performance
144    ///
145    /// This method is `O(1)`.
146    #[must_use]
147    pub fn node_count(&self) -> usize {
148        self.0.element_count()
149    }
150
151    /// Returns the number of edges in this inbound view.
152    ///
153    /// # Performance
154    ///
155    /// This method is `O(1)`.
156    #[must_use]
157    pub fn relation_count(&self) -> usize {
158        self.0.relation_count()
159    }
160
161    /// Returns an iterator over predecessor node ids for `target`.
162    ///
163    /// # Performance
164    ///
165    /// This method is `O(1)` to create and `O(k)` to yield `k` predecessors.
166    #[must_use]
167    pub fn predecessors(&self, target: u32) -> impl ExactSizeIterator<Item = u32> + '_ {
168        self.0.outgoing_neighbors(CsrNodeId(target)).map(|id| id.0)
169    }
170
171    /// Walks predecessor node ids for `target` via the CSC source slice.
172    ///
173    /// Stops early when `visit` returns `true`.
174    ///
175    /// # Performance
176    ///
177    /// This method is `O(k)` for `k` predecessors with no iterator adapters.
178    pub fn for_each_predecessor(&self, target: u32, mut visit: impl FnMut(u32) -> bool) -> bool {
179        self.0
180            .for_each_out_target(CsrNodeId(target), |id| visit(id.0))
181    }
182}
183
184/// Iterator over predecessor node ids in one inbound row.
185pub struct CscPredecessors<'view> {
186    /// Underlying CSR out-neighbor iterator for the transposed inbound row.
187    inner: <CscInnerGraph<'view> as OutgoingNeighborsGraph>::OutNeighbors<'view>,
188}
189
190impl Iterator for CscPredecessors<'_> {
191    type Item = CscNodeId;
192
193    fn next(&mut self) -> Option<Self::Item> {
194        self.inner.next().map(|id| CscNodeId(id.0))
195    }
196
197    fn size_hint(&self) -> (usize, Option<usize>) {
198        self.inner.size_hint()
199    }
200}
201
202impl ExactSizeIterator for CscPredecessors<'_> {}
203
204impl TopologyBase for CscSnapshotGraph<'_> {
205    type ElementId = CscNodeId;
206    type RelationId = CsrEdgeId<u32>;
207}
208
209impl TopologyCounts for CscSnapshotGraph<'_> {
210    fn element_count(&self) -> usize {
211        self.0.element_count()
212    }
213
214    fn relation_count(&self) -> usize {
215        self.0.relation_count()
216    }
217}
218
219impl ElementPredecessors for CscSnapshotGraph<'_> {
220    type Predecessors<'view>
221        = CscPredecessors<'view>
222    where
223        Self: 'view;
224
225    fn element_predecessors(&self, element: CscNodeId) -> Self::Predecessors<'_> {
226        CscPredecessors {
227            inner: self.0.outgoing_neighbors(CsrNodeId(element.0)),
228        }
229    }
230}
231
232#[cfg(kani)]
233mod proofs;