oxgraph_property/width.rs
1//! Property index width contracts, per-axis markers, and metadata-word codecs.
2//!
3//! Defines the sealed [`PropertyIndex`] / [`PropertySnapshotMetaWord`] width
4//! traits, the three built-in [`PropertyAxis`] markers and their [`AxisIndex`]
5//! topology-bound dispatch, the snapshot section-kind constants keyed by
6//! metadata width, and the little-endian metadata-word conversion helpers.
7
8use std::vec::Vec;
9
10use arrow_array::{PrimitiveArray, types::ArrowPrimitiveType};
11use oxgraph_layout_util::SnapshotWidth;
12use oxgraph_topology::{DenseElementIndex, DenseIncidenceIndex, DenseRelationIndex, TopologyBase};
13
14use crate::model::{IdFamily, PropertyError};
15
16/// 4-aligned base section kind for property-layer descriptors; the persisted
17/// kind is `BASE | WIDTH_CODE` for the metadata width.
18///
19/// The six property-band bases ascend by at least 4 in the canonical export
20/// order (descriptors, data, identity modes, element/relation/incidence
21/// identity maps), so every derived kind of one role sorts below every derived
22/// kind of the next role for any metadata/map width mix — the
23/// strictly-ascending order the container mandates.
24///
25/// # Performance
26///
27/// `perf: unspecified`; this is a compile-time constant.
28pub const SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_BASE: u32 = 0x0100;
29
30/// 4-aligned base section kind for Arrow IPC property-layer payloads.
31///
32/// # Performance
33///
34/// `perf: unspecified`; this is a compile-time constant.
35pub const SNAPSHOT_KIND_PROPERTY_DATA_BASE: u32 = 0x0104;
36
37/// 4-aligned base section kind for identity-mode metadata records.
38///
39/// # Performance
40///
41/// `perf: unspecified`; this is a compile-time constant.
42pub const SNAPSHOT_KIND_IDENTITY_MODES_BASE: u32 = 0x0110;
43
44/// 4-aligned base section kind for element local-to-canonical maps.
45///
46/// # Performance
47///
48/// `perf: unspecified`; this is a compile-time constant.
49pub const SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_BASE: u32 = 0x0114;
50
51/// 4-aligned base section kind for relation local-to-canonical maps.
52///
53/// # Performance
54///
55/// `perf: unspecified`; this is a compile-time constant.
56pub const SNAPSHOT_KIND_RELATION_IDENTITY_MAP_BASE: u32 = 0x0118;
57
58/// 4-aligned base section kind for incidence local-to-canonical maps.
59///
60/// # Performance
61///
62/// `perf: unspecified`; this is a compile-time constant.
63pub const SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_BASE: u32 = 0x011C;
64
65/// Internal property/identity snapshot section version.
66///
67/// # Performance
68///
69/// `perf: unspecified`; this is a compile-time constant.
70pub const SNAPSHOT_PROPERTY_VERSION: u32 = 1;
71
72/// Sealed trait modules for property width contracts.
73mod sealed {
74 /// Seals [`super::PropertyIndex`] to supported unsigned sparse widths.
75 pub trait PropertyIndex {}
76
77 /// Seals [`super::PropertySnapshotMetaWord`] to supported metadata widths.
78 pub trait PropertySnapshotMetaWord {}
79
80 /// Seals [`super::PropertyAxis`] to the three built-in axis markers.
81 pub trait PropertyAxis {}
82}
83
84/// Unsigned index width usable for sparse property indexes.
85///
86/// This is the thin Arrow-specific layer over the shared
87/// [`SnapshotWidth`](oxgraph_layout_util::SnapshotWidth) contract: the
88/// `usize`/little-endian-word conversions and [`SnapshotWidth::WIDTH_CODE`]
89/// resolve through that trait, so `I::LittleEndianWord` and `I::to_le_word`
90/// have one definition substrate-wide. This trait only adds the Arrow
91/// primitive-array vocabulary plus the `Into<u64>` diagnostic widening.
92///
93/// # Performance
94///
95/// Implementations perform conversions in `O(1)` and array construction in
96/// `O(values.len())`.
97pub trait PropertyIndex: sealed::PropertyIndex + SnapshotWidth + Into<u64> {
98 /// Arrow unsigned primitive type for sparse index arrays.
99 type ArrowType: ArrowPrimitiveType<Native = Self> + 'static;
100
101 /// Builds an Arrow primitive array from native index values.
102 ///
103 /// # Performance
104 ///
105 /// This function is `O(values.len())`.
106 fn primitive_array(values: Vec<Self>) -> PrimitiveArray<Self::ArrowType>;
107}
108
109/// Metadata/canonical-ID word width for property and identity snapshot sections.
110///
111/// Derives each width-specific section kind from the crate's 4-aligned base
112/// constants or-ed with [`SnapshotWidth::WIDTH_CODE`] (inherited through
113/// [`PropertyIndex`]), so the two-bit width encoding has one source of truth.
114///
115/// # Performance
116///
117/// Reading the kind constants is `O(1)`.
118pub trait PropertySnapshotMetaWord: sealed::PropertySnapshotMetaWord + PropertyIndex {
119 /// Property descriptor section kind for this metadata width.
120 const PROPERTY_DESCRIPTORS_KIND: u32 =
121 SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_BASE | Self::WIDTH_CODE;
122
123 /// Property data section kind for this metadata width.
124 const PROPERTY_DATA_KIND: u32 = SNAPSHOT_KIND_PROPERTY_DATA_BASE | Self::WIDTH_CODE;
125
126 /// Identity mode section kind for this metadata width.
127 const IDENTITY_MODES_KIND: u32 = SNAPSHOT_KIND_IDENTITY_MODES_BASE | Self::WIDTH_CODE;
128
129 /// Element identity map section kind for this metadata width.
130 const ELEMENT_IDENTITY_MAP_KIND: u32 =
131 SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_BASE | Self::WIDTH_CODE;
132
133 /// Relation identity map section kind for this metadata width.
134 const RELATION_IDENTITY_MAP_KIND: u32 =
135 SNAPSHOT_KIND_RELATION_IDENTITY_MAP_BASE | Self::WIDTH_CODE;
136
137 /// Incidence identity map section kind for this metadata width.
138 const INCIDENCE_IDENTITY_MAP_KIND: u32 =
139 SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_BASE | Self::WIDTH_CODE;
140}
141
142/// Implements property width traits for one unsigned integer.
143macro_rules! impl_property_width {
144 ($index:ty, $arrow:ty) => {
145 impl sealed::PropertyIndex for $index {}
146
147 impl PropertyIndex for $index {
148 type ArrowType = $arrow;
149
150 fn primitive_array(values: Vec<Self>) -> PrimitiveArray<Self::ArrowType> {
151 PrimitiveArray::<$arrow>::from(values)
152 }
153 }
154
155 impl sealed::PropertySnapshotMetaWord for $index {}
156
157 impl PropertySnapshotMetaWord for $index {}
158 };
159}
160
161impl_property_width!(u16, arrow_array::types::UInt16Type);
162
163impl_property_width!(u32, arrow_array::types::UInt32Type);
164
165impl_property_width!(u64, arrow_array::types::UInt64Type);
166
167/// Marker trait selecting which axis of a topology view a property layer
168/// keys against (elements, relations, or incidences).
169///
170/// Built-in axis markers — [`ElementAxis`], [`RelationAxis`], [`IncidenceAxis`]
171/// — opt into the corresponding [`*Index`] topology trait when paired with
172/// [`DenseWeights`] or [`SparseWeights`] storage. The trait itself only
173/// reports the layer's [`IdFamily`]; per-axis topology accessors live in
174/// inherent impls on each storage type for each axis marker.
175///
176/// # Performance
177///
178/// `perf: unspecified`; this is a metadata trait.
179pub trait PropertyAxis: sealed::PropertyAxis {
180 /// Returns the [`IdFamily`] this axis selects from a property layer.
181 ///
182 /// # Performance
183 ///
184 /// This function is `O(1)`.
185 fn id_family() -> IdFamily;
186}
187
188/// Element-keyed axis marker.
189///
190/// # Performance
191///
192/// Copying and debug-formatting are `O(1)`.
193#[derive(Clone, Copy, Debug, Default)]
194pub struct ElementAxis;
195
196impl sealed::PropertyAxis for ElementAxis {}
197impl PropertyAxis for ElementAxis {
198 fn id_family() -> IdFamily {
199 IdFamily::Element
200 }
201}
202
203/// Relation-keyed axis marker.
204///
205/// # Performance
206///
207/// Copying and debug-formatting are `O(1)`.
208#[derive(Clone, Copy, Debug, Default)]
209pub struct RelationAxis;
210
211impl sealed::PropertyAxis for RelationAxis {}
212impl PropertyAxis for RelationAxis {
213 fn id_family() -> IdFamily {
214 IdFamily::Relation
215 }
216}
217
218/// Incidence-keyed axis marker.
219///
220/// # Performance
221///
222/// Copying and debug-formatting are `O(1)`.
223#[derive(Clone, Copy, Debug, Default)]
224pub struct IncidenceAxis;
225
226impl sealed::PropertyAxis for IncidenceAxis {}
227impl PropertyAxis for IncidenceAxis {
228 fn id_family() -> IdFamily {
229 IdFamily::Incidence
230 }
231}
232
233/// Axis-aware topology bound accessor.
234///
235/// Implemented for every topology view that exposes the per-axis index trait
236/// `DenseElementIndex` / `DenseRelationIndex` / `DenseIncidenceIndex`. Exists so that
237/// generic constructors on [`DenseWeights`] and [`SparseWeights`] can dispatch
238/// to the right `element_bound` / `relation_bound` / `incidence_bound` accessor
239/// from a single body, without parallel per-axis impl blocks.
240///
241/// External code does not normally implement this trait; it is `pub` only
242/// because it appears as a bound in `pub` constructor signatures.
243///
244/// # Performance
245///
246/// `axis_bound` is `O(1)` — it forwards to the topology's own
247/// `*_bound` accessor.
248pub trait AxisIndex<A: PropertyAxis>: TopologyBase {
249 /// Returns the dense index bound for axis `A` on this topology view.
250 ///
251 /// # Performance
252 ///
253 /// `O(1)`.
254 fn axis_bound(&self) -> usize;
255}
256
257impl<T> AxisIndex<ElementAxis> for T
258where
259 T: DenseElementIndex,
260{
261 fn axis_bound(&self) -> usize {
262 self.element_bound()
263 }
264}
265
266impl<T> AxisIndex<RelationAxis> for T
267where
268 T: DenseRelationIndex,
269{
270 fn axis_bound(&self) -> usize {
271 self.relation_bound()
272 }
273}
274
275impl<T> AxisIndex<IncidenceAxis> for T
276where
277 T: DenseIncidenceIndex,
278{
279 fn axis_bound(&self) -> usize {
280 self.incidence_bound()
281 }
282}
283
284/// Converts `value` into a little-endian metadata word.
285///
286/// # Performance
287///
288/// This function is `O(1)`.
289pub(crate) fn le_word<W>(value: usize) -> Result<W::LittleEndianWord, PropertyError>
290where
291 W: PropertySnapshotMetaWord,
292{
293 let Some(value) = W::from_usize(value) else {
294 return Err(PropertyError::SnapshotDescriptorMismatch {
295 reason: "value does not fit selected metadata width",
296 });
297 };
298 Ok(value.to_le_word())
299}
300
301/// Decodes a little-endian metadata word as `usize`.
302///
303/// # Performance
304///
305/// This function is `O(1)`.
306pub(crate) fn le_word_to_usize<W>(word: W::LittleEndianWord) -> Result<usize, PropertyError>
307where
308 W: PropertySnapshotMetaWord,
309{
310 W::from_le_word(word)
311 .to_usize()
312 .ok_or(PropertyError::SnapshotDescriptorMismatch {
313 reason: "metadata word does not fit usize",
314 })
315}
316
317/// Decodes a little-endian metadata word as `u64`.
318///
319/// # Performance
320///
321/// This function is `O(1)`.
322pub(crate) fn le_word_to_u64<W>(word: W::LittleEndianWord) -> u64
323where
324 W: PropertySnapshotMetaWord,
325{
326 W::from_le_word(word).into()
327}
328
329/// Decodes a little-endian metadata word as `u32`.
330///
331/// # Performance
332///
333/// This function is `O(1)`.
334pub(crate) fn le_word_to_u32<W>(word: W::LittleEndianWord) -> Result<u32, PropertyError>
335where
336 W: PropertySnapshotMetaWord,
337{
338 let value = le_word_to_u64::<W>(word);
339 u32::try_from(value).map_err(|_error| PropertyError::SnapshotDescriptorMismatch {
340 reason: "metadata word does not fit u32 tag",
341 })
342}