Skip to main content

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_topology::{ElementIndex, IncidenceIndex, RelationIndex, TopologyBase};
12use zerocopy::{
13    FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned,
14    byteorder::{LE, U16, U32, U64},
15};
16
17use crate::model::{IdFamily, PropertyError};
18
19/// Snapshot section kind reserved for `u16` property-layer descriptors.
20pub const SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U16: u32 = 0x0100;
21/// Snapshot section kind reserved for `u16` Arrow IPC property-layer payloads.
22pub const SNAPSHOT_KIND_PROPERTY_DATA_U16: u32 = 0x0101;
23/// Snapshot section kind reserved for `u32` property-layer descriptors.
24pub const SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U32: u32 = 0x0102;
25/// Snapshot section kind reserved for `u32` Arrow IPC property-layer payloads.
26pub const SNAPSHOT_KIND_PROPERTY_DATA_U32: u32 = 0x0103;
27/// Snapshot section kind reserved for `u64` property-layer descriptors.
28pub const SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U64: u32 = 0x0104;
29/// Snapshot section kind reserved for `u64` Arrow IPC property-layer payloads.
30pub const SNAPSHOT_KIND_PROPERTY_DATA_U64: u32 = 0x0105;
31
32/// Snapshot section kind for `u16` identity-mode metadata records.
33///
34/// # Performance
35///
36/// `perf: unspecified`; this is a compile-time constant.
37pub const SNAPSHOT_KIND_IDENTITY_MODES_U16: u32 = 0x0110;
38
39/// Snapshot section kind for `u32` identity-mode metadata records.
40///
41/// # Performance
42///
43/// `perf: unspecified`; this is a compile-time constant.
44pub const SNAPSHOT_KIND_IDENTITY_MODES_U32: u32 = 0x0111;
45
46/// Snapshot section kind for `u64` identity-mode metadata records.
47///
48/// # Performance
49///
50/// `perf: unspecified`; this is a compile-time constant.
51pub const SNAPSHOT_KIND_IDENTITY_MODES_U64: u32 = 0x0112;
52
53/// Snapshot section kind for element local-to-canonical `u16` maps.
54///
55/// # Performance
56///
57/// `perf: unspecified`; this is a compile-time constant.
58pub const SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U16: u32 = 0x0113;
59
60/// Snapshot section kind for element local-to-canonical `u32` maps.
61///
62/// # Performance
63///
64/// `perf: unspecified`; this is a compile-time constant.
65pub const SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U32: u32 = 0x0114;
66
67/// Snapshot section kind for element local-to-canonical `u64` maps.
68///
69/// # Performance
70///
71/// `perf: unspecified`; this is a compile-time constant.
72pub const SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U64: u32 = 0x0115;
73
74/// Snapshot section kind for relation local-to-canonical `u16` maps.
75///
76/// # Performance
77///
78/// `perf: unspecified`; this is a compile-time constant.
79pub const SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U16: u32 = 0x0116;
80
81/// Snapshot section kind for relation local-to-canonical `u32` maps.
82///
83/// # Performance
84///
85/// `perf: unspecified`; this is a compile-time constant.
86pub const SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U32: u32 = 0x0117;
87
88/// Snapshot section kind for relation local-to-canonical `u64` maps.
89///
90/// # Performance
91///
92/// `perf: unspecified`; this is a compile-time constant.
93pub const SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U64: u32 = 0x0118;
94
95/// Snapshot section kind for incidence local-to-canonical `u16` maps.
96///
97/// # Performance
98///
99/// `perf: unspecified`; this is a compile-time constant.
100pub const SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U16: u32 = 0x0119;
101
102/// Snapshot section kind for incidence local-to-canonical `u32` maps.
103///
104/// # Performance
105///
106/// `perf: unspecified`; this is a compile-time constant.
107pub const SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U32: u32 = 0x011A;
108
109/// Snapshot section kind for incidence local-to-canonical `u64` maps.
110///
111/// # Performance
112///
113/// `perf: unspecified`; this is a compile-time constant.
114pub const SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U64: u32 = 0x011B;
115
116/// Internal property/identity snapshot section version.
117///
118/// # Performance
119///
120/// `perf: unspecified`; this is a compile-time constant.
121pub const SNAPSHOT_PROPERTY_VERSION: u32 = 1;
122
123/// Sealed trait modules for property width contracts.
124mod sealed {
125    /// Seals [`super::PropertyIndex`] to supported unsigned sparse widths.
126    pub trait PropertyIndex {}
127
128    /// Seals [`super::PropertySnapshotMetaWord`] to supported metadata widths.
129    pub trait PropertySnapshotMetaWord {}
130
131    /// Seals [`super::PropertyAxis`] to the three built-in axis markers.
132    pub trait PropertyAxis {}
133}
134
135/// Unsigned index width usable for sparse property indexes.
136///
137/// # Performance
138///
139/// Implementations perform checked conversions in `O(1)`.
140pub trait PropertyIndex: sealed::PropertyIndex + Copy + Ord {
141    /// Arrow unsigned primitive type for sparse index arrays.
142    type ArrowType: ArrowPrimitiveType<Native = Self> + 'static;
143
144    /// Little-endian word used when this width appears in snapshots.
145    type LittleEndianWord: FromBytes + Immutable + IntoBytes + KnownLayout + Unaligned + Copy;
146
147    /// Returns `self` as `usize`, or `None` if the target platform cannot hold it.
148    ///
149    /// # Performance
150    ///
151    /// This function is `O(1)`.
152    fn to_usize(self) -> Option<usize>;
153
154    /// Converts `value` into this index width if it fits.
155    ///
156    /// # Performance
157    ///
158    /// This function is `O(1)`.
159    fn from_usize(value: usize) -> Option<Self>;
160
161    /// Converts `value` into this index width if it fits.
162    ///
163    /// # Performance
164    ///
165    /// This function is `O(1)`.
166    fn from_u64(value: u64) -> Option<Self>;
167
168    /// Returns `self` as `u64` for diagnostics.
169    ///
170    /// # Performance
171    ///
172    /// This function is `O(1)`.
173    fn to_u64(self) -> u64;
174
175    /// Encodes `self` as a little-endian snapshot word.
176    ///
177    /// # Performance
178    ///
179    /// This function is `O(1)`.
180    fn to_le_word(self) -> Self::LittleEndianWord;
181
182    /// Decodes a little-endian snapshot word.
183    ///
184    /// # Performance
185    ///
186    /// This function is `O(1)`.
187    fn from_le_word(word: Self::LittleEndianWord) -> Self;
188
189    /// Builds an Arrow primitive array from native index values.
190    ///
191    /// # Performance
192    ///
193    /// This function is `O(values.len())`.
194    fn primitive_array(values: Vec<Self>) -> PrimitiveArray<Self::ArrowType>;
195}
196
197/// Metadata/canonical-ID word width for property and identity snapshot sections.
198///
199/// # Performance
200///
201/// Implementations perform checked conversions in `O(1)`.
202pub trait PropertySnapshotMetaWord: sealed::PropertySnapshotMetaWord + PropertyIndex {
203    /// Property descriptor section kind for this metadata width.
204    const PROPERTY_DESCRIPTORS_KIND: u32;
205
206    /// Property data section kind for this metadata width.
207    const PROPERTY_DATA_KIND: u32;
208
209    /// Identity mode section kind for this metadata width.
210    const IDENTITY_MODES_KIND: u32;
211
212    /// Element identity map section kind for this metadata width.
213    const ELEMENT_IDENTITY_MAP_KIND: u32;
214
215    /// Relation identity map section kind for this metadata width.
216    const RELATION_IDENTITY_MAP_KIND: u32;
217
218    /// Incidence identity map section kind for this metadata width.
219    const INCIDENCE_IDENTITY_MAP_KIND: u32;
220}
221
222/// Implements property width traits for one unsigned integer.
223macro_rules! impl_property_width {
224    (
225        $index:ty,
226        $arrow:ty,
227        $word:ty,
228        $descriptor_kind:expr,
229        $data_kind:expr,
230        $identity_kind:expr,
231        $element_kind:expr,
232        $relation_kind:expr,
233        $incidence_kind:expr
234    ) => {
235        impl sealed::PropertyIndex for $index {}
236
237        impl PropertyIndex for $index {
238            type ArrowType = $arrow;
239            type LittleEndianWord = $word;
240
241            fn to_usize(self) -> Option<usize> {
242                usize::try_from(self).ok()
243            }
244
245            fn from_usize(value: usize) -> Option<Self> {
246                <$index>::try_from(value).ok()
247            }
248
249            fn from_u64(value: u64) -> Option<Self> {
250                <$index>::try_from(value).ok()
251            }
252
253            fn to_u64(self) -> u64 {
254                u64::from(self)
255            }
256
257            fn to_le_word(self) -> Self::LittleEndianWord {
258                <$word>::new(self)
259            }
260
261            fn from_le_word(word: Self::LittleEndianWord) -> Self {
262                word.get()
263            }
264
265            fn primitive_array(values: Vec<Self>) -> PrimitiveArray<Self::ArrowType> {
266                PrimitiveArray::<$arrow>::from(values)
267            }
268        }
269
270        impl sealed::PropertySnapshotMetaWord for $index {}
271
272        impl PropertySnapshotMetaWord for $index {
273            const PROPERTY_DESCRIPTORS_KIND: u32 = $descriptor_kind;
274            const PROPERTY_DATA_KIND: u32 = $data_kind;
275            const IDENTITY_MODES_KIND: u32 = $identity_kind;
276            const ELEMENT_IDENTITY_MAP_KIND: u32 = $element_kind;
277            const RELATION_IDENTITY_MAP_KIND: u32 = $relation_kind;
278            const INCIDENCE_IDENTITY_MAP_KIND: u32 = $incidence_kind;
279        }
280    };
281}
282
283impl_property_width!(
284    u16,
285    arrow_array::types::UInt16Type,
286    U16<LE>,
287    SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U16,
288    SNAPSHOT_KIND_PROPERTY_DATA_U16,
289    SNAPSHOT_KIND_IDENTITY_MODES_U16,
290    SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U16,
291    SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U16,
292    SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U16
293);
294
295impl_property_width!(
296    u32,
297    arrow_array::types::UInt32Type,
298    U32<LE>,
299    SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U32,
300    SNAPSHOT_KIND_PROPERTY_DATA_U32,
301    SNAPSHOT_KIND_IDENTITY_MODES_U32,
302    SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U32,
303    SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U32,
304    SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U32
305);
306
307impl_property_width!(
308    u64,
309    arrow_array::types::UInt64Type,
310    U64<LE>,
311    SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U64,
312    SNAPSHOT_KIND_PROPERTY_DATA_U64,
313    SNAPSHOT_KIND_IDENTITY_MODES_U64,
314    SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U64,
315    SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U64,
316    SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U64
317);
318
319/// Marker trait selecting which axis of a topology view a property layer
320/// keys against (elements, relations, or incidences).
321///
322/// Built-in axis markers — [`ElementAxis`], [`RelationAxis`], [`IncidenceAxis`]
323/// — opt into the corresponding [`*Index`] topology trait when paired with
324/// [`DenseWeights`] or [`SparseWeights`] storage. The trait itself only
325/// reports the layer's [`IdFamily`]; per-axis topology accessors live in
326/// inherent impls on each storage type for each axis marker.
327///
328/// # Performance
329///
330/// `perf: unspecified`; this is a metadata trait.
331pub trait PropertyAxis: sealed::PropertyAxis {
332    /// Returns the [`IdFamily`] this axis selects from a property layer.
333    ///
334    /// # Performance
335    ///
336    /// This function is `O(1)`.
337    fn id_family() -> IdFamily;
338}
339
340/// Element-keyed axis marker.
341///
342/// # Performance
343///
344/// Copying and debug-formatting are `O(1)`.
345#[derive(Clone, Copy, Debug, Default)]
346pub struct ElementAxis;
347
348impl sealed::PropertyAxis for ElementAxis {}
349impl PropertyAxis for ElementAxis {
350    fn id_family() -> IdFamily {
351        IdFamily::Element
352    }
353}
354
355/// Relation-keyed axis marker.
356///
357/// # Performance
358///
359/// Copying and debug-formatting are `O(1)`.
360#[derive(Clone, Copy, Debug, Default)]
361pub struct RelationAxis;
362
363impl sealed::PropertyAxis for RelationAxis {}
364impl PropertyAxis for RelationAxis {
365    fn id_family() -> IdFamily {
366        IdFamily::Relation
367    }
368}
369
370/// Incidence-keyed axis marker.
371///
372/// # Performance
373///
374/// Copying and debug-formatting are `O(1)`.
375#[derive(Clone, Copy, Debug, Default)]
376pub struct IncidenceAxis;
377
378impl sealed::PropertyAxis for IncidenceAxis {}
379impl PropertyAxis for IncidenceAxis {
380    fn id_family() -> IdFamily {
381        IdFamily::Incidence
382    }
383}
384
385/// Axis-aware topology bound accessor.
386///
387/// Implemented for every topology view that exposes the per-axis index trait
388/// `ElementIndex` / `RelationIndex` / `IncidenceIndex`. Exists so that
389/// generic constructors on [`DenseWeights`] and [`SparseWeights`] can dispatch
390/// to the right `element_bound` / `relation_bound` / `incidence_bound` accessor
391/// from a single body, without parallel per-axis impl blocks.
392///
393/// External code does not normally implement this trait; it is `pub` only
394/// because it appears as a bound in `pub` constructor signatures.
395///
396/// # Performance
397///
398/// `axis_bound` is `O(1)` — it forwards to the topology's own
399/// `*_bound` accessor.
400pub trait AxisIndex<A: PropertyAxis>: TopologyBase {
401    /// Returns the dense index bound for axis `A` on this topology view.
402    ///
403    /// # Performance
404    ///
405    /// `O(1)`.
406    fn axis_bound(&self) -> usize;
407}
408
409impl<T> AxisIndex<ElementAxis> for T
410where
411    T: ElementIndex,
412{
413    fn axis_bound(&self) -> usize {
414        self.element_bound()
415    }
416}
417
418impl<T> AxisIndex<RelationAxis> for T
419where
420    T: RelationIndex,
421{
422    fn axis_bound(&self) -> usize {
423        self.relation_bound()
424    }
425}
426
427impl<T> AxisIndex<IncidenceAxis> for T
428where
429    T: IncidenceIndex,
430{
431    fn axis_bound(&self) -> usize {
432        self.incidence_bound()
433    }
434}
435
436/// Converts `value` into a little-endian metadata word.
437///
438/// # Performance
439///
440/// This function is `O(1)`.
441pub(crate) fn le_word<W>(value: usize) -> Result<W::LittleEndianWord, PropertyError>
442where
443    W: PropertySnapshotMetaWord,
444{
445    let Some(value) = W::from_usize(value) else {
446        return Err(PropertyError::SnapshotDescriptorMismatch {
447            reason: "value does not fit selected metadata width",
448        });
449    };
450    Ok(value.to_le_word())
451}
452
453/// Decodes a little-endian metadata word as `usize`.
454///
455/// # Performance
456///
457/// This function is `O(1)`.
458pub(crate) fn le_word_to_usize<W>(word: W::LittleEndianWord) -> Result<usize, PropertyError>
459where
460    W: PropertySnapshotMetaWord,
461{
462    W::from_le_word(word)
463        .to_usize()
464        .ok_or(PropertyError::SnapshotDescriptorMismatch {
465            reason: "metadata word does not fit usize",
466        })
467}
468
469/// Decodes a little-endian metadata word as `u64`.
470///
471/// # Performance
472///
473/// This function is `O(1)`.
474pub(crate) fn le_word_to_u64<W>(word: W::LittleEndianWord) -> u64
475where
476    W: PropertySnapshotMetaWord,
477{
478    W::from_le_word(word).to_u64()
479}
480
481/// Decodes a little-endian metadata word as `u32`.
482///
483/// # Performance
484///
485/// This function is `O(1)`.
486pub(crate) fn le_word_to_u32<W>(word: W::LittleEndianWord) -> Result<u32, PropertyError>
487where
488    W: PropertySnapshotMetaWord,
489{
490    let value = le_word_to_u64::<W>(word);
491    u32::try_from(value).map_err(|_error| PropertyError::SnapshotDescriptorMismatch {
492        reason: "metadata word does not fit u32 tag",
493    })
494}