Skip to main content

oxgraph_postgres/artifact/
metadata.rs

1//! Postgres-owned snapshot metadata section ([`SNAPSHOT_KIND_PG_METADATA`]).
2
3use oxgraph_layout_util::SnapshotWidth;
4use oxgraph_snapshot::{PlanError, Snapshot, SnapshotError, SnapshotWriter};
5use zerocopy::{
6    FromBytes, Immutable, IntoBytes, KnownLayout,
7    byteorder::{LE, U32, U64},
8};
9
10/// Section kind for serialized catalog blobs owned by Postgres.
11pub const SNAPSHOT_KIND_PG_CATALOG: u32 = 0x0200;
12
13/// 4-aligned base section kind for Postgres-owned inbound CSC offsets; the
14/// persisted kind is `BASE | WIDTH_CODE` for the offsets word width.
15pub const SNAPSHOT_KIND_PG_INBOUND_OFFSETS_BASE: u32 = 0x0204;
16
17/// 4-aligned base section kind for Postgres-owned inbound CSC targets.
18pub const SNAPSHOT_KIND_PG_INBOUND_TARGETS_BASE: u32 = 0x0208;
19
20/// Postgres-owned inbound CSC offsets section (`u32` words).
21///
22/// The inbound (reverse) adjacency is persisted in the Postgres band rather
23/// than the CSR band so that forward and inbound views never collide on a
24/// section kind. The physical layout is CSR-on-transposed-edges; the storage-
25/// agnostic [`CscSnapshotGraph`](oxgraph_csc::CscSnapshotGraph) reads it through
26/// `from_snapshot_with_kinds` using these kinds. The engine pins the `u32`
27/// width.
28pub const SNAPSHOT_KIND_PG_INBOUND_OFFSETS_U32: u32 =
29    SNAPSHOT_KIND_PG_INBOUND_OFFSETS_BASE | <u32 as SnapshotWidth>::WIDTH_CODE;
30
31/// Postgres-owned inbound CSC targets section (`u32` words).
32pub const SNAPSHOT_KIND_PG_INBOUND_TARGETS_U32: u32 =
33    SNAPSHOT_KIND_PG_INBOUND_TARGETS_BASE | <u32 as SnapshotWidth>::WIDTH_CODE;
34
35/// Section kind for Postgres engine metadata (`0x0200` reserved range).
36///
37/// Sits above every derived inbound CSC kind so the artifact's
38/// forward-then-inbound-then-metadata emission stays strictly ascending.
39pub const SNAPSHOT_KIND_PG_METADATA: u32 = 0x020C;
40
41/// Fixed-layout Postgres metadata stored in snapshot section payloads.
42#[derive(Clone, Copy, Debug, PartialEq, Eq, FromBytes, Immutable, IntoBytes, KnownLayout)]
43#[repr(C)]
44pub struct PostgresMetadata {
45    /// Metadata schema version.
46    pub version: U32<LE>,
47    /// Bit flags (`READ_ONLY`, `HAS_REVERSE_INDEX`, …).
48    pub flags: U32<LE>,
49    /// Build timestamp in Unix seconds (semantic-free).
50    pub built_at_unix: U64<LE>,
51    /// Node count at build time.
52    pub node_count: U32<LE>,
53    /// Edge count at build time.
54    pub edge_count: U32<LE>,
55}
56
57impl PostgresMetadata {
58    /// Metadata schema version written by this library.
59    pub const VERSION: u32 = 1;
60
61    /// Flag indicating the artifact was published read-only.
62    pub const FLAG_READ_ONLY: u32 = 1;
63
64    /// Flag indicating inbound CSC sections
65    /// ([`SNAPSHOT_KIND_PG_INBOUND_OFFSETS_U32`] /
66    /// [`SNAPSHOT_KIND_PG_INBOUND_TARGETS_U32`]) are present.
67    pub const FLAG_HAS_REVERSE_INDEX: u32 = 2;
68
69    /// Creates metadata for a freshly built artifact.
70    #[must_use]
71    pub const fn new(
72        node_count: u32,
73        edge_count: u32,
74        built_at_unix: u64,
75        read_only: bool,
76    ) -> Self {
77        let mut flags = 0_u32;
78        if read_only {
79            flags |= Self::FLAG_READ_ONLY;
80        }
81        Self {
82            version: U32::new(Self::VERSION),
83            flags: U32::new(flags),
84            built_at_unix: U64::new(built_at_unix),
85            node_count: U32::new(node_count),
86            edge_count: U32::new(edge_count),
87        }
88    }
89
90    /// Returns metadata with [`Self::FLAG_HAS_REVERSE_INDEX`] set.
91    #[must_use]
92    pub const fn with_reverse_index(mut self) -> Self {
93        self.flags = U32::new(self.flags.get() | Self::FLAG_HAS_REVERSE_INDEX);
94        self
95    }
96
97    /// Returns whether the read-only flag is set.
98    #[must_use]
99    pub const fn is_read_only(self) -> bool {
100        self.flags.get() & Self::FLAG_READ_ONLY != 0
101    }
102
103    /// Returns whether inbound CSC sections are required at engine open.
104    #[must_use]
105    pub const fn has_reverse_index(self) -> bool {
106        self.flags.get() & Self::FLAG_HAS_REVERSE_INDEX != 0
107    }
108}
109
110/// Errors while reading Postgres-owned snapshot sections.
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub enum PostgresSectionError {
113    /// Underlying snapshot validation failed.
114    Snapshot(SnapshotError),
115    /// Expected Postgres section was absent.
116    MissingSection,
117    /// Section payload could not be interpreted.
118    Malformed(alloc::string::String),
119}
120
121/// Reads [`PostgresMetadata`] from a snapshot's Postgres section.
122///
123/// # Errors
124///
125/// Returns [`PostgresSectionError`] when the section is missing or malformed.
126///
127/// # Performance
128///
129/// This function is `O(s)`.
130pub(super) fn read_postgres_metadata(
131    snapshot: &Snapshot<'_>,
132) -> Result<PostgresMetadata, PostgresSectionError> {
133    let section = snapshot
134        .section(SNAPSHOT_KIND_PG_METADATA)
135        .ok_or(PostgresSectionError::MissingSection)?;
136    PostgresMetadata::ref_from_bytes(section.bytes())
137        .map_err(|error| {
138            PostgresSectionError::Malformed(alloc::format!("postgres metadata layout: {error}"))
139        })
140        .copied()
141}
142
143/// Writes the Postgres metadata section into a snapshot writer.
144///
145/// # Errors
146///
147/// Returns [`SnapshotError`] when planning or encoding fails.
148///
149/// # Performance
150///
151/// This function is `O(1)`.
152pub(super) fn write_postgres_metadata_section(
153    writer: &mut SnapshotWriter,
154    metadata: &PostgresMetadata,
155) -> Result<(), PlanError> {
156    writer.section_typed(
157        SNAPSHOT_KIND_PG_METADATA,
158        PostgresMetadata::VERSION,
159        core::slice::from_ref(metadata),
160    )
161}