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}