Skip to main content

oxgraph_db/
state.rs

1//! Canonical incidence-first database state.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    Catalog, DbError, ElementId, IncidenceId, IndexId, LabelId, ProjectionDefinition, ProjectionId,
9    PropertyKeyId, RelationId, RelationTypeId, RoleId,
10    catalog::{IndexDefinition, PropertyFamily, PropertyKeyDefinition},
11    value::PropertyValue,
12};
13
14/// One visible canonical element.
15///
16/// # Performance
17///
18/// Cloning is `O(label count)`.
19#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
20pub struct ElementRecord {
21    /// Stable element identifier.
22    pub id: ElementId,
23    /// Labels assigned to this element.
24    pub labels: BTreeSet<LabelId>,
25}
26
27/// One visible canonical relation.
28///
29/// # Performance
30///
31/// Cloning is `O(label count)`.
32#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
33pub struct RelationRecord {
34    /// Stable relation identifier.
35    pub id: RelationId,
36    /// Optional relation type.
37    pub relation_type: Option<RelationTypeId>,
38    /// Labels assigned to this relation.
39    pub labels: BTreeSet<LabelId>,
40}
41
42/// One visible incidence in canonical database coordinates.
43///
44/// # Performance
45///
46/// Copying and comparing this record are `O(1)`.
47#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
48pub struct IncidenceRecord {
49    /// Stable incidence id.
50    pub id: IncidenceId,
51    /// Stable relation id containing the incidence.
52    pub relation: RelationId,
53    /// Stable element id participating in the relation.
54    pub element: ElementId,
55    /// Structural role of the incidence.
56    pub role: RoleId,
57}
58
59/// Subject that can own properties.
60///
61/// # Performance
62///
63/// Copying, comparing, ordering, and hashing are `O(1)`.
64#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
65pub enum PropertySubject {
66    /// Element property subject.
67    Element(ElementId),
68    /// Relation property subject.
69    Relation(RelationId),
70    /// Incidence property subject.
71    Incidence(IncidenceId),
72}
73
74impl PropertySubject {
75    /// Returns the property family for this subject.
76    ///
77    /// # Performance
78    ///
79    /// This function is `O(1)`.
80    #[must_use]
81    pub const fn family(self) -> PropertyFamily {
82        match self {
83            Self::Element(_id) => PropertyFamily::Element,
84            Self::Relation(_id) => PropertyFamily::Relation,
85            Self::Incidence(_id) => PropertyFamily::Incidence,
86        }
87    }
88}
89
90/// Durable canonical database state.
91#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
92pub(crate) struct DatabaseState {
93    /// Visible elements keyed by ID.
94    #[serde(with = "serde_btree_map_vec")]
95    elements: BTreeMap<ElementId, ElementRecord>,
96    /// Visible relations keyed by ID.
97    #[serde(with = "serde_btree_map_vec")]
98    relations: BTreeMap<RelationId, RelationRecord>,
99    /// Visible incidences keyed by ID.
100    #[serde(with = "serde_btree_map_vec")]
101    incidences: BTreeMap<IncidenceId, IncidenceRecord>,
102    /// Property values keyed by subject and property key.
103    #[serde(with = "serde_properties_vec")]
104    properties: BTreeMap<PropertySubject, BTreeMap<PropertyKeyId, PropertyValue>>,
105    /// Catalog metadata.
106    catalog: Catalog,
107    /// Next element ID candidate.
108    next_element: ElementId,
109    /// Next relation ID candidate.
110    next_relation: RelationId,
111    /// Next incidence ID candidate.
112    next_incidence: IncidenceId,
113    /// Next role ID candidate.
114    next_role: RoleId,
115    /// Next label ID candidate.
116    next_label: LabelId,
117    /// Next relation type ID candidate.
118    next_relation_type: RelationTypeId,
119    /// Next property key ID candidate.
120    next_property_key: PropertyKeyId,
121    /// Next projection ID candidate.
122    next_projection: ProjectionId,
123    /// Next index ID candidate.
124    next_index: IndexId,
125}
126
127/// Serde helper for `BTreeMap` values keyed by non-string IDs.
128mod serde_btree_map_vec {
129    /// Serializes a map as an ordered entry array.
130    pub(super) fn serialize<S, K, V>(
131        map: &std::collections::BTreeMap<K, V>,
132        serializer: S,
133    ) -> Result<S::Ok, S::Error>
134    where
135        S: serde::Serializer,
136        K: serde::Serialize,
137        V: serde::Serialize,
138    {
139        serde::Serialize::serialize(&map.iter().collect::<Vec<_>>(), serializer)
140    }
141
142    /// Deserializes a map from an ordered entry array.
143    pub(super) fn deserialize<'de, D, K, V>(
144        deserializer: D,
145    ) -> Result<std::collections::BTreeMap<K, V>, D::Error>
146    where
147        D: serde::Deserializer<'de>,
148        K: Ord + serde::de::DeserializeOwned,
149        V: serde::de::DeserializeOwned,
150    {
151        <Vec<(K, V)> as serde::Deserialize>::deserialize(deserializer)
152            .map(|entries| entries.into_iter().collect())
153    }
154}
155
156/// Serde helper for nested property maps.
157mod serde_properties_vec {
158    use super::{PropertyKeyId, PropertySubject, PropertyValue};
159
160    /// Property entry array shape.
161    type PropertyEntries = Vec<(PropertySubject, Vec<(PropertyKeyId, PropertyValue)>)>;
162    /// Nested property value map.
163    type PropertyValueMap = std::collections::BTreeMap<PropertyKeyId, PropertyValue>;
164    /// Property map keyed by subject.
165    type PropertyMap = std::collections::BTreeMap<PropertySubject, PropertyValueMap>;
166
167    /// Serializes nested property maps as ordered entry arrays.
168    pub(super) fn serialize<S>(map: &PropertyMap, serializer: S) -> Result<S::Ok, S::Error>
169    where
170        S: serde::Serializer,
171    {
172        let entries = map
173            .iter()
174            .map(|(subject, values)| {
175                (
176                    *subject,
177                    values
178                        .iter()
179                        .map(|(key, value)| (*key, value.clone()))
180                        .collect::<Vec<_>>(),
181                )
182            })
183            .collect::<Vec<_>>();
184        serde::Serialize::serialize(&entries, serializer)
185    }
186
187    /// Deserializes nested property maps from ordered entry arrays.
188    pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<PropertyMap, D::Error>
189    where
190        D: serde::Deserializer<'de>,
191    {
192        <PropertyEntries as serde::Deserialize>::deserialize(deserializer).map(|entries| {
193            entries
194                .into_iter()
195                .map(|(subject, values)| (subject, values.into_iter().collect()))
196                .collect()
197        })
198    }
199}
200
201impl DatabaseState {
202    /// Creates an empty canonical state.
203    ///
204    /// # Performance
205    ///
206    /// This function is `O(1)`.
207    #[must_use]
208    pub(crate) const fn empty() -> Self {
209        Self {
210            elements: BTreeMap::new(),
211            relations: BTreeMap::new(),
212            incidences: BTreeMap::new(),
213            properties: BTreeMap::new(),
214            catalog: Catalog::empty(),
215            next_element: ElementId::new(1),
216            next_relation: RelationId::new(1),
217            next_incidence: IncidenceId::new(1),
218            next_role: RoleId::new(1),
219            next_label: LabelId::new(1),
220            next_relation_type: RelationTypeId::new(1),
221            next_property_key: PropertyKeyId::new(1),
222            next_projection: ProjectionId::new(1),
223            next_index: IndexId::new(1),
224        }
225    }
226
227    /// Returns catalog metadata.
228    ///
229    /// # Performance
230    ///
231    /// This method is `O(1)`.
232    #[must_use]
233    pub(crate) const fn catalog(&self) -> &Catalog {
234        &self.catalog
235    }
236
237    /// Returns whether an element exists.
238    ///
239    /// # Performance
240    ///
241    /// This method is `O(log n)`.
242    #[must_use]
243    pub(crate) fn contains_element(&self, id: ElementId) -> bool {
244        self.elements.contains_key(&id)
245    }
246
247    /// Returns whether a relation exists.
248    ///
249    /// # Performance
250    ///
251    /// This method is `O(log n)`.
252    #[must_use]
253    pub(crate) fn contains_relation(&self, id: RelationId) -> bool {
254        self.relations.contains_key(&id)
255    }
256
257    /// Returns whether an incidence exists.
258    ///
259    /// # Performance
260    ///
261    /// This method is `O(log n)`.
262    #[must_use]
263    pub(crate) fn contains_incidence(&self, id: IncidenceId) -> bool {
264        self.incidences.contains_key(&id)
265    }
266
267    /// Returns the number of visible elements.
268    ///
269    /// # Performance
270    ///
271    /// This method is `O(1)`.
272    #[must_use]
273    pub(crate) fn element_count(&self) -> usize {
274        self.elements.len()
275    }
276
277    /// Returns the number of visible relations.
278    ///
279    /// # Performance
280    ///
281    /// This method is `O(1)`.
282    #[must_use]
283    pub(crate) fn relation_count(&self) -> usize {
284        self.relations.len()
285    }
286
287    /// Returns the number of visible incidences.
288    ///
289    /// # Performance
290    ///
291    /// This method is `O(1)`.
292    #[must_use]
293    pub(crate) fn incidence_count(&self) -> usize {
294        self.incidences.len()
295    }
296
297    /// Iterates elements in ID order.
298    ///
299    /// # Performance
300    ///
301    /// Creating the iterator is `O(1)`.
302    pub(crate) fn elements(&self) -> impl Iterator<Item = &ElementRecord> {
303        self.elements.values()
304    }
305
306    /// Iterates relations in ID order.
307    ///
308    /// # Performance
309    ///
310    /// Creating the iterator is `O(1)`.
311    pub(crate) fn relations(&self) -> impl Iterator<Item = &RelationRecord> {
312        self.relations.values()
313    }
314
315    /// Iterates incidences in ID order.
316    ///
317    /// # Performance
318    ///
319    /// Creating the iterator is `O(1)`.
320    pub(crate) fn incidences(&self) -> impl Iterator<Item = &IncidenceRecord> {
321        self.incidences.values()
322    }
323
324    /// Returns one element record.
325    ///
326    /// # Performance
327    ///
328    /// This method is `O(log n)`.
329    #[must_use]
330    pub(crate) fn element(&self, id: ElementId) -> Option<&ElementRecord> {
331        self.elements.get(&id)
332    }
333
334    /// Returns one relation record.
335    ///
336    /// # Performance
337    ///
338    /// This method is `O(log n)`.
339    #[must_use]
340    pub(crate) fn relation(&self, id: RelationId) -> Option<&RelationRecord> {
341        self.relations.get(&id)
342    }
343
344    /// Returns one incidence record.
345    ///
346    /// # Performance
347    ///
348    /// This method is `O(log n)`.
349    #[must_use]
350    pub(crate) fn incidence(&self, id: IncidenceId) -> Option<&IncidenceRecord> {
351        self.incidences.get(&id)
352    }
353
354    /// Returns all incidences for a relation.
355    ///
356    /// # Performance
357    ///
358    /// This method is `O(i)` for visible incidence count.
359    pub(crate) fn relation_incidences(
360        &self,
361        id: RelationId,
362    ) -> impl Iterator<Item = &IncidenceRecord> {
363        self.incidences
364            .values()
365            .filter(move |record| record.relation == id)
366    }
367
368    /// Returns all incidences for an element.
369    ///
370    /// # Performance
371    ///
372    /// This method is `O(i)` for visible incidence count.
373    pub(crate) fn element_incidences(
374        &self,
375        id: ElementId,
376    ) -> impl Iterator<Item = &IncidenceRecord> {
377        self.incidences
378            .values()
379            .filter(move |record| record.element == id)
380    }
381
382    /// Creates an element.
383    pub(crate) fn create_element(&mut self) -> Result<ElementId, DbError> {
384        let id = self.next_element;
385        self.next_element = id.checked_next().ok_or(DbError::IdOverflow)?;
386        let previous = self.elements.insert(
387            id,
388            ElementRecord {
389                id,
390                labels: BTreeSet::new(),
391            },
392        );
393        if previous.is_some() {
394            return Err(DbError::DuplicateId);
395        }
396        Ok(id)
397    }
398
399    /// Creates a relation.
400    pub(crate) fn create_relation(&mut self) -> Result<RelationId, DbError> {
401        let id = self.next_relation;
402        self.next_relation = id.checked_next().ok_or(DbError::IdOverflow)?;
403        let previous = self.relations.insert(
404            id,
405            RelationRecord {
406                id,
407                relation_type: None,
408                labels: BTreeSet::new(),
409            },
410        );
411        if previous.is_some() {
412            return Err(DbError::DuplicateId);
413        }
414        Ok(id)
415    }
416
417    /// Creates an incidence.
418    pub(crate) fn create_incidence(
419        &mut self,
420        relation: RelationId,
421        element: ElementId,
422        role: RoleId,
423    ) -> Result<IncidenceId, DbError> {
424        self.require_relation(relation)?;
425        self.require_element(element)?;
426        self.require_role(role)?;
427        let id = self.next_incidence;
428        self.next_incidence = id.checked_next().ok_or(DbError::IdOverflow)?;
429        let previous = self.incidences.insert(
430            id,
431            IncidenceRecord {
432                id,
433                relation,
434                element,
435                role,
436            },
437        );
438        if previous.is_some() {
439            return Err(DbError::DuplicateId);
440        }
441        Ok(id)
442    }
443
444    /// Tombstones an element and its incidences.
445    pub(crate) fn tombstone_element(&mut self, id: ElementId) -> Result<(), DbError> {
446        self.elements
447            .remove(&id)
448            .ok_or(DbError::UnknownElement { id })?;
449        self.properties.remove(&PropertySubject::Element(id));
450        self.remove_incidences(|record| record.element == id);
451        Ok(())
452    }
453
454    /// Tombstones a relation and its incidences.
455    pub(crate) fn tombstone_relation(&mut self, id: RelationId) -> Result<(), DbError> {
456        self.relations
457            .remove(&id)
458            .ok_or(DbError::UnknownRelation { id })?;
459        self.properties.remove(&PropertySubject::Relation(id));
460        self.remove_incidences(|record| record.relation == id);
461        Ok(())
462    }
463
464    /// Tombstones one incidence.
465    pub(crate) fn tombstone_incidence(&mut self, id: IncidenceId) -> Result<(), DbError> {
466        self.incidences
467            .remove(&id)
468            .ok_or(DbError::UnknownIncidence { id })?;
469        self.properties.remove(&PropertySubject::Incidence(id));
470        Ok(())
471    }
472
473    /// Registers a role.
474    pub(crate) fn register_role(&mut self, name: String) -> Result<RoleId, DbError> {
475        let id = self.next_role;
476        self.next_role = id.checked_next().ok_or(DbError::IdOverflow)?;
477        self.catalog.insert_role(id, name)?;
478        Ok(id)
479    }
480
481    /// Registers a label.
482    pub(crate) fn register_label(&mut self, name: String) -> Result<LabelId, DbError> {
483        let id = self.next_label;
484        self.next_label = id.checked_next().ok_or(DbError::IdOverflow)?;
485        self.catalog.insert_label(id, name)?;
486        Ok(id)
487    }
488
489    /// Registers a relation type.
490    pub(crate) fn register_relation_type(
491        &mut self,
492        name: String,
493    ) -> Result<RelationTypeId, DbError> {
494        let id = self.next_relation_type;
495        self.next_relation_type = id.checked_next().ok_or(DbError::IdOverflow)?;
496        self.catalog.insert_relation_type(id, name)?;
497        Ok(id)
498    }
499
500    /// Registers a property key.
501    pub(crate) fn register_property_key(
502        &mut self,
503        name: String,
504        family: PropertyFamily,
505        value_type: crate::PropertyType,
506    ) -> Result<PropertyKeyId, DbError> {
507        let id = self.next_property_key;
508        self.next_property_key = id.checked_next().ok_or(DbError::IdOverflow)?;
509        self.catalog.insert_property_key(PropertyKeyDefinition {
510            id,
511            name,
512            family,
513            value_type,
514        })?;
515        Ok(id)
516    }
517
518    /// Defines a physical projection.
519    pub(crate) fn define_projection(
520        &mut self,
521        definition: ProjectionDefinition,
522    ) -> Result<ProjectionId, DbError> {
523        self.validate_projection_definition(&definition)?;
524        let id = self.next_projection;
525        self.next_projection = id.checked_next().ok_or(DbError::IdOverflow)?;
526        self.catalog.insert_projection(id, definition)?;
527        Ok(id)
528    }
529
530    /// Defines an index.
531    pub(crate) fn define_index(
532        &mut self,
533        name: String,
534        definition: IndexDefinition,
535    ) -> Result<IndexId, DbError> {
536        self.validate_index_definition(&definition)?;
537        let id = self.next_index;
538        self.next_index = id.checked_next().ok_or(DbError::IdOverflow)?;
539        self.catalog.insert_index(id, name, definition)?;
540        Ok(id)
541    }
542
543    /// Assigns a label to an element.
544    pub(crate) fn add_element_label(
545        &mut self,
546        element: ElementId,
547        label: LabelId,
548    ) -> Result<(), DbError> {
549        self.require_label(label)?;
550        let record = self
551            .elements
552            .get_mut(&element)
553            .ok_or(DbError::UnknownElement { id: element })?;
554        record.labels.insert(label);
555        Ok(())
556    }
557
558    /// Assigns a label to a relation.
559    pub(crate) fn add_relation_label(
560        &mut self,
561        relation: RelationId,
562        label: LabelId,
563    ) -> Result<(), DbError> {
564        self.require_label(label)?;
565        let record = self
566            .relations
567            .get_mut(&relation)
568            .ok_or(DbError::UnknownRelation { id: relation })?;
569        record.labels.insert(label);
570        Ok(())
571    }
572
573    /// Sets a relation type.
574    pub(crate) fn set_relation_type(
575        &mut self,
576        relation: RelationId,
577        relation_type: RelationTypeId,
578    ) -> Result<(), DbError> {
579        self.require_relation_type(relation_type)?;
580        let record = self
581            .relations
582            .get_mut(&relation)
583            .ok_or(DbError::UnknownRelation { id: relation })?;
584        record.relation_type = Some(relation_type);
585        Ok(())
586    }
587
588    /// Sets a typed property value.
589    pub(crate) fn set_property(
590        &mut self,
591        subject: PropertySubject,
592        key: PropertyKeyId,
593        value: PropertyValue,
594    ) -> Result<(), DbError> {
595        self.require_subject(subject)?;
596        let definition = self
597            .catalog
598            .property_key(key)
599            .ok_or(DbError::UnknownPropertyKey { id: key })?;
600        if definition.family != subject.family() {
601            return Err(DbError::WrongPropertyFamily {
602                expected: definition.family,
603                actual: subject.family(),
604            });
605        }
606        if definition.value_type != value.value_type() {
607            return Err(DbError::PropertyTypeMismatch {
608                expected: definition.value_type,
609                actual: value.value_type(),
610            });
611        }
612        self.properties
613            .entry(subject)
614            .or_default()
615            .insert(key, value);
616        Ok(())
617    }
618
619    /// Removes one property value.
620    pub(crate) fn remove_property(
621        &mut self,
622        subject: PropertySubject,
623        key: PropertyKeyId,
624    ) -> Result<(), DbError> {
625        self.require_subject(subject)?;
626        self.require_property_key(key)?;
627        if let Some(values) = self.properties.get_mut(&subject) {
628            values.remove(&key);
629            if values.is_empty() {
630                self.properties.remove(&subject);
631            }
632        }
633        Ok(())
634    }
635
636    /// Returns one property value.
637    #[must_use]
638    pub(crate) fn property(
639        &self,
640        subject: PropertySubject,
641        key: PropertyKeyId,
642    ) -> Option<&PropertyValue> {
643        self.properties
644            .get(&subject)
645            .and_then(|values| values.get(&key))
646    }
647
648    /// Returns subjects whose property equals `value`.
649    pub(crate) fn property_equal(
650        &self,
651        key: PropertyKeyId,
652        value: &PropertyValue,
653    ) -> Vec<PropertySubject> {
654        self.properties
655            .iter()
656            .filter_map(|(subject, values)| (values.get(&key) == Some(value)).then_some(*subject))
657            .collect()
658    }
659
660    /// Returns subjects whose typed property equals `value`.
661    pub(crate) fn typed_property_equal(
662        &self,
663        key: PropertyKeyId,
664        value: &PropertyValue,
665    ) -> Result<Vec<PropertySubject>, DbError> {
666        self.validate_lookup_value(key, value)?;
667        Ok(self.property_equal(key, value))
668    }
669
670    /// Returns subjects in `family` whose typed property equals `value`.
671    pub(crate) fn typed_property_equal_for_family(
672        &self,
673        key: PropertyKeyId,
674        family: PropertyFamily,
675        value: &PropertyValue,
676    ) -> Result<Vec<PropertySubject>, DbError> {
677        self.validate_lookup_value_for_family(key, family, value)?;
678        Ok(self.property_equal(key, value))
679    }
680
681    /// Returns subjects whose ordered property falls inside an inclusive range.
682    pub(crate) fn property_range(
683        &self,
684        key: PropertyKeyId,
685        min: &PropertyValue,
686        max: &PropertyValue,
687    ) -> Vec<PropertySubject> {
688        self.properties
689            .iter()
690            .filter_map(|(subject, values)| {
691                let value = values.get(&key)?;
692                (value >= min && value <= max).then_some(*subject)
693            })
694            .collect()
695    }
696
697    /// Returns subjects whose typed property falls inside an inclusive range.
698    pub(crate) fn typed_property_range(
699        &self,
700        key: PropertyKeyId,
701        min: &PropertyValue,
702        max: &PropertyValue,
703    ) -> Result<Vec<PropertySubject>, DbError> {
704        self.validate_lookup_value(key, min)?;
705        self.validate_lookup_value(key, max)?;
706        if min > max {
707            return Ok(Vec::new());
708        }
709        Ok(self.property_range(key, min, max))
710    }
711
712    /// Returns subjects matching an ordered typed property tuple.
713    pub(crate) fn typed_property_composite_equal(
714        &self,
715        keys: &[PropertyKeyId],
716        values: &[PropertyValue],
717    ) -> Result<Vec<PropertySubject>, DbError> {
718        if keys.len() != values.len() {
719            return Err(DbError::unsupported(
720                "composite equality tuple arity mismatch",
721            ));
722        }
723        for (key, value) in keys.iter().copied().zip(values) {
724            self.validate_lookup_value(key, value)?;
725        }
726        Ok(self
727            .properties
728            .iter()
729            .filter_map(|(subject, property_values)| {
730                keys.iter()
731                    .copied()
732                    .zip(values)
733                    .all(|(key, value)| property_values.get(&key) == Some(value))
734                    .then_some(*subject)
735            })
736            .collect())
737    }
738
739    /// Returns elements that have `label`.
740    pub(crate) fn elements_with_label(&self, label: LabelId) -> Vec<ElementId> {
741        self.elements
742            .values()
743            .filter_map(|record| record.labels.contains(&label).then_some(record.id))
744            .collect()
745    }
746
747    /// Returns relations that have `relation_type`.
748    pub(crate) fn relations_with_type(&self, relation_type: RelationTypeId) -> Vec<RelationId> {
749        self.relations
750            .values()
751            .filter_map(|record| (record.relation_type == Some(relation_type)).then_some(record.id))
752            .collect()
753    }
754
755    /// Validates all persisted references.
756    pub(crate) fn validate(&self) -> Result<(), DbError> {
757        for record in self.incidences.values() {
758            self.require_relation(record.relation)?;
759            self.require_element(record.element)?;
760            self.require_role(record.role)?;
761        }
762        for record in self.elements.values() {
763            self.require_labels(&record.labels)?;
764        }
765        for record in self.relations.values() {
766            self.require_labels(&record.labels)?;
767            if let Some(relation_type) = record.relation_type {
768                self.require_relation_type(relation_type)?;
769            }
770        }
771        self.validate_properties()?;
772        self.validate_catalog_definitions()
773    }
774
775    /// Requires an element to exist.
776    fn require_element(&self, id: ElementId) -> Result<(), DbError> {
777        self.elements
778            .contains_key(&id)
779            .then_some(())
780            .ok_or(DbError::UnknownElement { id })
781    }
782
783    /// Requires a relation to exist.
784    fn require_relation(&self, id: RelationId) -> Result<(), DbError> {
785        self.relations
786            .contains_key(&id)
787            .then_some(())
788            .ok_or(DbError::UnknownRelation { id })
789    }
790
791    /// Requires an incidence to exist.
792    fn require_incidence(&self, id: IncidenceId) -> Result<(), DbError> {
793        self.incidences
794            .contains_key(&id)
795            .then_some(())
796            .ok_or(DbError::UnknownIncidence { id })
797    }
798
799    /// Requires a role to exist.
800    fn require_role(&self, id: RoleId) -> Result<(), DbError> {
801        self.catalog
802            .role(id)
803            .is_some()
804            .then_some(())
805            .ok_or(DbError::UnknownRole { id })
806    }
807
808    /// Requires a label to exist.
809    fn require_label(&self, id: LabelId) -> Result<(), DbError> {
810        self.catalog
811            .label(id)
812            .is_some()
813            .then_some(())
814            .ok_or(DbError::UnknownLabel { id })
815    }
816
817    /// Requires a relation type to exist.
818    fn require_relation_type(&self, id: RelationTypeId) -> Result<(), DbError> {
819        self.catalog
820            .relation_type(id)
821            .is_some()
822            .then_some(())
823            .ok_or(DbError::UnknownRelationType { id })
824    }
825
826    /// Requires a property key to exist.
827    fn require_property_key(&self, id: PropertyKeyId) -> Result<(), DbError> {
828        self.catalog
829            .property_key(id)
830            .is_some()
831            .then_some(())
832            .ok_or(DbError::UnknownPropertyKey { id })
833    }
834
835    /// Requires a property subject to exist.
836    fn require_subject(&self, subject: PropertySubject) -> Result<(), DbError> {
837        match subject {
838            PropertySubject::Element(id) => self.require_element(id),
839            PropertySubject::Relation(id) => self.require_relation(id),
840            PropertySubject::Incidence(id) => self.require_incidence(id),
841        }
842    }
843
844    /// Requires all labels to exist.
845    fn require_labels(&self, labels: &BTreeSet<LabelId>) -> Result<(), DbError> {
846        for label in labels {
847            self.require_label(*label)?;
848        }
849        Ok(())
850    }
851
852    /// Removes matching incidences and their properties.
853    fn remove_incidences(&mut self, mut should_remove: impl FnMut(&IncidenceRecord) -> bool) {
854        let removed: Vec<_> = self
855            .incidences
856            .values()
857            .filter_map(|record| should_remove(record).then_some(record.id))
858            .collect();
859        for id in removed {
860            self.incidences.remove(&id);
861            self.properties.remove(&PropertySubject::Incidence(id));
862        }
863    }
864
865    /// Validates one projection definition.
866    fn validate_projection_definition(
867        &self,
868        definition: &ProjectionDefinition,
869    ) -> Result<(), DbError> {
870        match definition {
871            ProjectionDefinition::Graph(graph) => {
872                self.require_role(graph.source_role)?;
873                self.require_role(graph.target_role)?;
874                self.require_relation_types(&graph.relation_types)
875            }
876            ProjectionDefinition::Hypergraph(hypergraph) => {
877                self.require_roles(&hypergraph.source_roles)?;
878                self.require_roles(&hypergraph.target_roles)?;
879                self.require_relation_types(&hypergraph.relation_types)
880            }
881        }
882    }
883
884    /// Validates one index definition.
885    fn validate_index_definition(&self, definition: &IndexDefinition) -> Result<(), DbError> {
886        match definition {
887            IndexDefinition::Label { label } => self.require_label(*label),
888            IndexDefinition::RelationType { relation_type } => {
889                self.require_relation_type(*relation_type)
890            }
891            IndexDefinition::PropertyEquality { key } | IndexDefinition::PropertyRange { key } => {
892                self.require_property_key(*key)
893            }
894            IndexDefinition::CompositeEquality { keys } => {
895                if keys.is_empty() {
896                    return Err(DbError::unsupported(
897                        "composite equality index requires at least one key",
898                    ));
899                }
900                for key in keys {
901                    self.require_property_key(*key)?;
902                }
903                Ok(())
904            }
905            IndexDefinition::Projection { projection } => self
906                .catalog
907                .projection(*projection)
908                .is_some()
909                .then_some(())
910                .ok_or(DbError::UnknownProjection { id: *projection }),
911        }
912    }
913
914    /// Requires every role in a set to exist.
915    fn require_roles(&self, roles: &BTreeSet<RoleId>) -> Result<(), DbError> {
916        for role in roles {
917            self.require_role(*role)?;
918        }
919        Ok(())
920    }
921
922    /// Requires every relation type in a set to exist.
923    fn require_relation_types(
924        &self,
925        relation_types: &BTreeSet<RelationTypeId>,
926    ) -> Result<(), DbError> {
927        for relation_type in relation_types {
928            self.require_relation_type(*relation_type)?;
929        }
930        Ok(())
931    }
932
933    /// Validates property maps.
934    fn validate_properties(&self) -> Result<(), DbError> {
935        for (subject, values) in &self.properties {
936            self.require_subject(*subject)?;
937            for (key, value) in values {
938                self.validate_property_value(*subject, *key, value)?;
939            }
940        }
941        Ok(())
942    }
943
944    /// Validates a lookup value against a property key schema.
945    fn validate_lookup_value(
946        &self,
947        key: PropertyKeyId,
948        value: &PropertyValue,
949    ) -> Result<(), DbError> {
950        let definition = self
951            .catalog
952            .property_key(key)
953            .ok_or(DbError::UnknownPropertyKey { id: key })?;
954        let actual = value.value_type();
955        if definition.value_type != actual {
956            return Err(DbError::PropertyTypeMismatch {
957                expected: definition.value_type,
958                actual,
959            });
960        }
961        Ok(())
962    }
963
964    /// Validates a lookup value and subject family against a property key schema.
965    pub(crate) fn validate_lookup_value_for_family(
966        &self,
967        key: PropertyKeyId,
968        family: PropertyFamily,
969        value: &PropertyValue,
970    ) -> Result<(), DbError> {
971        let definition = self
972            .catalog
973            .property_key(key)
974            .ok_or(DbError::UnknownPropertyKey { id: key })?;
975        if definition.family != family {
976            return Err(DbError::WrongPropertyFamily {
977                expected: definition.family,
978                actual: family,
979            });
980        }
981        if definition.value_type != value.value_type() {
982            return Err(DbError::PropertyTypeMismatch {
983                expected: definition.value_type,
984                actual: value.value_type(),
985            });
986        }
987        Ok(())
988    }
989
990    /// Validates one property value against the catalog schema.
991    fn validate_property_value(
992        &self,
993        subject: PropertySubject,
994        key: PropertyKeyId,
995        value: &PropertyValue,
996    ) -> Result<(), DbError> {
997        let definition = self
998            .catalog
999            .property_key(key)
1000            .ok_or(DbError::UnknownPropertyKey { id: key })?;
1001        if definition.family != subject.family() {
1002            return Err(DbError::WrongPropertyFamily {
1003                expected: definition.family,
1004                actual: subject.family(),
1005            });
1006        }
1007        if definition.value_type != value.value_type() {
1008            return Err(DbError::PropertyTypeMismatch {
1009                expected: definition.value_type,
1010                actual: value.value_type(),
1011            });
1012        }
1013        Ok(())
1014    }
1015
1016    /// Validates catalog projection and index references.
1017    fn validate_catalog_definitions(&self) -> Result<(), DbError> {
1018        for entry in self.catalog.projections() {
1019            self.validate_projection_definition(&entry.definition)?;
1020        }
1021        for entry in self.catalog.indexes() {
1022            self.validate_index_definition(&entry.definition)?;
1023        }
1024        Ok(())
1025    }
1026}