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
111impl From<ElementId> for PropertySubject {
112    /// Wraps an element id as an element property subject.
113    ///
114    /// # Performance
115    ///
116    /// This function is `O(1)`.
117    fn from(id: ElementId) -> Self {
118        Self::Element(id)
119    }
120}
121
122impl From<RelationId> for PropertySubject {
123    /// Wraps a relation id as a relation property subject.
124    ///
125    /// # Performance
126    ///
127    /// This function is `O(1)`.
128    fn from(id: RelationId) -> Self {
129        Self::Relation(id)
130    }
131}
132
133impl From<IncidenceId> for PropertySubject {
134    /// Wraps an incidence id as an incidence property subject.
135    ///
136    /// # Performance
137    ///
138    /// This function is `O(1)`.
139    fn from(id: IncidenceId) -> Self {
140        Self::Incidence(id)
141    }
142}
143
144/// The nine monotonic id allocators, captured for the store header and every
145/// dirty commit's watermark op in a fixed order. Recovery folds the elementwise
146/// maximum of the base header and every replayed frame's watermark so a
147/// canonical id is never reissued across a crash.
148///
149/// # Performance
150///
151/// Copying is `O(1)`.
152#[derive(Clone, Copy, Debug, Eq, PartialEq)]
153pub(crate) struct NextIds {
154    /// Next element id candidate.
155    pub(crate) element: ElementId,
156    /// Next relation id candidate.
157    pub(crate) relation: RelationId,
158    /// Next incidence id candidate.
159    pub(crate) incidence: IncidenceId,
160    /// Next role id candidate.
161    pub(crate) role: RoleId,
162    /// Next label id candidate.
163    pub(crate) label: LabelId,
164    /// Next relation-type id candidate.
165    pub(crate) relation_type: RelationTypeId,
166    /// Next property-key id candidate.
167    pub(crate) property_key: PropertyKeyId,
168    /// Next projection id candidate.
169    pub(crate) projection: ProjectionId,
170    /// Next index id candidate.
171    pub(crate) index: IndexId,
172}
173
174impl NextIds {
175    /// The watermark of an empty store: every allocator starts at `1`, so the
176    /// `0` sentinel ([`crate::wire::RELATION_TYPE_NONE`]) can never collide with
177    /// a real id.
178    ///
179    /// # Performance
180    ///
181    /// `perf: unspecified`; this is a compile-time constant.
182    pub(crate) const INITIAL: Self = Self {
183        element: ElementId::new(1),
184        relation: RelationId::new(1),
185        incidence: IncidenceId::new(1),
186        role: RoleId::new(1),
187        label: LabelId::new(1),
188        relation_type: RelationTypeId::new(1),
189        property_key: PropertyKeyId::new(1),
190        projection: ProjectionId::new(1),
191        index: IndexId::new(1),
192    };
193
194    /// Reads the watermark out of a base header's nine `next_*` allocator
195    /// snapshots.
196    ///
197    /// # Performance
198    ///
199    /// This function is `O(1)`.
200    pub(crate) const fn from_header(header: &DbHeader) -> Self {
201        Self {
202            element: ElementId::new(header.next_element),
203            relation: RelationId::new(header.next_relation),
204            incidence: IncidenceId::new(header.next_incidence),
205            role: RoleId::new(header.next_role),
206            label: LabelId::new(header.next_label),
207            relation_type: RelationTypeId::new(header.next_relation_type),
208            property_key: PropertyKeyId::new(header.next_property_key),
209            projection: ProjectionId::new(header.next_projection),
210            index: IndexId::new(header.next_index),
211        }
212    }
213
214    /// Returns the elementwise maximum of two watermarks, allocator by
215    /// allocator. Recovery folds this over the base header and every replayed
216    /// frame so the recovered watermark sits at or above every id ever issued —
217    /// canonical ids are never reused.
218    ///
219    /// # Performance
220    ///
221    /// This function is `O(1)`.
222    pub(crate) fn elementwise_max(self, other: Self) -> Self {
223        Self {
224            element: self.element.max(other.element),
225            relation: self.relation.max(other.relation),
226            incidence: self.incidence.max(other.incidence),
227            role: self.role.max(other.role),
228            label: self.label.max(other.label),
229            relation_type: self.relation_type.max(other.relation_type),
230            property_key: self.property_key.max(other.property_key),
231            projection: self.projection.max(other.projection),
232            index: self.index.max(other.index),
233        }
234    }
235}