Skip to main content

grafeo_engine/catalog/
mod.rs

1//! Schema metadata - what labels, properties, and indexes exist.
2//!
3//! The catalog is the "dictionary" of your database. When you write `(:Person)`,
4//! the catalog maps "Person" to an internal LabelId. This indirection keeps
5//! storage compact while names stay readable.
6//!
7//! | What it tracks | Why it matters |
8//! | -------------- | -------------- |
9//! | Labels | Maps "Person" → LabelId for efficient storage |
10//! | Property keys | Maps "name" → PropertyKeyId |
11//! | Edge types | Maps "KNOWS" → EdgeTypeId |
12//! | Indexes | Which properties are indexed for fast lookups |
13
14use std::collections::HashMap;
15use std::sync::Arc;
16use std::sync::atomic::{AtomicU32, Ordering};
17
18use parking_lot::RwLock;
19
20use grafeo_common::types::{EdgeTypeId, IndexId, LabelId, PropertyKeyId};
21
22/// The database's schema dictionary - maps names to compact internal IDs.
23///
24/// You rarely interact with this directly. The query processor uses it to
25/// resolve names like "Person" and "name" to internal IDs.
26pub struct Catalog {
27    /// Label name-to-ID mappings.
28    labels: LabelCatalog,
29    /// Property key name-to-ID mappings.
30    property_keys: PropertyCatalog,
31    /// Edge type name-to-ID mappings.
32    edge_types: EdgeTypeCatalog,
33    /// Index definitions.
34    indexes: IndexCatalog,
35    /// Optional schema constraints.
36    schema: Option<SchemaCatalog>,
37}
38
39impl Catalog {
40    /// Creates a new empty catalog.
41    #[must_use]
42    pub fn new() -> Self {
43        Self {
44            labels: LabelCatalog::new(),
45            property_keys: PropertyCatalog::new(),
46            edge_types: EdgeTypeCatalog::new(),
47            indexes: IndexCatalog::new(),
48            schema: None,
49        }
50    }
51
52    /// Creates a new catalog with schema constraints enabled.
53    #[must_use]
54    pub fn with_schema() -> Self {
55        Self {
56            labels: LabelCatalog::new(),
57            property_keys: PropertyCatalog::new(),
58            edge_types: EdgeTypeCatalog::new(),
59            indexes: IndexCatalog::new(),
60            schema: Some(SchemaCatalog::new()),
61        }
62    }
63
64    // === Label Operations ===
65
66    /// Gets or creates a label ID for the given label name.
67    pub fn get_or_create_label(&self, name: &str) -> LabelId {
68        self.labels.get_or_create(name)
69    }
70
71    /// Gets the label ID for a label name, if it exists.
72    #[must_use]
73    pub fn get_label_id(&self, name: &str) -> Option<LabelId> {
74        self.labels.get_id(name)
75    }
76
77    /// Gets the label name for a label ID, if it exists.
78    #[must_use]
79    pub fn get_label_name(&self, id: LabelId) -> Option<Arc<str>> {
80        self.labels.get_name(id)
81    }
82
83    /// Returns the number of distinct labels.
84    #[must_use]
85    pub fn label_count(&self) -> usize {
86        self.labels.count()
87    }
88
89    /// Returns all label names.
90    #[must_use]
91    pub fn all_labels(&self) -> Vec<Arc<str>> {
92        self.labels.all_names()
93    }
94
95    // === Property Key Operations ===
96
97    /// Gets or creates a property key ID for the given property key name.
98    pub fn get_or_create_property_key(&self, name: &str) -> PropertyKeyId {
99        self.property_keys.get_or_create(name)
100    }
101
102    /// Gets the property key ID for a property key name, if it exists.
103    #[must_use]
104    pub fn get_property_key_id(&self, name: &str) -> Option<PropertyKeyId> {
105        self.property_keys.get_id(name)
106    }
107
108    /// Gets the property key name for a property key ID, if it exists.
109    #[must_use]
110    pub fn get_property_key_name(&self, id: PropertyKeyId) -> Option<Arc<str>> {
111        self.property_keys.get_name(id)
112    }
113
114    /// Returns the number of distinct property keys.
115    #[must_use]
116    pub fn property_key_count(&self) -> usize {
117        self.property_keys.count()
118    }
119
120    /// Returns all property key names.
121    #[must_use]
122    pub fn all_property_keys(&self) -> Vec<Arc<str>> {
123        self.property_keys.all_names()
124    }
125
126    // === Edge Type Operations ===
127
128    /// Gets or creates an edge type ID for the given edge type name.
129    pub fn get_or_create_edge_type(&self, name: &str) -> EdgeTypeId {
130        self.edge_types.get_or_create(name)
131    }
132
133    /// Gets the edge type ID for an edge type name, if it exists.
134    #[must_use]
135    pub fn get_edge_type_id(&self, name: &str) -> Option<EdgeTypeId> {
136        self.edge_types.get_id(name)
137    }
138
139    /// Gets the edge type name for an edge type ID, if it exists.
140    #[must_use]
141    pub fn get_edge_type_name(&self, id: EdgeTypeId) -> Option<Arc<str>> {
142        self.edge_types.get_name(id)
143    }
144
145    /// Returns the number of distinct edge types.
146    #[must_use]
147    pub fn edge_type_count(&self) -> usize {
148        self.edge_types.count()
149    }
150
151    /// Returns all edge type names.
152    #[must_use]
153    pub fn all_edge_types(&self) -> Vec<Arc<str>> {
154        self.edge_types.all_names()
155    }
156
157    // === Index Operations ===
158
159    /// Creates a new index on a label and property key.
160    pub fn create_index(
161        &self,
162        label: LabelId,
163        property_key: PropertyKeyId,
164        index_type: IndexType,
165    ) -> IndexId {
166        self.indexes.create(label, property_key, index_type)
167    }
168
169    /// Drops an index by ID.
170    pub fn drop_index(&self, id: IndexId) -> bool {
171        self.indexes.drop(id)
172    }
173
174    /// Gets the index definition for an index ID.
175    #[must_use]
176    pub fn get_index(&self, id: IndexId) -> Option<IndexDefinition> {
177        self.indexes.get(id)
178    }
179
180    /// Finds indexes for a given label.
181    #[must_use]
182    pub fn indexes_for_label(&self, label: LabelId) -> Vec<IndexId> {
183        self.indexes.for_label(label)
184    }
185
186    /// Finds indexes for a given label and property key.
187    #[must_use]
188    pub fn indexes_for_label_property(
189        &self,
190        label: LabelId,
191        property_key: PropertyKeyId,
192    ) -> Vec<IndexId> {
193        self.indexes.for_label_property(label, property_key)
194    }
195
196    /// Returns the number of indexes.
197    #[must_use]
198    pub fn index_count(&self) -> usize {
199        self.indexes.count()
200    }
201
202    // === Schema Operations ===
203
204    /// Returns whether schema constraints are enabled.
205    #[must_use]
206    pub fn has_schema(&self) -> bool {
207        self.schema.is_some()
208    }
209
210    /// Adds a uniqueness constraint.
211    ///
212    /// Returns an error if schema is not enabled or constraint already exists.
213    pub fn add_unique_constraint(
214        &self,
215        label: LabelId,
216        property_key: PropertyKeyId,
217    ) -> Result<(), CatalogError> {
218        match &self.schema {
219            Some(schema) => schema.add_unique_constraint(label, property_key),
220            None => Err(CatalogError::SchemaNotEnabled),
221        }
222    }
223
224    /// Adds a required property constraint (NOT NULL).
225    ///
226    /// Returns an error if schema is not enabled or constraint already exists.
227    pub fn add_required_property(
228        &self,
229        label: LabelId,
230        property_key: PropertyKeyId,
231    ) -> Result<(), CatalogError> {
232        match &self.schema {
233            Some(schema) => schema.add_required_property(label, property_key),
234            None => Err(CatalogError::SchemaNotEnabled),
235        }
236    }
237
238    /// Checks if a property is required for a label.
239    #[must_use]
240    pub fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
241        self.schema
242            .as_ref()
243            .is_some_and(|s| s.is_property_required(label, property_key))
244    }
245
246    /// Checks if a property must be unique for a label.
247    #[must_use]
248    pub fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
249        self.schema
250            .as_ref()
251            .is_some_and(|s| s.is_property_unique(label, property_key))
252    }
253}
254
255impl Default for Catalog {
256    fn default() -> Self {
257        Self::new()
258    }
259}
260
261// === Label Catalog ===
262
263/// Bidirectional mapping between label names and IDs.
264struct LabelCatalog {
265    name_to_id: RwLock<HashMap<Arc<str>, LabelId>>,
266    id_to_name: RwLock<Vec<Arc<str>>>,
267    next_id: AtomicU32,
268}
269
270impl LabelCatalog {
271    fn new() -> Self {
272        Self {
273            name_to_id: RwLock::new(HashMap::new()),
274            id_to_name: RwLock::new(Vec::new()),
275            next_id: AtomicU32::new(0),
276        }
277    }
278
279    fn get_or_create(&self, name: &str) -> LabelId {
280        // Fast path: check if already exists
281        {
282            let name_to_id = self.name_to_id.read();
283            if let Some(&id) = name_to_id.get(name) {
284                return id;
285            }
286        }
287
288        // Slow path: create new entry
289        let mut name_to_id = self.name_to_id.write();
290        let mut id_to_name = self.id_to_name.write();
291
292        // Double-check after acquiring write lock
293        if let Some(&id) = name_to_id.get(name) {
294            return id;
295        }
296
297        let id = LabelId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
298        let name: Arc<str> = name.into();
299        name_to_id.insert(Arc::clone(&name), id);
300        id_to_name.push(name);
301        id
302    }
303
304    fn get_id(&self, name: &str) -> Option<LabelId> {
305        self.name_to_id.read().get(name).copied()
306    }
307
308    fn get_name(&self, id: LabelId) -> Option<Arc<str>> {
309        self.id_to_name.read().get(id.as_u32() as usize).cloned()
310    }
311
312    fn count(&self) -> usize {
313        self.id_to_name.read().len()
314    }
315
316    fn all_names(&self) -> Vec<Arc<str>> {
317        self.id_to_name.read().clone()
318    }
319}
320
321// === Property Catalog ===
322
323/// Bidirectional mapping between property key names and IDs.
324struct PropertyCatalog {
325    name_to_id: RwLock<HashMap<Arc<str>, PropertyKeyId>>,
326    id_to_name: RwLock<Vec<Arc<str>>>,
327    next_id: AtomicU32,
328}
329
330impl PropertyCatalog {
331    fn new() -> Self {
332        Self {
333            name_to_id: RwLock::new(HashMap::new()),
334            id_to_name: RwLock::new(Vec::new()),
335            next_id: AtomicU32::new(0),
336        }
337    }
338
339    fn get_or_create(&self, name: &str) -> PropertyKeyId {
340        // Fast path: check if already exists
341        {
342            let name_to_id = self.name_to_id.read();
343            if let Some(&id) = name_to_id.get(name) {
344                return id;
345            }
346        }
347
348        // Slow path: create new entry
349        let mut name_to_id = self.name_to_id.write();
350        let mut id_to_name = self.id_to_name.write();
351
352        // Double-check after acquiring write lock
353        if let Some(&id) = name_to_id.get(name) {
354            return id;
355        }
356
357        let id = PropertyKeyId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
358        let name: Arc<str> = name.into();
359        name_to_id.insert(Arc::clone(&name), id);
360        id_to_name.push(name);
361        id
362    }
363
364    fn get_id(&self, name: &str) -> Option<PropertyKeyId> {
365        self.name_to_id.read().get(name).copied()
366    }
367
368    fn get_name(&self, id: PropertyKeyId) -> Option<Arc<str>> {
369        self.id_to_name.read().get(id.as_u32() as usize).cloned()
370    }
371
372    fn count(&self) -> usize {
373        self.id_to_name.read().len()
374    }
375
376    fn all_names(&self) -> Vec<Arc<str>> {
377        self.id_to_name.read().clone()
378    }
379}
380
381// === Edge Type Catalog ===
382
383/// Bidirectional mapping between edge type names and IDs.
384struct EdgeTypeCatalog {
385    name_to_id: RwLock<HashMap<Arc<str>, EdgeTypeId>>,
386    id_to_name: RwLock<Vec<Arc<str>>>,
387    next_id: AtomicU32,
388}
389
390impl EdgeTypeCatalog {
391    fn new() -> Self {
392        Self {
393            name_to_id: RwLock::new(HashMap::new()),
394            id_to_name: RwLock::new(Vec::new()),
395            next_id: AtomicU32::new(0),
396        }
397    }
398
399    fn get_or_create(&self, name: &str) -> EdgeTypeId {
400        // Fast path: check if already exists
401        {
402            let name_to_id = self.name_to_id.read();
403            if let Some(&id) = name_to_id.get(name) {
404                return id;
405            }
406        }
407
408        // Slow path: create new entry
409        let mut name_to_id = self.name_to_id.write();
410        let mut id_to_name = self.id_to_name.write();
411
412        // Double-check after acquiring write lock
413        if let Some(&id) = name_to_id.get(name) {
414            return id;
415        }
416
417        let id = EdgeTypeId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
418        let name: Arc<str> = name.into();
419        name_to_id.insert(Arc::clone(&name), id);
420        id_to_name.push(name);
421        id
422    }
423
424    fn get_id(&self, name: &str) -> Option<EdgeTypeId> {
425        self.name_to_id.read().get(name).copied()
426    }
427
428    fn get_name(&self, id: EdgeTypeId) -> Option<Arc<str>> {
429        self.id_to_name.read().get(id.as_u32() as usize).cloned()
430    }
431
432    fn count(&self) -> usize {
433        self.id_to_name.read().len()
434    }
435
436    fn all_names(&self) -> Vec<Arc<str>> {
437        self.id_to_name.read().clone()
438    }
439}
440
441// === Index Catalog ===
442
443/// Type of index.
444#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445pub enum IndexType {
446    /// Hash index for equality lookups.
447    Hash,
448    /// BTree index for range queries.
449    BTree,
450    /// Full-text index for text search.
451    FullText,
452}
453
454/// Index definition.
455#[derive(Debug, Clone)]
456pub struct IndexDefinition {
457    /// The index ID.
458    pub id: IndexId,
459    /// The label this index applies to.
460    pub label: LabelId,
461    /// The property key being indexed.
462    pub property_key: PropertyKeyId,
463    /// The type of index.
464    pub index_type: IndexType,
465}
466
467/// Manages index definitions.
468struct IndexCatalog {
469    indexes: RwLock<HashMap<IndexId, IndexDefinition>>,
470    label_indexes: RwLock<HashMap<LabelId, Vec<IndexId>>>,
471    label_property_indexes: RwLock<HashMap<(LabelId, PropertyKeyId), Vec<IndexId>>>,
472    next_id: AtomicU32,
473}
474
475impl IndexCatalog {
476    fn new() -> Self {
477        Self {
478            indexes: RwLock::new(HashMap::new()),
479            label_indexes: RwLock::new(HashMap::new()),
480            label_property_indexes: RwLock::new(HashMap::new()),
481            next_id: AtomicU32::new(0),
482        }
483    }
484
485    fn create(
486        &self,
487        label: LabelId,
488        property_key: PropertyKeyId,
489        index_type: IndexType,
490    ) -> IndexId {
491        let id = IndexId::new(self.next_id.fetch_add(1, Ordering::Relaxed));
492        let definition = IndexDefinition {
493            id,
494            label,
495            property_key,
496            index_type,
497        };
498
499        let mut indexes = self.indexes.write();
500        let mut label_indexes = self.label_indexes.write();
501        let mut label_property_indexes = self.label_property_indexes.write();
502
503        indexes.insert(id, definition);
504        label_indexes.entry(label).or_default().push(id);
505        label_property_indexes
506            .entry((label, property_key))
507            .or_default()
508            .push(id);
509
510        id
511    }
512
513    fn drop(&self, id: IndexId) -> bool {
514        let mut indexes = self.indexes.write();
515        let mut label_indexes = self.label_indexes.write();
516        let mut label_property_indexes = self.label_property_indexes.write();
517
518        if let Some(definition) = indexes.remove(&id) {
519            // Remove from label index
520            if let Some(ids) = label_indexes.get_mut(&definition.label) {
521                ids.retain(|&i| i != id);
522            }
523            // Remove from label-property index
524            if let Some(ids) =
525                label_property_indexes.get_mut(&(definition.label, definition.property_key))
526            {
527                ids.retain(|&i| i != id);
528            }
529            true
530        } else {
531            false
532        }
533    }
534
535    fn get(&self, id: IndexId) -> Option<IndexDefinition> {
536        self.indexes.read().get(&id).cloned()
537    }
538
539    fn for_label(&self, label: LabelId) -> Vec<IndexId> {
540        self.label_indexes
541            .read()
542            .get(&label)
543            .cloned()
544            .unwrap_or_default()
545    }
546
547    fn for_label_property(&self, label: LabelId, property_key: PropertyKeyId) -> Vec<IndexId> {
548        self.label_property_indexes
549            .read()
550            .get(&(label, property_key))
551            .cloned()
552            .unwrap_or_default()
553    }
554
555    fn count(&self) -> usize {
556        self.indexes.read().len()
557    }
558}
559
560// === Schema Catalog ===
561
562/// Schema constraints.
563struct SchemaCatalog {
564    /// Properties that must be unique for a given label.
565    unique_constraints: RwLock<HashMap<(LabelId, PropertyKeyId), ()>>,
566    /// Properties that are required (NOT NULL) for a given label.
567    required_properties: RwLock<HashMap<(LabelId, PropertyKeyId), ()>>,
568}
569
570impl SchemaCatalog {
571    fn new() -> Self {
572        Self {
573            unique_constraints: RwLock::new(HashMap::new()),
574            required_properties: RwLock::new(HashMap::new()),
575        }
576    }
577
578    fn add_unique_constraint(
579        &self,
580        label: LabelId,
581        property_key: PropertyKeyId,
582    ) -> Result<(), CatalogError> {
583        let mut constraints = self.unique_constraints.write();
584        let key = (label, property_key);
585        if constraints.contains_key(&key) {
586            return Err(CatalogError::ConstraintAlreadyExists);
587        }
588        constraints.insert(key, ());
589        Ok(())
590    }
591
592    fn add_required_property(
593        &self,
594        label: LabelId,
595        property_key: PropertyKeyId,
596    ) -> Result<(), CatalogError> {
597        let mut required = self.required_properties.write();
598        let key = (label, property_key);
599        if required.contains_key(&key) {
600            return Err(CatalogError::ConstraintAlreadyExists);
601        }
602        required.insert(key, ());
603        Ok(())
604    }
605
606    fn is_property_required(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
607        self.required_properties
608            .read()
609            .contains_key(&(label, property_key))
610    }
611
612    fn is_property_unique(&self, label: LabelId, property_key: PropertyKeyId) -> bool {
613        self.unique_constraints
614            .read()
615            .contains_key(&(label, property_key))
616    }
617}
618
619// === Errors ===
620
621/// Catalog-related errors.
622#[derive(Debug, Clone, PartialEq, Eq)]
623pub enum CatalogError {
624    /// Schema constraints are not enabled.
625    SchemaNotEnabled,
626    /// The constraint already exists.
627    ConstraintAlreadyExists,
628    /// The label does not exist.
629    LabelNotFound(String),
630    /// The property key does not exist.
631    PropertyKeyNotFound(String),
632    /// The edge type does not exist.
633    EdgeTypeNotFound(String),
634    /// The index does not exist.
635    IndexNotFound(IndexId),
636}
637
638impl std::fmt::Display for CatalogError {
639    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
640        match self {
641            Self::SchemaNotEnabled => write!(f, "Schema constraints are not enabled"),
642            Self::ConstraintAlreadyExists => write!(f, "Constraint already exists"),
643            Self::LabelNotFound(name) => write!(f, "Label not found: {name}"),
644            Self::PropertyKeyNotFound(name) => write!(f, "Property key not found: {name}"),
645            Self::EdgeTypeNotFound(name) => write!(f, "Edge type not found: {name}"),
646            Self::IndexNotFound(id) => write!(f, "Index not found: {id}"),
647        }
648    }
649}
650
651impl std::error::Error for CatalogError {}
652
653#[cfg(test)]
654mod tests {
655    use super::*;
656    use std::thread;
657
658    #[test]
659    fn test_catalog_labels() {
660        let catalog = Catalog::new();
661
662        // Get or create labels
663        let person_id = catalog.get_or_create_label("Person");
664        let company_id = catalog.get_or_create_label("Company");
665
666        // IDs should be different
667        assert_ne!(person_id, company_id);
668
669        // Getting the same label should return the same ID
670        assert_eq!(catalog.get_or_create_label("Person"), person_id);
671
672        // Should be able to look up by name
673        assert_eq!(catalog.get_label_id("Person"), Some(person_id));
674        assert_eq!(catalog.get_label_id("Company"), Some(company_id));
675        assert_eq!(catalog.get_label_id("Unknown"), None);
676
677        // Should be able to look up by ID
678        assert_eq!(catalog.get_label_name(person_id).as_deref(), Some("Person"));
679        assert_eq!(
680            catalog.get_label_name(company_id).as_deref(),
681            Some("Company")
682        );
683
684        // Count should be correct
685        assert_eq!(catalog.label_count(), 2);
686    }
687
688    #[test]
689    fn test_catalog_property_keys() {
690        let catalog = Catalog::new();
691
692        let name_id = catalog.get_or_create_property_key("name");
693        let age_id = catalog.get_or_create_property_key("age");
694
695        assert_ne!(name_id, age_id);
696        assert_eq!(catalog.get_or_create_property_key("name"), name_id);
697        assert_eq!(catalog.get_property_key_id("name"), Some(name_id));
698        assert_eq!(
699            catalog.get_property_key_name(name_id).as_deref(),
700            Some("name")
701        );
702        assert_eq!(catalog.property_key_count(), 2);
703    }
704
705    #[test]
706    fn test_catalog_edge_types() {
707        let catalog = Catalog::new();
708
709        let knows_id = catalog.get_or_create_edge_type("KNOWS");
710        let works_at_id = catalog.get_or_create_edge_type("WORKS_AT");
711
712        assert_ne!(knows_id, works_at_id);
713        assert_eq!(catalog.get_or_create_edge_type("KNOWS"), knows_id);
714        assert_eq!(catalog.get_edge_type_id("KNOWS"), Some(knows_id));
715        assert_eq!(
716            catalog.get_edge_type_name(knows_id).as_deref(),
717            Some("KNOWS")
718        );
719        assert_eq!(catalog.edge_type_count(), 2);
720    }
721
722    #[test]
723    fn test_catalog_indexes() {
724        let catalog = Catalog::new();
725
726        let person_id = catalog.get_or_create_label("Person");
727        let name_id = catalog.get_or_create_property_key("name");
728        let age_id = catalog.get_or_create_property_key("age");
729
730        // Create indexes
731        let idx1 = catalog.create_index(person_id, name_id, IndexType::Hash);
732        let idx2 = catalog.create_index(person_id, age_id, IndexType::BTree);
733
734        assert_ne!(idx1, idx2);
735        assert_eq!(catalog.index_count(), 2);
736
737        // Look up by label
738        let label_indexes = catalog.indexes_for_label(person_id);
739        assert_eq!(label_indexes.len(), 2);
740        assert!(label_indexes.contains(&idx1));
741        assert!(label_indexes.contains(&idx2));
742
743        // Look up by label and property
744        let name_indexes = catalog.indexes_for_label_property(person_id, name_id);
745        assert_eq!(name_indexes.len(), 1);
746        assert_eq!(name_indexes[0], idx1);
747
748        // Get definition
749        let def = catalog.get_index(idx1).unwrap();
750        assert_eq!(def.label, person_id);
751        assert_eq!(def.property_key, name_id);
752        assert_eq!(def.index_type, IndexType::Hash);
753
754        // Drop index
755        assert!(catalog.drop_index(idx1));
756        assert_eq!(catalog.index_count(), 1);
757        assert!(catalog.get_index(idx1).is_none());
758        assert_eq!(catalog.indexes_for_label(person_id).len(), 1);
759    }
760
761    #[test]
762    fn test_catalog_schema_constraints() {
763        let catalog = Catalog::with_schema();
764
765        let person_id = catalog.get_or_create_label("Person");
766        let email_id = catalog.get_or_create_property_key("email");
767        let name_id = catalog.get_or_create_property_key("name");
768
769        // Add constraints
770        assert!(catalog.add_unique_constraint(person_id, email_id).is_ok());
771        assert!(catalog.add_required_property(person_id, name_id).is_ok());
772
773        // Check constraints
774        assert!(catalog.is_property_unique(person_id, email_id));
775        assert!(!catalog.is_property_unique(person_id, name_id));
776        assert!(catalog.is_property_required(person_id, name_id));
777        assert!(!catalog.is_property_required(person_id, email_id));
778
779        // Duplicate constraint should fail
780        assert_eq!(
781            catalog.add_unique_constraint(person_id, email_id),
782            Err(CatalogError::ConstraintAlreadyExists)
783        );
784    }
785
786    #[test]
787    fn test_catalog_no_schema() {
788        let catalog = Catalog::new();
789
790        let person_id = catalog.get_or_create_label("Person");
791        let email_id = catalog.get_or_create_property_key("email");
792
793        // Should fail without schema
794        assert_eq!(
795            catalog.add_unique_constraint(person_id, email_id),
796            Err(CatalogError::SchemaNotEnabled)
797        );
798    }
799
800    // === Additional tests for comprehensive coverage ===
801
802    #[test]
803    fn test_catalog_default() {
804        let catalog = Catalog::default();
805        assert!(!catalog.has_schema());
806        assert_eq!(catalog.label_count(), 0);
807        assert_eq!(catalog.property_key_count(), 0);
808        assert_eq!(catalog.edge_type_count(), 0);
809        assert_eq!(catalog.index_count(), 0);
810    }
811
812    #[test]
813    fn test_catalog_all_labels() {
814        let catalog = Catalog::new();
815
816        catalog.get_or_create_label("Person");
817        catalog.get_or_create_label("Company");
818        catalog.get_or_create_label("Product");
819
820        let all = catalog.all_labels();
821        assert_eq!(all.len(), 3);
822        assert!(all.iter().any(|l| l.as_ref() == "Person"));
823        assert!(all.iter().any(|l| l.as_ref() == "Company"));
824        assert!(all.iter().any(|l| l.as_ref() == "Product"));
825    }
826
827    #[test]
828    fn test_catalog_all_property_keys() {
829        let catalog = Catalog::new();
830
831        catalog.get_or_create_property_key("name");
832        catalog.get_or_create_property_key("age");
833        catalog.get_or_create_property_key("email");
834
835        let all = catalog.all_property_keys();
836        assert_eq!(all.len(), 3);
837        assert!(all.iter().any(|k| k.as_ref() == "name"));
838        assert!(all.iter().any(|k| k.as_ref() == "age"));
839        assert!(all.iter().any(|k| k.as_ref() == "email"));
840    }
841
842    #[test]
843    fn test_catalog_all_edge_types() {
844        let catalog = Catalog::new();
845
846        catalog.get_or_create_edge_type("KNOWS");
847        catalog.get_or_create_edge_type("WORKS_AT");
848        catalog.get_or_create_edge_type("LIVES_IN");
849
850        let all = catalog.all_edge_types();
851        assert_eq!(all.len(), 3);
852        assert!(all.iter().any(|t| t.as_ref() == "KNOWS"));
853        assert!(all.iter().any(|t| t.as_ref() == "WORKS_AT"));
854        assert!(all.iter().any(|t| t.as_ref() == "LIVES_IN"));
855    }
856
857    #[test]
858    fn test_catalog_invalid_id_lookup() {
859        let catalog = Catalog::new();
860
861        // Create one label to ensure IDs are allocated
862        let _ = catalog.get_or_create_label("Person");
863
864        // Try to look up non-existent IDs
865        let invalid_label = LabelId::new(999);
866        let invalid_property = PropertyKeyId::new(999);
867        let invalid_edge_type = EdgeTypeId::new(999);
868        let invalid_index = IndexId::new(999);
869
870        assert!(catalog.get_label_name(invalid_label).is_none());
871        assert!(catalog.get_property_key_name(invalid_property).is_none());
872        assert!(catalog.get_edge_type_name(invalid_edge_type).is_none());
873        assert!(catalog.get_index(invalid_index).is_none());
874    }
875
876    #[test]
877    fn test_catalog_drop_nonexistent_index() {
878        let catalog = Catalog::new();
879        let invalid_index = IndexId::new(999);
880        assert!(!catalog.drop_index(invalid_index));
881    }
882
883    #[test]
884    fn test_catalog_indexes_for_nonexistent_label() {
885        let catalog = Catalog::new();
886        let invalid_label = LabelId::new(999);
887        let invalid_property = PropertyKeyId::new(999);
888
889        assert!(catalog.indexes_for_label(invalid_label).is_empty());
890        assert!(
891            catalog
892                .indexes_for_label_property(invalid_label, invalid_property)
893                .is_empty()
894        );
895    }
896
897    #[test]
898    fn test_catalog_multiple_indexes_same_property() {
899        let catalog = Catalog::new();
900
901        let person_id = catalog.get_or_create_label("Person");
902        let name_id = catalog.get_or_create_property_key("name");
903
904        // Create multiple indexes on the same property with different types
905        let hash_idx = catalog.create_index(person_id, name_id, IndexType::Hash);
906        let btree_idx = catalog.create_index(person_id, name_id, IndexType::BTree);
907        let fulltext_idx = catalog.create_index(person_id, name_id, IndexType::FullText);
908
909        assert_eq!(catalog.index_count(), 3);
910
911        let indexes = catalog.indexes_for_label_property(person_id, name_id);
912        assert_eq!(indexes.len(), 3);
913        assert!(indexes.contains(&hash_idx));
914        assert!(indexes.contains(&btree_idx));
915        assert!(indexes.contains(&fulltext_idx));
916
917        // Verify each has the correct type
918        assert_eq!(
919            catalog.get_index(hash_idx).unwrap().index_type,
920            IndexType::Hash
921        );
922        assert_eq!(
923            catalog.get_index(btree_idx).unwrap().index_type,
924            IndexType::BTree
925        );
926        assert_eq!(
927            catalog.get_index(fulltext_idx).unwrap().index_type,
928            IndexType::FullText
929        );
930    }
931
932    #[test]
933    fn test_catalog_schema_required_property_duplicate() {
934        let catalog = Catalog::with_schema();
935
936        let person_id = catalog.get_or_create_label("Person");
937        let name_id = catalog.get_or_create_property_key("name");
938
939        // First should succeed
940        assert!(catalog.add_required_property(person_id, name_id).is_ok());
941
942        // Duplicate should fail
943        assert_eq!(
944            catalog.add_required_property(person_id, name_id),
945            Err(CatalogError::ConstraintAlreadyExists)
946        );
947    }
948
949    #[test]
950    fn test_catalog_schema_check_without_constraints() {
951        let catalog = Catalog::new();
952
953        let person_id = catalog.get_or_create_label("Person");
954        let name_id = catalog.get_or_create_property_key("name");
955
956        // Without schema enabled, these should return false
957        assert!(!catalog.is_property_unique(person_id, name_id));
958        assert!(!catalog.is_property_required(person_id, name_id));
959    }
960
961    #[test]
962    fn test_catalog_has_schema() {
963        let without_schema = Catalog::new();
964        assert!(!without_schema.has_schema());
965
966        let with_schema = Catalog::with_schema();
967        assert!(with_schema.has_schema());
968    }
969
970    #[test]
971    fn test_catalog_error_display() {
972        assert_eq!(
973            CatalogError::SchemaNotEnabled.to_string(),
974            "Schema constraints are not enabled"
975        );
976        assert_eq!(
977            CatalogError::ConstraintAlreadyExists.to_string(),
978            "Constraint already exists"
979        );
980        assert_eq!(
981            CatalogError::LabelNotFound("Person".to_string()).to_string(),
982            "Label not found: Person"
983        );
984        assert_eq!(
985            CatalogError::PropertyKeyNotFound("name".to_string()).to_string(),
986            "Property key not found: name"
987        );
988        assert_eq!(
989            CatalogError::EdgeTypeNotFound("KNOWS".to_string()).to_string(),
990            "Edge type not found: KNOWS"
991        );
992        let idx = IndexId::new(42);
993        assert!(CatalogError::IndexNotFound(idx).to_string().contains("42"));
994    }
995
996    #[test]
997    fn test_catalog_concurrent_label_creation() {
998        use std::sync::Arc;
999
1000        let catalog = Arc::new(Catalog::new());
1001        let mut handles = vec![];
1002
1003        // Spawn multiple threads trying to create the same labels
1004        for i in 0..10 {
1005            let catalog = Arc::clone(&catalog);
1006            handles.push(thread::spawn(move || {
1007                let label_name = format!("Label{}", i % 3); // Only 3 unique labels
1008                catalog.get_or_create_label(&label_name)
1009            }));
1010        }
1011
1012        let mut ids: Vec<LabelId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
1013        ids.sort_by_key(|id| id.as_u32());
1014        ids.dedup();
1015
1016        // Should only have 3 unique label IDs
1017        assert_eq!(ids.len(), 3);
1018        assert_eq!(catalog.label_count(), 3);
1019    }
1020
1021    #[test]
1022    fn test_catalog_concurrent_property_key_creation() {
1023        use std::sync::Arc;
1024
1025        let catalog = Arc::new(Catalog::new());
1026        let mut handles = vec![];
1027
1028        for i in 0..10 {
1029            let catalog = Arc::clone(&catalog);
1030            handles.push(thread::spawn(move || {
1031                let key_name = format!("key{}", i % 4);
1032                catalog.get_or_create_property_key(&key_name)
1033            }));
1034        }
1035
1036        let mut ids: Vec<PropertyKeyId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
1037        ids.sort_by_key(|id| id.as_u32());
1038        ids.dedup();
1039
1040        assert_eq!(ids.len(), 4);
1041        assert_eq!(catalog.property_key_count(), 4);
1042    }
1043
1044    #[test]
1045    fn test_catalog_concurrent_index_operations() {
1046        use std::sync::Arc;
1047
1048        let catalog = Arc::new(Catalog::new());
1049        let label = catalog.get_or_create_label("Node");
1050
1051        let mut handles = vec![];
1052
1053        // Create indexes concurrently
1054        for i in 0..5 {
1055            let catalog = Arc::clone(&catalog);
1056            handles.push(thread::spawn(move || {
1057                let prop = PropertyKeyId::new(i);
1058                catalog.create_index(label, prop, IndexType::Hash)
1059            }));
1060        }
1061
1062        let ids: Vec<IndexId> = handles.into_iter().map(|h| h.join().unwrap()).collect();
1063        assert_eq!(ids.len(), 5);
1064        assert_eq!(catalog.index_count(), 5);
1065    }
1066
1067    #[test]
1068    fn test_catalog_special_characters_in_names() {
1069        let catalog = Catalog::new();
1070
1071        // Test with various special characters
1072        let label1 = catalog.get_or_create_label("Label With Spaces");
1073        let label2 = catalog.get_or_create_label("Label-With-Dashes");
1074        let label3 = catalog.get_or_create_label("Label_With_Underscores");
1075        let label4 = catalog.get_or_create_label("LabelWithUnicode\u{00E9}");
1076
1077        assert_ne!(label1, label2);
1078        assert_ne!(label2, label3);
1079        assert_ne!(label3, label4);
1080
1081        assert_eq!(
1082            catalog.get_label_name(label1).as_deref(),
1083            Some("Label With Spaces")
1084        );
1085        assert_eq!(
1086            catalog.get_label_name(label4).as_deref(),
1087            Some("LabelWithUnicode\u{00E9}")
1088        );
1089    }
1090
1091    #[test]
1092    fn test_catalog_empty_names() {
1093        let catalog = Catalog::new();
1094
1095        // Empty names should be valid (edge case)
1096        let empty_label = catalog.get_or_create_label("");
1097        let empty_prop = catalog.get_or_create_property_key("");
1098        let empty_edge = catalog.get_or_create_edge_type("");
1099
1100        assert_eq!(catalog.get_label_name(empty_label).as_deref(), Some(""));
1101        assert_eq!(
1102            catalog.get_property_key_name(empty_prop).as_deref(),
1103            Some("")
1104        );
1105        assert_eq!(catalog.get_edge_type_name(empty_edge).as_deref(), Some(""));
1106
1107        // Calling again should return same ID
1108        assert_eq!(catalog.get_or_create_label(""), empty_label);
1109    }
1110
1111    #[test]
1112    fn test_catalog_large_number_of_entries() {
1113        let catalog = Catalog::new();
1114
1115        // Create many labels
1116        for i in 0..1000 {
1117            catalog.get_or_create_label(&format!("Label{}", i));
1118        }
1119
1120        assert_eq!(catalog.label_count(), 1000);
1121
1122        // Verify we can retrieve them all
1123        let all = catalog.all_labels();
1124        assert_eq!(all.len(), 1000);
1125
1126        // Verify a specific one
1127        let id = catalog.get_label_id("Label500").unwrap();
1128        assert_eq!(catalog.get_label_name(id).as_deref(), Some("Label500"));
1129    }
1130
1131    #[test]
1132    fn test_index_definition_debug() {
1133        let def = IndexDefinition {
1134            id: IndexId::new(1),
1135            label: LabelId::new(2),
1136            property_key: PropertyKeyId::new(3),
1137            index_type: IndexType::Hash,
1138        };
1139
1140        // Should be able to debug print
1141        let debug_str = format!("{:?}", def);
1142        assert!(debug_str.contains("IndexDefinition"));
1143        assert!(debug_str.contains("Hash"));
1144    }
1145
1146    #[test]
1147    fn test_index_type_equality() {
1148        assert_eq!(IndexType::Hash, IndexType::Hash);
1149        assert_ne!(IndexType::Hash, IndexType::BTree);
1150        assert_ne!(IndexType::BTree, IndexType::FullText);
1151
1152        // Clone
1153        let t = IndexType::Hash;
1154        let t2 = t;
1155        assert_eq!(t, t2);
1156    }
1157
1158    #[test]
1159    fn test_catalog_error_equality() {
1160        assert_eq!(
1161            CatalogError::SchemaNotEnabled,
1162            CatalogError::SchemaNotEnabled
1163        );
1164        assert_eq!(
1165            CatalogError::ConstraintAlreadyExists,
1166            CatalogError::ConstraintAlreadyExists
1167        );
1168        assert_eq!(
1169            CatalogError::LabelNotFound("X".to_string()),
1170            CatalogError::LabelNotFound("X".to_string())
1171        );
1172        assert_ne!(
1173            CatalogError::LabelNotFound("X".to_string()),
1174            CatalogError::LabelNotFound("Y".to_string())
1175        );
1176    }
1177}