#![expect(
clippy::redundant_pub_crate,
reason = "kernel entrypoints are crate-internal but live in a private parent module"
)]
mod kernel;
mod profile;
mod scratch;
mod session;
use core::num::{NonZeroU32, NonZeroUsize};
use kernel::{KernelOutcome, run_bfs_multi};
use profile::TraverseMode;
#[expect(
clippy::redundant_pub_crate,
reason = "re-exported to engine and builder"
)]
pub(crate) use scratch::TraverseScratch;
use session::TraverseSession;
use crate::{config::Config, error::PostgresGraphError};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TraversalDirection {
#[default]
Out,
In,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TraverseLimits {
pub result_limit: NonZeroUsize,
pub max_depth: Option<NonZeroU32>,
}
impl TraverseLimits {
#[must_use]
pub const fn bounded(result_limit: NonZeroUsize) -> Self {
Self {
result_limit,
max_depth: None,
}
}
pub fn capped_by(self, config: &Config) -> Result<Self, PostgresGraphError> {
let cap = config.traverse_limit as usize;
let capped = core::cmp::min(self.result_limit.get(), cap);
let result_limit = NonZeroUsize::new(capped).ok_or(crate::error::QueryError::LimitZero)?;
Ok(Self {
result_limit,
max_depth: self.max_depth,
})
}
}
pub(crate) fn traverse_core(
engine: &mut crate::engine::Engine,
seeds: &[u32],
limits: TraverseLimits,
direction: TraversalDirection,
mode: TraverseMode,
) -> Result<KernelTraversalOutcome, PostgresGraphError> {
let session = TraverseSession::open(engine, seeds, limits, direction, mode)?;
match run_bfs_multi(engine, &session)? {
KernelOutcome::Nodes(nodes) => Ok(KernelTraversalOutcome::Nodes(nodes)),
KernelOutcome::Count(count) => Ok(KernelTraversalOutcome::Count(count)),
}
}
pub(crate) enum KernelTraversalOutcome {
Nodes(alloc::vec::Vec<u32>),
Count(usize),
}
pub(crate) fn traverse_core_collect(
engine: &mut crate::engine::Engine,
seeds: &[u32],
limits: TraverseLimits,
direction: TraversalDirection,
) -> Result<alloc::vec::Vec<u32>, PostgresGraphError> {
match traverse_core(engine, seeds, limits, direction, TraverseMode::Collect)? {
KernelTraversalOutcome::Nodes(nodes) => Ok(nodes),
KernelTraversalOutcome::Count(_) => Err(crate::error::QueryError::InternalInvariant(
"expected nodes from collect traversal",
)
.into()),
}
}
pub(crate) fn traverse_core_count(
engine: &mut crate::engine::Engine,
seeds: &[u32],
limits: TraverseLimits,
direction: TraversalDirection,
) -> Result<usize, PostgresGraphError> {
match traverse_core(engine, seeds, limits, direction, TraverseMode::Count)? {
KernelTraversalOutcome::Count(count) => Ok(count),
KernelTraversalOutcome::Nodes(_) => Err(crate::error::QueryError::InternalInvariant(
"expected count from count-only traversal",
)
.into()),
}
}