Skip to main content

oxgraph_db/
state.rs

1//! Canonical record value types and the id-allocator watermark.
2//!
3//! This module owns the durable record VALUE types the base and overlay both
4//! materialize ([`ElementRecord`], [`RelationRecord`], [`IncidenceRecord`],
5//! [`PropertySubject`]) plus the nine-value monotonic id allocator watermark
6//! ([`NextIds`]). The live read surface is the merged overlay-over-base view in
7//! [`crate::overlay`].
8//!
9//! # Performance
10//!
11//! `perf: unspecified`; this module defines value types and `O(1)` watermark
12//! arithmetic.
13
14use std::collections::BTreeSet;
15
16use serde::{Deserialize, Serialize};
17
18use crate::{
19    ElementId, IncidenceId, IndexId, LabelId, ProjectionId, PropertyKeyId, RelationId,
20    RelationTypeId, RoleId, backing::DbHeader, catalog::PropertyFamily,
21};
22
23/// One visible canonical element.
24///
25/// # Performance
26///
27/// Cloning is `O(label count)`.
28#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
29pub struct ElementRecord {
30    /// Stable element identifier.
31    pub id: ElementId,
32    /// Labels assigned to this element.
33    pub labels: BTreeSet<LabelId>,
34}
35
36/// One visible canonical relation.
37///
38/// # Performance
39///
40/// Cloning is `O(label count)`.
41#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
42pub struct RelationRecord {
43    /// Stable relation identifier.
44    pub id: RelationId,
45    /// Optional relation type.
46    pub relation_type: Option<RelationTypeId>,
47    /// Labels assigned to this relation.
48    pub labels: BTreeSet<LabelId>,
49}
50
51/// One visible incidence in canonical database coordinates.
52///
53/// # Performance
54///
55/// Copying and comparing this record are `O(1)`.
56#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
57pub struct IncidenceRecord {
58    /// Stable incidence id.
59    pub id: IncidenceId,
60    /// Stable relation id containing the incidence.
61    pub relation: RelationId,
62    /// Stable element id participating in the relation.
63    pub element: ElementId,
64    /// Structural role of the incidence.
65    pub role: RoleId,
66}
67
68/// Subject that can own properties.
69///
70/// # Invariant
71///
72/// The variant declaration order (`Element`, `Relation`, `Incidence`) is
73/// load-bearing: the derived `Ord` ranks them in that order, and that ranking
74/// MUST match the wire kind tags `0`/`1`/`2` produced by the wire
75/// `encode_subject` codec. The frozen base writes property records in
76/// `BTreeMap<PropertySubject, …>` order — sorted by `(subject_kind, subject_id,
77/// key)` — and the read path materializes them back into that same keyed order;
78/// reordering these variants would silently desynchronize the two. See the wire
79/// `encode_subject` docs for the full contract and the open-time assertion in
80/// `backing` that guards it.
81///
82/// # Performance
83///
84/// Copying, comparing, ordering, and hashing are `O(1)`.
85#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
86pub enum PropertySubject {
87    /// Element property subject.
88    Element(ElementId),
89    /// Relation property subject.
90    Relation(RelationId),
91    /// Incidence property subject.
92    Incidence(IncidenceId),
93}
94
95impl PropertySubject {
96    /// Returns the property family for this subject.
97    ///
98    /// # Performance
99    ///
100    /// This function is `O(1)`.
101    #[must_use]
102    pub const fn family(self) -> PropertyFamily {
103        match self {
104            Self::Element(_id) => PropertyFamily::Element,
105            Self::Relation(_id) => PropertyFamily::Relation,
106            Self::Incidence(_id) => PropertyFamily::Incidence,
107        }
108    }
109}
110
111/// The nine monotonic id allocators, captured for the store header and every
112/// dirty commit's watermark op in a fixed order. Recovery folds the elementwise
113/// maximum of the base header and every replayed frame's watermark so a
114/// canonical id is never reissued across a crash.
115///
116/// # Performance
117///
118/// Copying is `O(1)`.
119#[derive(Clone, Copy, Debug, Eq, PartialEq)]
120pub(crate) struct NextIds {
121    /// Next element id candidate.
122    pub(crate) element: ElementId,
123    /// Next relation id candidate.
124    pub(crate) relation: RelationId,
125    /// Next incidence id candidate.
126    pub(crate) incidence: IncidenceId,
127    /// Next role id candidate.
128    pub(crate) role: RoleId,
129    /// Next label id candidate.
130    pub(crate) label: LabelId,
131    /// Next relation-type id candidate.
132    pub(crate) relation_type: RelationTypeId,
133    /// Next property-key id candidate.
134    pub(crate) property_key: PropertyKeyId,
135    /// Next projection id candidate.
136    pub(crate) projection: ProjectionId,
137    /// Next index id candidate.
138    pub(crate) index: IndexId,
139}
140
141impl NextIds {
142    /// The watermark of an empty store: every allocator starts at `1`, so the
143    /// `0` sentinel ([`crate::wire::RELATION_TYPE_NONE`]) can never collide with
144    /// a real id.
145    ///
146    /// # Performance
147    ///
148    /// `perf: unspecified`; this is a compile-time constant.
149    pub(crate) const INITIAL: Self = Self {
150        element: ElementId::new(1),
151        relation: RelationId::new(1),
152        incidence: IncidenceId::new(1),
153        role: RoleId::new(1),
154        label: LabelId::new(1),
155        relation_type: RelationTypeId::new(1),
156        property_key: PropertyKeyId::new(1),
157        projection: ProjectionId::new(1),
158        index: IndexId::new(1),
159    };
160
161    /// Reads the watermark out of a base header's nine `next_*` allocator
162    /// snapshots.
163    ///
164    /// # Performance
165    ///
166    /// This function is `O(1)`.
167    pub(crate) const fn from_header(header: &DbHeader) -> Self {
168        Self {
169            element: ElementId::new(header.next_element),
170            relation: RelationId::new(header.next_relation),
171            incidence: IncidenceId::new(header.next_incidence),
172            role: RoleId::new(header.next_role),
173            label: LabelId::new(header.next_label),
174            relation_type: RelationTypeId::new(header.next_relation_type),
175            property_key: PropertyKeyId::new(header.next_property_key),
176            projection: ProjectionId::new(header.next_projection),
177            index: IndexId::new(header.next_index),
178        }
179    }
180
181    /// Returns the elementwise maximum of two watermarks, allocator by
182    /// allocator. Recovery folds this over the base header and every replayed
183    /// frame so the recovered watermark sits at or above every id ever issued —
184    /// canonical ids are never reused.
185    ///
186    /// # Performance
187    ///
188    /// This function is `O(1)`.
189    pub(crate) fn elementwise_max(self, other: Self) -> Self {
190        Self {
191            element: self.element.max(other.element),
192            relation: self.relation.max(other.relation),
193            incidence: self.incidence.max(other.incidence),
194            role: self.role.max(other.role),
195            label: self.label.max(other.label),
196            relation_type: self.relation_type.max(other.relation_type),
197            property_key: self.property_key.max(other.property_key),
198            projection: self.projection.max(other.projection),
199            index: self.index.max(other.index),
200        }
201    }
202}