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}