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