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}