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}