Skip to main content

oxgraph_postgres/
builder.rs

1//! Engine construction: validate artifact once and attach typed topology views.
2
3use alloc::{boxed::Box, vec::Vec};
4use core::cell::{Cell, RefCell};
5
6use oxgraph_snapshot::Snapshot;
7use yoke::Yoke;
8
9use crate::{
10    artifact::read_metadata,
11    config::Config,
12    engine::{Engine, EngineCart, EngineState},
13    error::{BuildError, PostgresGraphError},
14    overlay::OverlayState,
15    topology::{GraphTopology, UniqueAdjacency},
16    traverse::TraverseScratch,
17};
18
19/// Configures and builds a loaded [`Engine`] from OXGTOPO bytes.
20#[derive(Clone, Debug, Default)]
21pub struct EngineBuilder {
22    /// Owned snapshot bytes to validate and open.
23    backing: Option<Vec<u8>>,
24    /// Overlay state applied after topology open.
25    overlay: OverlayState,
26    /// Operational configuration validated at build time.
27    config: Config,
28}
29
30impl EngineBuilder {
31    /// Creates an empty builder (no snapshot bytes yet).
32    #[must_use]
33    pub fn new() -> Self {
34        Self {
35            backing: None,
36            overlay: OverlayState::default(),
37            config: Config::default(),
38        }
39    }
40
41    /// Supplies owned snapshot bytes to load.
42    #[must_use]
43    pub fn snapshot_owned(mut self, bytes: Vec<u8>) -> Self {
44        self.backing = Some(bytes);
45        self
46    }
47
48    /// Sets overlay state applied after open.
49    #[must_use]
50    pub fn overlay(mut self, overlay: OverlayState) -> Self {
51        self.overlay = overlay;
52        self
53    }
54
55    /// Sets operational configuration validated at build time.
56    #[must_use]
57    #[expect(
58        clippy::missing_const_for_fn,
59        reason = "Config is not a const-constructible snapshot"
60    )]
61    pub fn config(mut self, config: Config) -> Self {
62        self.config = config;
63        self
64    }
65
66    /// Validates the artifact and opens forward CSR plus inbound CSC views once.
67    ///
68    /// # Errors
69    ///
70    /// Returns [`PostgresGraphError`] when bytes are missing, invalid, or layouts disagree.
71    ///
72    /// # Performance
73    ///
74    /// This function is `O(s + n + m)`; queries are `O(1)` to borrow topology afterward.
75    pub fn build(self) -> Result<Engine, PostgresGraphError> {
76        let backing = self
77            .backing
78            .ok_or(PostgresGraphError::Build(BuildError::MissingSnapshotBytes))?;
79        self.config.validate()?;
80        let snapshot = Snapshot::open(&backing)?;
81        let metadata = read_metadata(&snapshot)?;
82        let cart = Box::new(EngineCart { backing, metadata });
83        let inner = Yoke::try_attach_to_cart(cart, |cart: &EngineCart| {
84            let snapshot = Snapshot::open(cart.backing.as_slice())?;
85            let topology = GraphTopology::open(&snapshot)?;
86            Ok::<EngineState<'_>, PostgresGraphError>(EngineState { topology })
87        })?;
88        let mut overlay = self.overlay;
89        if !overlay.added_edges.is_empty() {
90            overlay.rebuild_indexes();
91        }
92        let node_count = inner.backing_cart().metadata.node_count.get() as usize;
93        let mut traverse_scratch = TraverseScratch::default();
94        traverse_scratch.resize_for_nodes(node_count);
95        Ok(Engine::from_parts(
96            inner,
97            overlay,
98            self.config,
99            traverse_scratch,
100            RefCell::new(UniqueAdjacency::default()),
101            Cell::new(false),
102        ))
103    }
104}