Skip to main content

oxgraph_postgres/artifact/
sections.rs

1//! Postgres-owned snapshot section assembly (inbound CSC + metadata).
2
3use oxgraph_csr::CsrSnapshotIndex;
4use oxgraph_layout_util::crc32c_append;
5use oxgraph_snapshot::{Snapshot, SnapshotWriter};
6
7use super::metadata::{
8    PostgresMetadata, SNAPSHOT_KIND_PG_INBOUND_OFFSETS_U32, SNAPSHOT_KIND_PG_INBOUND_TARGETS_U32,
9    write_postgres_metadata_section,
10};
11use crate::error::{BuildError, PostgresGraphError};
12
13/// Appends or replaces Postgres-owned sections on CSR topology bytes.
14///
15/// Forward topology sections are preserved. When `inbound_topology_bytes` is
16/// [`Some`], CSR offsets/targets are copied into the inbound section kinds
17/// [`SNAPSHOT_KIND_PG_INBOUND_OFFSETS_U32`] /
18/// [`SNAPSHOT_KIND_PG_INBOUND_TARGETS_U32`].
19///
20/// # Errors
21///
22/// Returns [`PostgresGraphError::Snapshot`] when input bytes are invalid, or
23/// [`PostgresGraphError::Build`] when re-encoding fails.
24///
25/// # Performance
26///
27/// This function is `O(b + s)`.
28pub fn attach_postgres_sections(
29    forward_topology_bytes: &[u8],
30    inbound_topology_bytes: Option<&[u8]>,
31    metadata: &PostgresMetadata,
32) -> Result<Vec<u8>, PostgresGraphError> {
33    let forward = Snapshot::open(forward_topology_bytes)?;
34    let inbound = inbound_topology_bytes.map(Snapshot::open).transpose()?;
35    let copied = forward
36        .sections()
37        .filter(|section| !is_postgres_owned_kind(section.kind()))
38        .count();
39    let inbound_count = if inbound.is_some() { 2 } else { 0 };
40    let mut writer = SnapshotWriter::new(copied + inbound_count + 1, crc32c_append)?;
41    for section in forward.sections() {
42        if is_postgres_owned_kind(section.kind()) {
43            continue;
44        }
45        writer.section_bytes(
46            section.kind(),
47            section.version(),
48            alignment_log2_from_section(&section),
49            section.bytes(),
50        )?;
51    }
52    if let Some(inbound_snapshot) = inbound.as_ref() {
53        copy_csr_section(
54            inbound_snapshot,
55            u32::OFFSETS_KIND,
56            SNAPSHOT_KIND_PG_INBOUND_OFFSETS_U32,
57            &mut writer,
58        )?;
59        copy_csr_section(
60            inbound_snapshot,
61            u32::TARGETS_KIND,
62            SNAPSHOT_KIND_PG_INBOUND_TARGETS_U32,
63            &mut writer,
64        )?;
65    }
66    write_postgres_metadata_section(&mut writer, metadata)?;
67    writer.finish().map_err(PostgresGraphError::from)
68}
69
70/// Appends or replaces the Postgres metadata section on CSR topology bytes.
71///
72/// Prefer [`attach_postgres_sections`] when inbound CSC bytes are available.
73///
74/// # Errors
75///
76/// Returns [`PostgresGraphError::Snapshot`] when input bytes are invalid, or
77/// [`PostgresGraphError::Build`] when re-encoding fails.
78///
79/// # Performance
80///
81/// This function is `O(b + s)`.
82pub fn attach_metadata(
83    base_bytes: &[u8],
84    metadata: &PostgresMetadata,
85) -> Result<Vec<u8>, PostgresGraphError> {
86    attach_postgres_sections(base_bytes, None, metadata)
87}
88
89/// Copies one CSR topology section into a Postgres-owned section kind.
90fn copy_csr_section(
91    snapshot: &Snapshot<'_>,
92    source_kind: u32,
93    dest_kind: u32,
94    writer: &mut SnapshotWriter,
95) -> Result<(), PostgresGraphError> {
96    let section = snapshot
97        .section(source_kind)
98        .ok_or(PostgresGraphError::Build(BuildError::MissingCsrSection {
99            kind: source_kind,
100        }))?;
101    writer.section_bytes(
102        dest_kind,
103        section.version(),
104        alignment_log2_from_section(&section),
105        section.bytes(),
106    )?;
107    Ok(())
108}
109
110/// Returns whether `kind` is in the Postgres-owned reserved section range.
111fn is_postgres_owned_kind(kind: u32) -> bool {
112    (0x0200..0x0300).contains(&kind)
113}
114
115/// Derives snapshot writer alignment metadata from a borrowed section view.
116fn alignment_log2_from_section(section: &oxgraph_snapshot::Section<'_>) -> u8 {
117    let alignment = section.declared_alignment();
118    if alignment <= 1 {
119        0
120    } else {
121        u8::try_from(alignment.trailing_zeros()).unwrap_or(u8::MAX)
122    }
123}