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}