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