oxgraph-property 0.3.2

Arrow-backed named property layers for OxGraph topology views.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
//! Property index width contracts, per-axis markers, and metadata-word codecs.
//!
//! Defines the sealed [`PropertyIndex`] / [`PropertySnapshotMetaWord`] width
//! traits, the three built-in [`PropertyAxis`] markers and their [`AxisIndex`]
//! topology-bound dispatch, the snapshot section-kind constants keyed by
//! metadata width, and the little-endian metadata-word conversion helpers.

use std::vec::Vec;

use arrow_array::{PrimitiveArray, types::ArrowPrimitiveType};
use oxgraph_topology::{ElementIndex, IncidenceIndex, RelationIndex, TopologyBase};
use zerocopy::{
    FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned,
    byteorder::{LE, U16, U32, U64},
};

use crate::model::{IdFamily, PropertyError};

/// Snapshot section kind reserved for `u16` property-layer descriptors.
pub const SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U16: u32 = 0x0100;
/// Snapshot section kind reserved for `u16` Arrow IPC property-layer payloads.
pub const SNAPSHOT_KIND_PROPERTY_DATA_U16: u32 = 0x0101;
/// Snapshot section kind reserved for `u32` property-layer descriptors.
pub const SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U32: u32 = 0x0102;
/// Snapshot section kind reserved for `u32` Arrow IPC property-layer payloads.
pub const SNAPSHOT_KIND_PROPERTY_DATA_U32: u32 = 0x0103;
/// Snapshot section kind reserved for `u64` property-layer descriptors.
pub const SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U64: u32 = 0x0104;
/// Snapshot section kind reserved for `u64` Arrow IPC property-layer payloads.
pub const SNAPSHOT_KIND_PROPERTY_DATA_U64: u32 = 0x0105;

/// Snapshot section kind for `u16` identity-mode metadata records.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_IDENTITY_MODES_U16: u32 = 0x0110;

/// Snapshot section kind for `u32` identity-mode metadata records.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_IDENTITY_MODES_U32: u32 = 0x0111;

/// Snapshot section kind for `u64` identity-mode metadata records.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_IDENTITY_MODES_U64: u32 = 0x0112;

/// Snapshot section kind for element local-to-canonical `u16` maps.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U16: u32 = 0x0113;

/// Snapshot section kind for element local-to-canonical `u32` maps.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U32: u32 = 0x0114;

/// Snapshot section kind for element local-to-canonical `u64` maps.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U64: u32 = 0x0115;

/// Snapshot section kind for relation local-to-canonical `u16` maps.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U16: u32 = 0x0116;

/// Snapshot section kind for relation local-to-canonical `u32` maps.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U32: u32 = 0x0117;

/// Snapshot section kind for relation local-to-canonical `u64` maps.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U64: u32 = 0x0118;

/// Snapshot section kind for incidence local-to-canonical `u16` maps.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U16: u32 = 0x0119;

/// Snapshot section kind for incidence local-to-canonical `u32` maps.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U32: u32 = 0x011A;

/// Snapshot section kind for incidence local-to-canonical `u64` maps.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U64: u32 = 0x011B;

/// Internal property/identity snapshot section version.
///
/// # Performance
///
/// `perf: unspecified`; this is a compile-time constant.
pub const SNAPSHOT_PROPERTY_VERSION: u32 = 1;

/// Sealed trait modules for property width contracts.
mod sealed {
    /// Seals [`super::PropertyIndex`] to supported unsigned sparse widths.
    pub trait PropertyIndex {}

    /// Seals [`super::PropertySnapshotMetaWord`] to supported metadata widths.
    pub trait PropertySnapshotMetaWord {}

    /// Seals [`super::PropertyAxis`] to the three built-in axis markers.
    pub trait PropertyAxis {}
}

/// Unsigned index width usable for sparse property indexes.
///
/// # Performance
///
/// Implementations perform checked conversions in `O(1)`.
pub trait PropertyIndex: sealed::PropertyIndex + Copy + Ord {
    /// Arrow unsigned primitive type for sparse index arrays.
    type ArrowType: ArrowPrimitiveType<Native = Self> + 'static;

    /// Little-endian word used when this width appears in snapshots.
    type LittleEndianWord: FromBytes + Immutable + IntoBytes + KnownLayout + Unaligned + Copy;

    /// Returns `self` as `usize`, or `None` if the target platform cannot hold it.
    ///
    /// # Performance
    ///
    /// This function is `O(1)`.
    fn to_usize(self) -> Option<usize>;

    /// Converts `value` into this index width if it fits.
    ///
    /// # Performance
    ///
    /// This function is `O(1)`.
    fn from_usize(value: usize) -> Option<Self>;

    /// Converts `value` into this index width if it fits.
    ///
    /// # Performance
    ///
    /// This function is `O(1)`.
    fn from_u64(value: u64) -> Option<Self>;

    /// Returns `self` as `u64` for diagnostics.
    ///
    /// # Performance
    ///
    /// This function is `O(1)`.
    fn to_u64(self) -> u64;

    /// Encodes `self` as a little-endian snapshot word.
    ///
    /// # Performance
    ///
    /// This function is `O(1)`.
    fn to_le_word(self) -> Self::LittleEndianWord;

    /// Decodes a little-endian snapshot word.
    ///
    /// # Performance
    ///
    /// This function is `O(1)`.
    fn from_le_word(word: Self::LittleEndianWord) -> Self;

    /// Builds an Arrow primitive array from native index values.
    ///
    /// # Performance
    ///
    /// This function is `O(values.len())`.
    fn primitive_array(values: Vec<Self>) -> PrimitiveArray<Self::ArrowType>;
}

/// Metadata/canonical-ID word width for property and identity snapshot sections.
///
/// # Performance
///
/// Implementations perform checked conversions in `O(1)`.
pub trait PropertySnapshotMetaWord: sealed::PropertySnapshotMetaWord + PropertyIndex {
    /// Property descriptor section kind for this metadata width.
    const PROPERTY_DESCRIPTORS_KIND: u32;

    /// Property data section kind for this metadata width.
    const PROPERTY_DATA_KIND: u32;

    /// Identity mode section kind for this metadata width.
    const IDENTITY_MODES_KIND: u32;

    /// Element identity map section kind for this metadata width.
    const ELEMENT_IDENTITY_MAP_KIND: u32;

    /// Relation identity map section kind for this metadata width.
    const RELATION_IDENTITY_MAP_KIND: u32;

    /// Incidence identity map section kind for this metadata width.
    const INCIDENCE_IDENTITY_MAP_KIND: u32;
}

/// Implements property width traits for one unsigned integer.
macro_rules! impl_property_width {
    (
        $index:ty,
        $arrow:ty,
        $word:ty,
        $descriptor_kind:expr,
        $data_kind:expr,
        $identity_kind:expr,
        $element_kind:expr,
        $relation_kind:expr,
        $incidence_kind:expr
    ) => {
        impl sealed::PropertyIndex for $index {}

        impl PropertyIndex for $index {
            type ArrowType = $arrow;
            type LittleEndianWord = $word;

            fn to_usize(self) -> Option<usize> {
                usize::try_from(self).ok()
            }

            fn from_usize(value: usize) -> Option<Self> {
                <$index>::try_from(value).ok()
            }

            fn from_u64(value: u64) -> Option<Self> {
                <$index>::try_from(value).ok()
            }

            fn to_u64(self) -> u64 {
                u64::from(self)
            }

            fn to_le_word(self) -> Self::LittleEndianWord {
                <$word>::new(self)
            }

            fn from_le_word(word: Self::LittleEndianWord) -> Self {
                word.get()
            }

            fn primitive_array(values: Vec<Self>) -> PrimitiveArray<Self::ArrowType> {
                PrimitiveArray::<$arrow>::from(values)
            }
        }

        impl sealed::PropertySnapshotMetaWord for $index {}

        impl PropertySnapshotMetaWord for $index {
            const PROPERTY_DESCRIPTORS_KIND: u32 = $descriptor_kind;
            const PROPERTY_DATA_KIND: u32 = $data_kind;
            const IDENTITY_MODES_KIND: u32 = $identity_kind;
            const ELEMENT_IDENTITY_MAP_KIND: u32 = $element_kind;
            const RELATION_IDENTITY_MAP_KIND: u32 = $relation_kind;
            const INCIDENCE_IDENTITY_MAP_KIND: u32 = $incidence_kind;
        }
    };
}

impl_property_width!(
    u16,
    arrow_array::types::UInt16Type,
    U16<LE>,
    SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U16,
    SNAPSHOT_KIND_PROPERTY_DATA_U16,
    SNAPSHOT_KIND_IDENTITY_MODES_U16,
    SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U16,
    SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U16,
    SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U16
);

impl_property_width!(
    u32,
    arrow_array::types::UInt32Type,
    U32<LE>,
    SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U32,
    SNAPSHOT_KIND_PROPERTY_DATA_U32,
    SNAPSHOT_KIND_IDENTITY_MODES_U32,
    SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U32,
    SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U32,
    SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U32
);

impl_property_width!(
    u64,
    arrow_array::types::UInt64Type,
    U64<LE>,
    SNAPSHOT_KIND_PROPERTY_DESCRIPTORS_U64,
    SNAPSHOT_KIND_PROPERTY_DATA_U64,
    SNAPSHOT_KIND_IDENTITY_MODES_U64,
    SNAPSHOT_KIND_ELEMENT_IDENTITY_MAP_U64,
    SNAPSHOT_KIND_RELATION_IDENTITY_MAP_U64,
    SNAPSHOT_KIND_INCIDENCE_IDENTITY_MAP_U64
);

/// Marker trait selecting which axis of a topology view a property layer
/// keys against (elements, relations, or incidences).
///
/// Built-in axis markers — [`ElementAxis`], [`RelationAxis`], [`IncidenceAxis`]
/// — opt into the corresponding [`*Index`] topology trait when paired with
/// [`DenseWeights`] or [`SparseWeights`] storage. The trait itself only
/// reports the layer's [`IdFamily`]; per-axis topology accessors live in
/// inherent impls on each storage type for each axis marker.
///
/// # Performance
///
/// `perf: unspecified`; this is a metadata trait.
pub trait PropertyAxis: sealed::PropertyAxis {
    /// Returns the [`IdFamily`] this axis selects from a property layer.
    ///
    /// # Performance
    ///
    /// This function is `O(1)`.
    fn id_family() -> IdFamily;
}

/// Element-keyed axis marker.
///
/// # Performance
///
/// Copying and debug-formatting are `O(1)`.
#[derive(Clone, Copy, Debug, Default)]
pub struct ElementAxis;

impl sealed::PropertyAxis for ElementAxis {}
impl PropertyAxis for ElementAxis {
    fn id_family() -> IdFamily {
        IdFamily::Element
    }
}

/// Relation-keyed axis marker.
///
/// # Performance
///
/// Copying and debug-formatting are `O(1)`.
#[derive(Clone, Copy, Debug, Default)]
pub struct RelationAxis;

impl sealed::PropertyAxis for RelationAxis {}
impl PropertyAxis for RelationAxis {
    fn id_family() -> IdFamily {
        IdFamily::Relation
    }
}

/// Incidence-keyed axis marker.
///
/// # Performance
///
/// Copying and debug-formatting are `O(1)`.
#[derive(Clone, Copy, Debug, Default)]
pub struct IncidenceAxis;

impl sealed::PropertyAxis for IncidenceAxis {}
impl PropertyAxis for IncidenceAxis {
    fn id_family() -> IdFamily {
        IdFamily::Incidence
    }
}

/// Axis-aware topology bound accessor.
///
/// Implemented for every topology view that exposes the per-axis index trait
/// `ElementIndex` / `RelationIndex` / `IncidenceIndex`. Exists so that
/// generic constructors on [`DenseWeights`] and [`SparseWeights`] can dispatch
/// to the right `element_bound` / `relation_bound` / `incidence_bound` accessor
/// from a single body, without parallel per-axis impl blocks.
///
/// External code does not normally implement this trait; it is `pub` only
/// because it appears as a bound in `pub` constructor signatures.
///
/// # Performance
///
/// `axis_bound` is `O(1)` — it forwards to the topology's own
/// `*_bound` accessor.
pub trait AxisIndex<A: PropertyAxis>: TopologyBase {
    /// Returns the dense index bound for axis `A` on this topology view.
    ///
    /// # Performance
    ///
    /// `O(1)`.
    fn axis_bound(&self) -> usize;
}

impl<T> AxisIndex<ElementAxis> for T
where
    T: ElementIndex,
{
    fn axis_bound(&self) -> usize {
        self.element_bound()
    }
}

impl<T> AxisIndex<RelationAxis> for T
where
    T: RelationIndex,
{
    fn axis_bound(&self) -> usize {
        self.relation_bound()
    }
}

impl<T> AxisIndex<IncidenceAxis> for T
where
    T: IncidenceIndex,
{
    fn axis_bound(&self) -> usize {
        self.incidence_bound()
    }
}

/// Converts `value` into a little-endian metadata word.
///
/// # Performance
///
/// This function is `O(1)`.
pub(crate) fn le_word<W>(value: usize) -> Result<W::LittleEndianWord, PropertyError>
where
    W: PropertySnapshotMetaWord,
{
    let Some(value) = W::from_usize(value) else {
        return Err(PropertyError::SnapshotDescriptorMismatch {
            reason: "value does not fit selected metadata width",
        });
    };
    Ok(value.to_le_word())
}

/// Decodes a little-endian metadata word as `usize`.
///
/// # Performance
///
/// This function is `O(1)`.
pub(crate) fn le_word_to_usize<W>(word: W::LittleEndianWord) -> Result<usize, PropertyError>
where
    W: PropertySnapshotMetaWord,
{
    W::from_le_word(word)
        .to_usize()
        .ok_or(PropertyError::SnapshotDescriptorMismatch {
            reason: "metadata word does not fit usize",
        })
}

/// Decodes a little-endian metadata word as `u64`.
///
/// # Performance
///
/// This function is `O(1)`.
pub(crate) fn le_word_to_u64<W>(word: W::LittleEndianWord) -> u64
where
    W: PropertySnapshotMetaWord,
{
    W::from_le_word(word).to_u64()
}

/// Decodes a little-endian metadata word as `u32`.
///
/// # Performance
///
/// This function is `O(1)`.
pub(crate) fn le_word_to_u32<W>(word: W::LittleEndianWord) -> Result<u32, PropertyError>
where
    W: PropertySnapshotMetaWord,
{
    let value = le_word_to_u64::<W>(word);
    u32::try_from(value).map_err(|_error| PropertyError::SnapshotDescriptorMismatch {
        reason: "metadata word does not fit u32 tag",
    })
}