Skip to main content

oxgraph_property/
export.rs

1//! Shared snapshot-export helpers for topology builders that attach property
2//! layers.
3//!
4//! Every layout exporter (CSR, BCSR) validates its property layers against the
5//! topology counts and then appends the SAME section tail — property
6//! descriptors, property data, identity modes, one explicit identity map — in
7//! the canonical (ascending-kind) order the container mandates. The
8//! layout-specific parts (which counts are required, which identity modes
9//! apply, which map kind carries the explicit map) stay in each layout crate;
10//! the mechanics live here once.
11
12use oxgraph_layout_util::SnapshotWidth;
13use oxgraph_snapshot::{PlanError, SnapshotWriter};
14
15use crate::{
16    EncodedPropertySnapshot, IdFamily, IdentityModeRecord, PropertyIndex, PropertyLayer,
17    PropertySnapshotMetaWord, SNAPSHOT_PROPERTY_VERSION,
18};
19
20/// A property layer shorter than the topology requires.
21///
22/// Carries the family, the required minimum row count, and the offending
23/// layer's length; layout crates map this into their typed
24/// `PropertyLayerTooShort` build-error variant.
25///
26/// # Performance
27///
28/// Copying this value is `O(1)`.
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30pub struct LayerLengthError {
31    /// Family whose layer fell short.
32    pub id_family: IdFamily,
33    /// Minimum row count the topology requires.
34    pub required: usize,
35    /// The offending layer's actual length.
36    pub actual: usize,
37}
38
39impl core::fmt::Display for LayerLengthError {
40    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41        write!(
42            formatter,
43            "{:?} property layer holds {} rows but the topology requires {}",
44            self.id_family, self.actual, self.required
45        )
46    }
47}
48
49impl core::error::Error for LayerLengthError {}
50
51/// Validates that every layer holds at least `required` rows for `id_family`.
52///
53/// # Errors
54///
55/// Returns [`LayerLengthError`] naming the first layer shorter than
56/// `required`.
57///
58/// # Performance
59///
60/// This function is `O(layers.len())`.
61pub fn validate_layer_lengths<Id, I>(
62    layers: &[PropertyLayer<Id, I>],
63    id_family: IdFamily,
64    required: usize,
65) -> Result<(), LayerLengthError>
66where
67    I: PropertyIndex,
68{
69    for layer in layers {
70        if layer.len() < required {
71            return Err(LayerLengthError {
72                id_family,
73                required,
74                actual: layer.len(),
75            });
76        }
77    }
78    Ok(())
79}
80
81/// Appends the canonical property-export section tail: the encoded property
82/// descriptors and data, then the identity modes and the explicit identity
83/// map under `identity_map_kind`.
84///
85/// The order follows the ascending kind values inside the property band
86/// (descriptors < data < identity modes < identity maps), which the
87/// container mandates table-wide.
88///
89/// # Errors
90///
91/// Returns [`PlanError`] when section planning rejects a section
92/// (non-ascending kind, count, or alignment).
93///
94/// # Performance
95///
96/// This function is `O(identity map len + encoded property bytes)`.
97pub fn append_identity_and_property_sections<W, MapW>(
98    writer: &mut SnapshotWriter,
99    identity_modes: &[IdentityModeRecord<W>],
100    identity_map_kind: u32,
101    identity_map: &[MapW],
102    encoded: &EncodedPropertySnapshot,
103) -> Result<(), PlanError>
104where
105    W: PropertySnapshotMetaWord,
106    MapW: SnapshotWidth,
107{
108    writer.section_bytes(
109        W::PROPERTY_DESCRIPTORS_KIND,
110        SNAPSHOT_PROPERTY_VERSION,
111        0,
112        &encoded.descriptors,
113    )?;
114    writer.section_bytes(
115        W::PROPERTY_DATA_KIND,
116        SNAPSHOT_PROPERTY_VERSION,
117        0,
118        &encoded.data,
119    )?;
120    writer.section_little_endian(
121        W::IDENTITY_MODES_KIND,
122        SNAPSHOT_PROPERTY_VERSION,
123        identity_modes,
124    )?;
125    writer.section_widths(identity_map_kind, SNAPSHOT_PROPERTY_VERSION, identity_map)?;
126    Ok(())
127}